diff --git a/src/renderer.cpp b/src/renderer.cpp
index c785939df..0c0df715d 100644
--- a/src/renderer.cpp
+++ b/src/renderer.cpp
@@ -1,1911 +1,1668 @@
/***************************************************************************
krender.cpp - description
-------------------
begin : Fri Nov 22 2002
copyright : (C) 2002 by Jason Wood
email : jasonwood@blueyonder.co.uk
copyright : (C) 2005 Lucio Flavio Correa
email : lucio.correa@gmail.com
copyright : (C) Marco Gittler
email : g.marco@freenet.de
copyright : (C) 2006 Jean-Baptiste Mardelle
email : jb@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) any later version. *
* *
***************************************************************************/
#include "renderer.h"
#include "kdenlivesettings.h"
#include "doc/kthumb.h"
#include "definitions.h"
#include "project/dialogs/slideshowclip.h"
#include "dialogs/profilesdialog.h"
#include "mltcontroller/bincontroller.h"
#include "bin/projectclip.h"
#include "timeline/clip.h"
#include "monitor/glwidget.h"
#include "mltcontroller/clipcontroller.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SEEK_INACTIVE (-1)
Render::Render(Kdenlive::MonitorId rendererName, BinController *binController, GLWidget *qmlView, QWidget *parent) :
AbstractRender(rendererName, parent),
requestedSeekPosition(SEEK_INACTIVE),
showFrameSemaphore(1),
externalConsumer(false),
m_name(rendererName),
m_mltConsumer(NULL),
m_mltProducer(NULL),
m_showFrameEvent(NULL),
m_pauseEvent(NULL),
m_binController(binController),
m_qmlView(qmlView),
m_isZoneMode(false),
m_isLoopMode(false),
m_blackClip(NULL),
m_isActive(false),
m_isRefreshing(false),
m_abortPreview(false)
{
qRegisterMetaType ("stringMap");
analyseAudio = KdenliveSettings::monitor_audio();
//buildConsumer();
if (m_qmlView) {
m_blackClip = new Mlt::Producer(*m_qmlView->profile(), "colour:black");
m_blackClip->set("id", "black");
m_blackClip->set("mlt_type", "producer");
m_mltProducer = m_blackClip->cut(0, 1);
m_qmlView->setProducer(m_mltProducer);
m_mltConsumer = qmlView->consumer();
}
/*m_mltConsumer->connect(*m_mltProducer);
m_mltProducer->set_speed(0.0);*/
m_refreshTimer.setSingleShot(true);
m_refreshTimer.setInterval(50);
connect(&m_refreshTimer, SIGNAL(timeout()), this, SLOT(refresh()));
connect(this, SIGNAL(checkSeeking()), this, SLOT(slotCheckSeeking()));
if (m_name == Kdenlive::ProjectMonitor) {
connect(m_binController, SIGNAL(prepareTimelineReplacement(QString)), this, SIGNAL(prepareTimelineReplacement(QString)), Qt::DirectConnection);
connect(m_binController, SIGNAL(replaceTimelineProducer(QString)), this, SIGNAL(replaceTimelineProducer(QString)), Qt::DirectConnection);
connect(m_binController, SIGNAL(updateTimelineProducer(QString)), this, SIGNAL(updateTimelineProducer(QString)));
connect(m_binController, SIGNAL(setDocumentNotes(QString)), this, SIGNAL(setDocumentNotes(QString)));
}
}
Render::~Render()
{
m_abortPreview = true;
closeMlt();
}
void Render::closeMlt()
{
delete m_showFrameEvent;
delete m_pauseEvent;
delete m_mltConsumer;
delete m_mltProducer;
delete m_blackClip;
m_previewThread.waitForFinished();
}
void Render::slotSwitchFullscreen()
{
if (m_mltConsumer)
m_mltConsumer->set("full_screen", 1);
}
void Render::prepareProfileReset(double fps)
{
m_refreshTimer.stop();
m_fps = fps;
}
void Render::finishProfileReset()
{
delete m_blackClip;
m_blackClip = new Mlt::Producer(*m_qmlView->profile(), "colour:black");
m_blackClip->set("id", "black");
m_blackClip->set("mlt_type", "producer");
}
void Render::seek(const GenTime &time)
{
if (!m_mltProducer || !m_isActive)
return;
int pos = time.frames(m_fps);
seek(pos);
}
void Render::seek(int time)
{
resetZoneMode();
time = qBound(0, time, m_mltProducer->get_length() - 1);
if (requestedSeekPosition == SEEK_INACTIVE) {
requestedSeekPosition = time;
if (m_mltProducer->get_speed() != 0) {
m_mltConsumer->purge();
}
m_mltProducer->seek(time);
if (!externalConsumer) {
m_isRefreshing = true;
m_mltConsumer->set("refresh", 1);
}
}
else {
requestedSeekPosition = time;
}
}
int Render::frameRenderWidth() const
{
return m_qmlView->profile()->width();
}
int Render::renderWidth() const
{
return (int)(m_qmlView->profile()->height() * m_qmlView->profile()->dar() + 0.5);
}
int Render::renderHeight() const
{
return m_qmlView->profile()->height();
}
QImage Render::extractFrame(int frame_position, const QString &path, int width, int height)
{
if (width == -1) {
width = frameRenderWidth();
height = renderHeight();
} else if (width % 2 == 1) width++;
if (!path.isEmpty()) {
Mlt::Producer *producer = new Mlt::Producer(*m_qmlView->profile(), path.toUtf8().constData());
if (producer) {
if (producer->is_valid()) {
QImage img = KThumb::getFrame(producer, frame_position, width, height);
delete producer;
return img;
}
else delete producer;
}
}
if (!m_mltProducer || !path.isEmpty()) {
QImage pix(width, height, QImage::Format_RGB32);
pix.fill(Qt::black);
return pix;
}
Mlt::Frame *frame = NULL;
if (KdenliveSettings::gpu_accel()) {
QString service = m_mltProducer->get("mlt_service");
//TODO: create duplicate prod from xml data
Mlt::Producer *tmpProd = new Mlt::Producer(*m_qmlView->profile(), service.toUtf8().constData(), path.toUtf8().constData());
Mlt::Filter scaler(*m_qmlView->profile(), "swscale");
Mlt::Filter converter(*m_qmlView->profile(), "avcolor_space");
tmpProd->attach(scaler);
tmpProd->attach(converter);
tmpProd->seek(m_mltProducer->position());
frame = tmpProd->get_frame();
delete tmpProd;
}
else {
frame = m_mltProducer->get_frame();
}
QImage img = KThumb::getFrame(frame, width, height);
delete frame;
return img;
}
int Render::getLength()
{
if (m_mltProducer) {
// //qDebug()<<"////// LENGTH: "<get_producer());
return mlt_producer_get_playtime(m_mltProducer->get_producer());
}
return 0;
}
bool Render::isValid(const QUrl &url)
{
Mlt::Producer producer(*m_qmlView->profile(), url.toLocalFile().toUtf8().constData());
if (producer.is_blank())
return false;
return true;
}
double Render::dar() const
{
return m_qmlView->profile()->dar();
}
double Render::sar() const
{
return m_qmlView->profile()->sar();
}
#if 0
/** Create the producer from the MLT XML QDomDocument */
void Render::initSceneList()
{
//qDebug() << "-------- INIT SCENE LIST ------_";
QDomDocument doc;
QDomElement mlt = doc.createElement("mlt");
doc.appendChild(mlt);
QDomElement prod = doc.createElement("producer");
prod.setAttribute("resource", "colour");
prod.setAttribute("colour", "red");
prod.setAttribute("id", "black");
prod.setAttribute("in", "0");
prod.setAttribute("out", "0");
QDomElement tractor = doc.createElement("tractor");
QDomElement multitrack = doc.createElement("multitrack");
QDomElement playlist1 = doc.createElement("playlist");
playlist1.appendChild(prod);
multitrack.appendChild(playlist1);
QDomElement playlist2 = doc.createElement("playlist");
multitrack.appendChild(playlist2);
QDomElement playlist3 = doc.createElement("playlist");
multitrack.appendChild(playlist3);
QDomElement playlist4 = doc.createElement("playlist");
multitrack.appendChild(playlist4);
QDomElement playlist5 = doc.createElement("playlist");
multitrack.appendChild(playlist5);
tractor.appendChild(multitrack);
mlt.appendChild(tractor);
// //qDebug()<");*/
setSceneList(doc, 0);
}
#endif
void Render::loadUrl(const QString &url)
{
Mlt::Producer *producer = new Mlt::Producer(*m_qmlView->profile(), url.toUtf8().constData());
setProducer(producer, 0, true);
}
bool Render::updateProducer(Mlt::Producer *producer)
{
if (m_mltProducer) {
if (strcmp(m_mltProducer->get("resource"), "") == 0) {
// We need to make some cleanup
Mlt::Tractor trac(*m_mltProducer);
for (int i = 0; i < trac.count(); i++) {
trac.set_track(*m_blackClip, i);
}
}
delete m_mltProducer;
m_mltProducer = NULL;
}
if (m_mltConsumer) {
if (!m_mltConsumer->is_stopped()) {
m_mltConsumer->stop();
}
}
if (!producer || !producer->is_valid()) {
return false;
}
m_fps = producer->get_fps();
m_mltProducer = producer;
if (m_qmlView) {
m_qmlView->setProducer(producer);
m_mltConsumer = m_qmlView->consumer();
}
return true;
}
bool Render::setProducer(Mlt::Producer *producer, int position, bool isActive)
{
m_refreshTimer.stop();
requestedSeekPosition = SEEK_INACTIVE;
QMutexLocker locker(&m_mutex);
QString currentId;
int consumerPosition = 0;
if (!producer && m_mltProducer && m_mltProducer->parent().get("id") == QLatin1String("black")) {
// Black clip already displayed no need to refresh
return true;
}
if (m_mltProducer) {
currentId = m_mltProducer->get("id");
m_mltProducer->set_speed(0);
if (QString(m_mltProducer->get("resource")) == QLatin1String("")) {
// We need to make some cleanup
Mlt::Tractor trac(*m_mltProducer);
for (int i = 0; i < trac.count(); i++) {
trac.set_track(*m_blackClip, i);
}
}
delete m_mltProducer;
m_mltProducer = NULL;
}
if (m_mltConsumer) {
if (!m_mltConsumer->is_stopped()) {
isActive = true;
m_mltConsumer->stop();
}
consumerPosition = m_mltConsumer->position();
}
blockSignals(true);
if (!producer || !producer->is_valid()) {
producer = m_blackClip->cut(0,1);
}
emit stopped();
if (position == -1 && producer->get("id") == currentId) position = consumerPosition;
if (position != -1) producer->seek(position);
m_fps = producer->get_fps();
blockSignals(false);
m_mltProducer = producer;
m_mltProducer->set_speed(0);
if (m_qmlView) {
m_qmlView->setProducer(producer);
m_mltConsumer = m_qmlView->consumer();
//m_mltConsumer->set("refresh", 1);
}
//m_mltConsumer->connect(*producer);
if (isActive) {
startConsumer();
}
emit durationChanged(m_mltProducer->get_playtime() - 1, m_mltProducer->get_in());
position = m_mltProducer->position();
emit rendererPosition(position);
return true;
}
void Render::startConsumer() {
if (m_mltConsumer->is_stopped() && m_mltConsumer->start() == -1) {
// ARGH CONSUMER BROKEN!!!!
KMessageBox::error(qApp->activeWindow(), i18n("Could not create the video preview window.\nThere is something wrong with your Kdenlive install or your driver settings, please fix it."));
if (m_showFrameEvent) delete m_showFrameEvent;
m_showFrameEvent = NULL;
if (m_pauseEvent) delete m_pauseEvent;
m_pauseEvent = NULL;
delete m_mltConsumer;
m_mltConsumer = NULL;
return;
}
m_isRefreshing = true;
m_mltConsumer->set("refresh", 1);
m_isActive = true;
}
int Render::setSceneList(const QDomDocument &list, int position)
{
return setSceneList(list.toString(), position);
}
int Render::setSceneList(QString playlist, int position)
{
requestedSeekPosition = SEEK_INACTIVE;
m_refreshTimer.stop();
QMutexLocker locker(&m_mutex);
//if (m_winid == -1) return -1;
int error = 0;
//qDebug() << "////// RENDER, SET SCENE LIST:\n" << playlist <<"\n..........:::.";
// Remove previous profile info
QDomDocument doc;
doc.setContent(playlist);
QDomElement profile = doc.documentElement().firstChildElement(QStringLiteral("profile"));
doc.documentElement().removeChild(profile);
playlist = doc.toString();
if (m_mltConsumer) {
if (!m_mltConsumer->is_stopped()) {
m_mltConsumer->stop();
}
} else {
qWarning() << "/////// ERROR, TRYING TO USE NULL MLT CONSUMER";
error = -1;
}
if (m_mltProducer) {
m_mltProducer->set_speed(0);
qDeleteAll(m_slowmotionProducers);
m_slowmotionProducers.clear();
delete m_mltProducer;
m_mltProducer = NULL;
emit stopped();
}
m_binController->destroyBin();
blockSignals(true);
m_locale = QLocale();
m_locale.setNumberOptions(QLocale::OmitGroupSeparator);
m_mltProducer = new Mlt::Producer(*m_qmlView->profile(), "xml-string", playlist.toUtf8().constData());
//m_mltProducer = new Mlt::Producer(*m_qmlView->profile(), "xml-nogl-string", playlist.toUtf8().constData());
if (!m_mltProducer || !m_mltProducer->is_valid()) {
qDebug() << " WARNING - - - - -INVALID PLAYLIST: " << playlist.toUtf8().constData();
m_mltProducer = m_blackClip->cut(0, 1);
error = -1;
}
m_mltProducer->set("eof", "pause");
checkMaxThreads();
m_mltProducer->optimise();
m_fps = m_mltProducer->get_fps();
if (position != 0) {
// Seek to correct place after opening project.
m_mltProducer->seek(position);
}
// init MLT's document root, useful to find full urls
m_binController->setDocumentRoot(doc.documentElement().attribute(QStringLiteral("root")));
// Fill Bin's playlist
Mlt::Service service(m_mltProducer->parent().get_service());
if (service.type() != tractor_type) {
qWarning() << "// TRACTOR PROBLEM";
}
blockSignals(false);
Mlt::Tractor tractor(service);
Mlt::Properties retainList((mlt_properties) tractor.get_data("xml_retain"));
if (retainList.is_valid() && retainList.get_data(m_binController->binPlaylistId().toUtf8().constData())) {
Mlt::Playlist playlist((mlt_playlist) retainList.get_data(m_binController->binPlaylistId().toUtf8().constData()));
if (playlist.is_valid() && playlist.type() == playlist_type) {
// Load bin clips
m_binController->initializeBin(playlist);
}
}
// No Playlist found, create new one
if (m_qmlView) {
m_binController->createIfNeeded(m_qmlView->profile());
QString retain = QStringLiteral("xml_retain %1").arg(m_binController->binPlaylistId());
tractor.set(retain.toUtf8().constData(), m_binController->service(), 0);
//if (!m_binController->hasClip("black")) m_binController->addClipToBin("black", *m_blackClip);
m_qmlView->setProducer(m_mltProducer);
m_mltConsumer = m_qmlView->consumer();
}
//qDebug() << "// NEW SCENE LIST DURATION SET TO: " << m_mltProducer->get_playtime();
//m_mltConsumer->connect(*m_mltProducer);
m_mltProducer->set_speed(0);
fillSlowMotionProducers();
emit durationChanged(m_mltProducer->get_playtime() - 1);
// Fill bin
QStringList ids = m_binController->getClipIds();
foreach(const QString &id, ids) {
if (id == QLatin1String("black")) {
//TODO: delegate handling of black clip to bincontroller
//delete m_blackClip;
//m_blackClip = &original->parent();
}
else {
// pass basic info, the others (folder, etc) will be taken from the producer itself
requestClipInfo info;
info.imageHeight = 0;
info.clipId = id;
info.replaceProducer = true;
emit gotFileProperties(info, m_binController->getController(id));
}
//delete original;
}
////qDebug()<<"// SETSCN LST, POS: "<parent().get_service());
if (service.type() != tractor_type) {
qWarning() << "// TRACTOR PROBLEM"<parent().get("mlt_service");
return;
}
Mlt::Tractor tractor(service);
int mltMaxThreads = mlt_service_cache_get_size(service.get_service(), "producer_avformat");
int requestedThreads = tractor.count() + m_qmlView->realTime() + 2;
if (requestedThreads > mltMaxThreads) {
mlt_service_cache_set_size(service.get_service(), "producer_avformat", requestedThreads);
//qDebug()<<"// MLT threads updated to: "<profile(), "xml:kdenlive_playlist");
//qDebug()<<" ++ + READY TO SAVE: "<profile()->width()<<" / "<profile()->description();
if (!xmlConsumer.is_valid()) return QString();
m_mltProducer->optimise();
xmlConsumer.set("terminate_on_pause", 1);
xmlConsumer.set("store", "kdenlive");
Mlt::Producer prod(m_mltProducer->get_producer());
if (!prod.is_valid()) return QString();
xmlConsumer.connect(prod);
xmlConsumer.run();
playlist = QString::fromUtf8(xmlConsumer.get("kdenlive_playlist"));
return playlist;
}
bool Render::saveSceneList(QString path, QDomElement kdenliveData)
{
QFile file(path);
QDomDocument doc;
doc.setContent(sceneList(), false);
if (doc.isNull()) return false;
QDomElement root = doc.documentElement();
if (!kdenliveData.isNull() && !root.isNull()) {
// add Kdenlive specific tags
root.appendChild(doc.importNode(kdenliveData, true));
}
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
qWarning() << "////// ERROR writing to file: " << path;
return false;
}
file.write(doc.toString().toUtf8());
if (file.error() != QFile::NoError) {
file.close();
return false;
}
file.close();
return true;
}
void Render::saveZone(QPoint zone)
{
QString clipFolder = KRecentDirs::dir(QStringLiteral(":KdenliveClipFolder"));
if (clipFolder.isEmpty()) {
clipFolder = QDir::homePath();
}
QString url = QFileDialog::getSaveFileName(qApp->activeWindow(), i18n("Save Zone"), clipFolder, i18n("MLT playlist (*.mlt)"));
Mlt::Consumer xmlConsumer(*m_qmlView->profile(), ("xml:" + url).toUtf8().constData());
xmlConsumer.set("terminate_on_pause", 1);
m_mltProducer->optimise();
qDebug()<<" - - -- - SAVE ZONE SEVICE: "<get("mlt_type");
if (QString(m_mltProducer->get("mlt_type")) != QLatin1String("producer")) {
// TODO: broken
QString scene = sceneList();
Mlt::Producer duplicate(*m_mltProducer->profile(), "xml-string", scene.toUtf8().constData());
duplicate.set_in_and_out(zone.x(), zone.y());
qDebug()<<"/// CUT: "<get_producer());
Mlt::Producer *prod2 = prod.cut(zone.x(), zone.y());
Mlt::Playlist list(*m_mltProducer->profile());
list.insert_at(0, *prod2, 0);
//list.set("title", desc.toUtf8().constData());
xmlConsumer.connect(list);
xmlConsumer.run();
delete prod2;
}
}
double Render::fps() const
{
return m_fps;
}
int Render::volume() const
{
if (!m_mltConsumer || !m_mltProducer) return -1;
if (m_mltConsumer->get("mlt_service") == QStringLiteral("multi")) {
return ((int) 100 * m_mltConsumer->get_double("0.volume"));
}
return ((int) 100 * m_mltConsumer->get_double("volume"));
}
void Render::start()
{
m_refreshTimer.stop();
QMutexLocker locker(&m_mutex);
/*if (m_winid == -1) {
//qDebug() << "----- BROKEN MONITOR: " << m_name << ", RESTART";
return;
}*/
if (!m_mltConsumer) {
//qDebug()<<" / - - - STARTED BEFORE CONSUMER!!!";
return;
}
if (m_mltConsumer->is_stopped()) {
if (m_mltConsumer->start() == -1) {
//KMessageBox::error(qApp->activeWindow(), i18n("Could not create the video preview window.\nThere is something wrong with your Kdenlive install or your driver settings, please fix it."));
qWarning() << "/ / / / CANNOT START MONITOR";
} else {
m_mltConsumer->purge();
m_isRefreshing = true;
m_mltConsumer->set("refresh", 1);
}
}
}
void Render::stop()
{
requestedSeekPosition = SEEK_INACTIVE;
m_refreshTimer.stop();
QMutexLocker locker(&m_mutex);
m_isActive = false;
if (m_mltProducer) {
if (m_isZoneMode) resetZoneMode();
m_mltProducer->set_speed(0.0);
}
if (m_mltConsumer) {
m_mltConsumer->purge();
if (!m_mltConsumer->is_stopped()) {
m_mltConsumer->stop();
}
}
m_isRefreshing = false;
}
void Render::stop(const GenTime & startTime)
{
requestedSeekPosition = SEEK_INACTIVE;
m_refreshTimer.stop();
QMutexLocker locker(&m_mutex);
m_isActive = false;
if (m_mltProducer) {
if (m_isZoneMode) resetZoneMode();
m_mltProducer->set_speed(0.0);
m_mltProducer->seek((int) startTime.frames(m_fps));
}
if (m_mltConsumer) {
m_mltConsumer->purge();
}
m_isRefreshing = false;
}
/*void Render::pause()
{
requestedSeekPosition = SEEK_INACTIVE;
if (!m_mltProducer || !m_mltConsumer || !m_isActive)
return;
m_mltProducer->set_speed(0.0);
//if (!m_mltConsumer->is_stopped()) m_mltConsumer->stop();
//m_mltProducer->seek(m_mltConsumer->position());
}*/
void Render::setActiveMonitor()
{
if (!m_isActive) emit activateMonitor(m_name);
}
void Render::switchPlay(bool play)
{
QMutexLocker locker(&m_mutex);
requestedSeekPosition = SEEK_INACTIVE;
if (!m_mltProducer || !m_mltConsumer || !m_isActive)
return;
if (m_isZoneMode) resetZoneMode();
if (play) {
if (m_name == Kdenlive::ClipMonitor && m_mltConsumer->position() == m_mltProducer->get_out()) m_mltProducer->seek(0);
if (m_mltConsumer->get_int("real_time") != m_qmlView->realTime()) {
m_mltConsumer->set("real_time", m_qmlView->realTime());
m_mltConsumer->set("buffer", 25);
m_mltConsumer->set("prefill", 1);
// Changes to real_time require a consumer restart if running.
if (!m_mltConsumer->is_stopped()) {
m_mltConsumer->stop();
}
}
m_mltConsumer->start();
m_isRefreshing = true;
m_mltConsumer->set("refresh", 1);
m_mltProducer->set_speed(1.0);
} else {
m_mltConsumer->purge();
m_mltProducer->set_speed(0.0);
m_mltConsumer->set("buffer", 0);
m_mltConsumer->set("prefill", 0);
m_mltConsumer->set("real_time", -1);
m_mltProducer->seek(m_mltConsumer->position() + 1);
m_mltConsumer->start();
}
}
void Render::play(double speed)
{
requestedSeekPosition = SEEK_INACTIVE;
if (!m_mltProducer || !m_isActive) return;
double current_speed = m_mltProducer->get_speed();
if (current_speed == speed) return;
if (m_isZoneMode) resetZoneMode();
m_mltProducer->set_speed(speed);
if (m_mltConsumer->is_stopped() && speed != 0.0) {
m_mltConsumer->start();
}
if (current_speed == 0.0 && speed != 0.0) {
m_isRefreshing = true;
m_mltConsumer->set("refresh", 1);
}
}
void Render::play(const GenTime & startTime)
{
requestedSeekPosition = SEEK_INACTIVE;
if (!m_mltProducer || !m_mltConsumer || !m_isActive)
return;
m_mltProducer->seek((int)(startTime.frames(m_fps)));
m_mltProducer->set_speed(1.0);
m_isRefreshing = true;
m_mltConsumer->set("refresh", 1);
}
void Render::loopZone(const GenTime & startTime, const GenTime & stopTime)
{
requestedSeekPosition = SEEK_INACTIVE;
if (!m_mltProducer || !m_mltConsumer || !m_isActive)
return;
//m_mltProducer->set("eof", "loop");
m_isLoopMode = true;
m_loopStart = startTime;
playZone(startTime, stopTime);
}
bool Render::playZone(const GenTime & startTime, const GenTime & stopTime)
{
requestedSeekPosition = SEEK_INACTIVE;
if (!m_mltProducer || !m_mltConsumer || !m_isActive)
return false;
m_mltProducer->seek((int)(startTime.frames(m_fps)));
m_mltProducer->set_speed(0);
m_mltConsumer->purge();
m_mltProducer->set("out", (int)(stopTime.frames(m_fps)));
m_mltProducer->set_speed(1.0);
if (m_mltConsumer->is_stopped()) m_mltConsumer->start();
m_isRefreshing = true;
m_mltConsumer->set("refresh", 1);
m_isZoneMode = true;
return true;
}
void Render::resetZoneMode()
{
if (!m_isZoneMode && !m_isLoopMode) return;
m_mltProducer->set("out", m_mltProducer->get_length());
m_isZoneMode = false;
m_isLoopMode = false;
}
void Render::seekToFrame(int pos)
{
if (!m_mltProducer || !m_isActive)
return;
pos = qBound(0, pos - m_mltProducer->get_in(), m_mltProducer->get_length());
seek(pos);
}
void Render::seekToFrameDiff(int diff)
{
if (!m_mltProducer || !m_isActive)
return;
if (requestedSeekPosition == SEEK_INACTIVE) {
seek(m_mltConsumer->position() + diff);
}
else {
seek(requestedSeekPosition + diff);
}
}
void Render::doRefresh()
{
if (m_mltProducer && (playSpeed() == 0) && m_isActive) {
if (m_isRefreshing) m_refreshTimer.start();
else refresh();
}
}
void Render::refresh()
{
m_refreshTimer.stop();
if (!m_mltProducer || !m_isActive)
return;
QMutexLocker locker(&m_mutex);
if (m_mltConsumer) {
m_isRefreshing = true;
if (m_mltConsumer->is_stopped()) m_mltConsumer->start();
m_mltConsumer->purge();
m_isRefreshing = true;
m_mltConsumer->set("refresh", 1);
}
}
void Render::setDropFrames(bool drop)
{
QMutexLocker locker(&m_mutex);
if (m_mltConsumer) {
int dropFrames = m_qmlView->realTime();
if (drop == false) dropFrames = -dropFrames;
//m_mltConsumer->stop();
m_mltConsumer->set("real_time", dropFrames);
if (m_mltConsumer->start() == -1) {
qWarning() << "ERROR, Cannot start monitor";
}
}
}
void Render::setConsumerProperty(const QString &name, const QString &value)
{
QMutexLocker locker(&m_mutex);
if (m_mltConsumer) {
//m_mltConsumer->stop();
m_mltConsumer->set(name.toUtf8().constData(), value.toUtf8().constData());
if (m_isActive && m_mltConsumer->start() == -1) {
qWarning() << "ERROR, Cannot start monitor";
}
}
}
bool Render::isPlaying() const
{
if (!m_mltConsumer || m_mltConsumer->is_stopped()) return false;
return playSpeed() != 0;
}
double Render::playSpeed() const
{
if (m_mltProducer) return m_mltProducer->get_speed();
return 0.0;
}
GenTime Render::seekPosition() const
{
if (m_mltConsumer) return GenTime((int) m_mltConsumer->position(), m_fps);
else return GenTime();
}
int Render::seekFramePosition() const
{
if (m_mltConsumer) return (int) m_mltConsumer->position();
return 0;
}
void Render::emitFrameUpdated(Mlt::Frame& frame)
{
Q_UNUSED(frame)
return;
/*TODO: fix movit crash
mlt_image_format format = mlt_image_rgb24;
int width = 0;
int height = 0;
//frame.set("rescale.interp", "bilinear");
//frame.set("deinterlace_method", "onefield");
//frame.set("top_field_first", -1);
const uchar* image = frame.get_image(format, width, height);
QImage qimage(width, height, QImage::Format_RGB888); //Format_ARGB32_Premultiplied);
memcpy(qimage.scanLine(0), image, width * height * 3);
emit frameUpdated(qimage);
*/
}
int Render::getCurrentSeekPosition() const
{
if (requestedSeekPosition != SEEK_INACTIVE) return requestedSeekPosition;
return (int) m_mltConsumer->position();
}
bool Render::checkFrameNumber(int pos)
{
if (pos == requestedSeekPosition) {
requestedSeekPosition = SEEK_INACTIVE;
}
if (requestedSeekPosition != SEEK_INACTIVE) {
double speed = m_mltProducer->get_speed();
m_mltProducer->set_speed(0);
m_mltProducer->seek(requestedSeekPosition);
if (speed == 0) {
m_mltConsumer->set("refresh", 1);
}
else m_mltProducer->set_speed(speed);
} else {
m_isRefreshing = false;
if (m_isZoneMode) {
if (pos >= m_mltProducer->get_int("out") - 1) {
if (m_isLoopMode) {
m_mltConsumer->purge();
m_mltProducer->seek((int)(m_loopStart.frames(m_fps)));
m_mltProducer->set_speed(1.0);
m_mltConsumer->set("refresh", 1);
} else {
if (m_mltProducer->get_speed() == 0) return false;
}
}
}
}
return true;
}
void Render::emitFrameUpdated(QImage img)
{
emit frameUpdated(img);
}
void Render::slotCheckSeeking()
{
if (requestedSeekPosition != SEEK_INACTIVE) {
m_mltProducer->seek(requestedSeekPosition);
requestedSeekPosition = SEEK_INACTIVE;
}
}
void Render::showAudio(Mlt::Frame& frame)
{
if (!frame.is_valid() || frame.get_int("test_audio") != 0) {
return;
}
mlt_audio_format audio_format = mlt_audio_s16;
//FIXME: should not be hardcoded..
int freq = 48000;
int num_channels = 2;
int samples = 0;
qint16* data = (qint16*)frame.get_audio(audio_format, freq, num_channels, samples);
if (!data) {
return;
}
// Data format: [ c00 c10 c01 c11 c02 c12 c03 c13 ... c0{samples-1} c1{samples-1} for 2 channels.
// So the vector is of size samples*channels.
audioShortVector sampleVector(samples*num_channels);
memcpy(sampleVector.data(), data, samples*num_channels*sizeof(qint16));
if (samples > 0) {
emit audioSamplesSignal(sampleVector, freq, num_channels, samples);
}
}
/*
* MLT playlist direct manipulation.
*/
void Render::mltCheckLength(Mlt::Tractor *tractor)
{
int trackNb = tractor->count();
int duration = 0;
if (m_isZoneMode) resetZoneMode();
if (trackNb == 1) {
QScopedPointer trackProducer(tractor->track(0));
duration = trackProducer->get_playtime();
m_mltProducer->set("out", duration);
emit durationChanged(duration);
return;
}
while (trackNb > 1) {
QScopedPointer trackProducer(tractor->track(trackNb - 1));
int trackDuration = trackProducer->get_playtime();
if (trackDuration > duration) duration = trackDuration;
trackNb--;
}
QScopedPointer blackTrackProducer(tractor->track(0));
if (blackTrackProducer->get_playtime() - 1 != duration) {
Mlt::Playlist blackTrackPlaylist((mlt_playlist) blackTrackProducer->get_service());
QScopedPointer blackclip(blackTrackPlaylist.get_clip(0));
if (!blackclip || blackclip->is_blank() || blackTrackPlaylist.count() != 1) {
blackTrackPlaylist.clear();
m_blackClip->set("length", duration + 1);
m_blackClip->set("out", duration);
Mlt::Producer *black2 = m_blackClip->cut(0, duration);
blackTrackPlaylist.insert_at(0, black2, 1);
delete black2;
} else {
if (duration > blackclip->parent().get_length()) {
blackclip->parent().set("length", duration + 1);
blackclip->parent().set("out", duration);
blackclip->set("length", duration + 1);
}
blackTrackPlaylist.resize_clip(0, 0, duration);
}
if (m_mltConsumer->position() > duration) {
m_mltConsumer->purge();
m_mltProducer->seek(duration);
}
m_mltProducer->set("out", duration);
emit durationChanged(duration);
}
}
Mlt::Producer *Render::getTrackProducer(const QString &id, int track, bool, bool)
{
Mlt::Service service(m_mltProducer->parent().get_service());
if (service.type() != tractor_type) {
qWarning() << "// TRACTOR PROBLEM";
return NULL;
}
Mlt::Tractor tractor(service);
// WARNING: Kdenlive's track numbering is 0 for top track, while in MLT 0 is black track and 1 is the bottom track so we MUST reverse track number
// TODO: memleak
Mlt::Producer destTrackProducer(tractor.track(tractor.count() - track - 1));
Mlt::Playlist destTrackPlaylist((mlt_playlist) destTrackProducer.get_service());
return getProducerForTrack(destTrackPlaylist, id);
}
Mlt::Producer *Render::getProducerForTrack(Mlt::Playlist &trackPlaylist, const QString &clipId)
{
//TODO: find a better way to check if a producer is already inserted in a track ?
QString trackName = trackPlaylist.get("id");
QString clipIdWithTrack = clipId + "_" + trackName;
Mlt::Producer *prod = NULL;
for (int i = 0; i < trackPlaylist.count(); i++) {
if (trackPlaylist.is_blank(i)) continue;
QScopedPointer p(trackPlaylist.get_clip(i));
QString id = p->parent().get("id");
if (id == clipIdWithTrack) {
// This producer already exists in the track, reuse it
prod = &p->parent();
break;
}
}
if (prod == NULL) prod = m_binController->getBinProducer(clipId);
return prod;
}
Mlt::Tractor *Render::lockService()
{
// we are going to replace some clips, purge consumer
if (!m_mltProducer) return NULL;
QMutexLocker locker(&m_mutex);
if (m_mltConsumer) {
m_mltConsumer->purge();
}
Mlt::Service service(m_mltProducer->parent().get_service());
if (service.type() != tractor_type) {
return NULL;
}
service.lock();
return new Mlt::Tractor(service);
}
void Render::unlockService(Mlt::Tractor *tractor)
{
if (tractor) {
delete tractor;
}
if (!m_mltProducer) return;
Mlt::Service service(m_mltProducer->parent().get_service());
if (service.type() != tractor_type) {
qWarning() << "// TRACTOR PROBLEM";
return;
}
service.unlock();
}
void Render::mltInsertSpace(QMap trackClipStartList, QMap trackTransitionStartList, int track, const GenTime &duration, const GenTime &timeOffset)
{
if (!m_mltProducer) {
//qDebug() << "PLAYLIST NOT INITIALISED //////";
return;
}
Mlt::Producer parentProd(m_mltProducer->parent());
if (parentProd.get_producer() == NULL) {
//qDebug() << "PLAYLIST BROKEN, CANNOT INSERT CLIP //////";
return;
}
////qDebug()<<"// CLP STRT LST: "< 0) {
trackPlaylist.insert_blank(clipIndex, diff - 1);
} else {
if (!trackPlaylist.is_blank(clipIndex)) clipIndex --;
if (!trackPlaylist.is_blank(clipIndex)) {
//qDebug() << "//// ERROR TRYING TO DELETE SPACE FROM " << insertPos;
}
int position = trackPlaylist.clip_start(clipIndex);
int blankDuration = trackPlaylist.clip_length(clipIndex);
if (blankDuration + diff == 0) {
trackPlaylist.remove(clipIndex);
} else trackPlaylist.remove_region(position, -diff);
}
trackPlaylist.consolidate_blanks(0);
}
// now move transitions
mlt_service serv = m_mltProducer->parent().get_service();
mlt_service nextservice = mlt_service_get_producer(serv);
mlt_properties properties = MLT_SERVICE_PROPERTIES(nextservice);
QString mlt_type = mlt_properties_get(properties, "mlt_type");
QString resource = mlt_properties_get(properties, "mlt_service");
while (mlt_type == QLatin1String("transition")) {
mlt_transition tr = (mlt_transition) nextservice;
int currentTrack = mlt_transition_get_b_track(tr);
int currentIn = (int) mlt_transition_get_in(tr);
int currentOut = (int) mlt_transition_get_out(tr);
insertPos = trackTransitionStartList.value(track);
if (insertPos != -1) {
insertPos += offset;
if (track == currentTrack && currentOut > insertPos && resource != QLatin1String("mix")) {
mlt_transition_set_in_and_out(tr, currentIn + diff, currentOut + diff);
}
}
nextservice = mlt_service_producer(nextservice);
if (nextservice == NULL) break;
properties = MLT_SERVICE_PROPERTIES(nextservice);
mlt_type = mlt_properties_get(properties, "mlt_type");
resource = mlt_properties_get(properties, "mlt_service");
}
} else {
for (int trackNb = tractor.count() - 1; trackNb >= 1; --trackNb) {
Mlt::Producer trackProducer(tractor.track(trackNb));
Mlt::Playlist trackPlaylist((mlt_playlist) trackProducer.get_service());
//int clipNb = trackPlaylist.count();
insertPos = trackClipStartList.value(trackNb);
if (insertPos != -1) {
insertPos += offset;
/* //qDebug()<<"-------------\nTRACK "< 0) {
trackPlaylist.insert_blank(clipIndex, diff - 1);
} else {
if (!trackPlaylist.is_blank(clipIndex)) {
clipIndex --;
}
if (!trackPlaylist.is_blank(clipIndex)) {
//qDebug() << "//// ERROR TRYING TO DELETE SPACE FROM " << insertPos;
}
int position = trackPlaylist.clip_start(clipIndex);
int blankDuration = trackPlaylist.clip_length(clipIndex);
if (diff + blankDuration == 0) {
trackPlaylist.remove(clipIndex);
} else trackPlaylist.remove_region(position, - diff);
}
trackPlaylist.consolidate_blanks(0);
}
}
// now move transitions
mlt_service serv = m_mltProducer->parent().get_service();
mlt_service nextservice = mlt_service_get_producer(serv);
mlt_properties properties = MLT_SERVICE_PROPERTIES(nextservice);
QString mlt_type = mlt_properties_get(properties, "mlt_type");
QString resource = mlt_properties_get(properties, "mlt_service");
while (mlt_type == QLatin1String("transition")) {
mlt_transition tr = (mlt_transition) nextservice;
int currentIn = (int) mlt_transition_get_in(tr);
int currentOut = (int) mlt_transition_get_out(tr);
int currentTrack = mlt_transition_get_b_track(tr);
insertPos = trackTransitionStartList.value(currentTrack);
if (insertPos != -1) {
insertPos += offset;
if (currentOut > insertPos && resource != QLatin1String("mix")) {
mlt_transition_set_in_and_out(tr, currentIn + diff, currentOut + diff);
}
}
nextservice = mlt_service_producer(nextservice);
if (nextservice == NULL) break;
properties = MLT_SERVICE_PROPERTIES(nextservice);
mlt_type = mlt_properties_get(properties, "mlt_type");
resource = mlt_properties_get(properties, "mlt_service");
}
}
service.unlock();
mltCheckLength(&tractor);
m_isRefreshing = true;
m_mltConsumer->set("refresh", 1);
}
-bool Render::mltEnableEffects(int track, const GenTime &position, const QList &effectIndexes, bool disable)
-{
- if (position < GenTime()) {
- return mltEnableTrackEffects(track, effectIndexes, disable);
- }
- // find filter
- Mlt::Service service(m_mltProducer->parent().get_service());
- Mlt::Tractor tractor(service);
- //TODO: memleak
- Mlt::Producer trackProducer(tractor.track(track));
- Mlt::Playlist trackPlaylist((mlt_playlist) trackProducer.get_service());
-
- int clipIndex = trackPlaylist.get_clip_index_at((int) position.frames(m_fps));
- QScopedPointer clip(trackPlaylist.get_clip(clipIndex));
- if (!clip) {
- //qDebug() << "WARINIG, CANNOT FIND CLIP ON track: " << track << ", AT POS: " << position.frames(m_fps);
- return false;
- }
-
- int duration = clip->get_playtime();
- bool doRefresh = true;
- // Check if clip is visible in monitor
- int diff = trackPlaylist.clip_start(clipIndex) + duration - m_mltProducer->position();
- if (diff < 0 || diff > duration)
- doRefresh = false;
- int ct = 0;
-
- Mlt::Filter *filter = clip->filter(ct);
- service.lock();
- while (filter) {
- if (effectIndexes.contains(filter->get_int("kdenlive_ix"))) {
- filter->set("disable", (int) disable);
- }
- ct++;
- filter = clip->filter(ct);
- }
- service.unlock();
-
- if (doRefresh) refresh();
- return true;
-}
-
-bool Render::mltEnableTrackEffects(int track, const QList &effectIndexes, bool disable)
-{
- Mlt::Service service(m_mltProducer->parent().get_service());
- Mlt::Tractor tractor(service);
- //TODO: memleak
- Mlt::Producer trackProducer(tractor.track(track));
- Mlt::Playlist trackPlaylist((mlt_playlist) trackProducer.get_service());
- Mlt::Service clipService(trackPlaylist.get_service());
- int ct = 0;
-
- Mlt::Filter *filter = clipService.filter(ct);
- service.lock();
- while (filter) {
- if (effectIndexes.contains(filter->get_int("kdenlive_ix"))) {
- filter->set("disable", (int) disable);
- }
- ct++;
- filter = clipService.filter(ct);
- }
- service.unlock();
-
- refresh();
- return true;
-}
-
-void Render::mltUpdateEffectPosition(int track, const GenTime &position, int oldPos, int newPos)
-{
- Mlt::Service service(m_mltProducer->parent().get_service());
- Mlt::Tractor tractor(service);
- //TODO: memleak
- Mlt::Producer trackProducer(tractor.track(track));
- Mlt::Playlist trackPlaylist((mlt_playlist) trackProducer.get_service());
-
- int clipIndex = trackPlaylist.get_clip_index_at((int) position.frames(m_fps));
- QScopedPointer clip(trackPlaylist.get_clip(clipIndex));
- if (!clip) {
- //qDebug() << "WARINIG, CANNOT FIND CLIP ON track: " << track << ", AT POS: " << position.frames(m_fps);
- return;
- }
-
- Mlt::Service clipService(clip->get_service());
- int duration = clip->get_playtime();
- bool doRefresh = true;
- // Check if clip is visible in monitor
- int diff = trackPlaylist.clip_start(clipIndex) + duration - m_mltProducer->position();
- if (diff < 0 || diff > duration) doRefresh = false;
-
- int ct = 0;
- Mlt::Filter *filter = clipService.filter(ct);
- while (filter) {
- int pos = filter->get_int("kdenlive_ix");
- if (pos == oldPos) {
- filter->set("kdenlive_ix", newPos);
- } else ct++;
- filter = clipService.filter(ct);
- }
- if (doRefresh) refresh();
-}
-
-void Render::mltMoveEffect(int track, const GenTime &position, int oldPos, int newPos)
-{
- if (position < GenTime()) {
- mltMoveTrackEffect(track, oldPos, newPos);
- return;
- }
- Mlt::Service service(m_mltProducer->parent().get_service());
- Mlt::Tractor tractor(service);
- //TODO: memleak
- Mlt::Producer trackProducer(tractor.track(track));
- Mlt::Playlist trackPlaylist((mlt_playlist) trackProducer.get_service());
-
- int clipIndex = trackPlaylist.get_clip_index_at((int) position.frames(m_fps));
- QScopedPointer clip(trackPlaylist.get_clip(clipIndex));
- if (!clip) {
- //qDebug() << "WARINIG, CANNOT FIND CLIP ON track: " << track << ", AT POS: " << position.frames(m_fps);
- return;
- }
-
- Mlt::Service clipService(clip->get_service());
- int duration = clip->get_playtime();
- bool doRefresh = true;
- // Check if clip is visible in monitor
- int diff = trackPlaylist.clip_start(clipIndex) + duration - m_mltProducer->position();
- if (diff < 0 || diff > duration) doRefresh = false;
-
- int ct = 0;
- QList filtersList;
- Mlt::Filter *filter = clipService.filter(ct);
- if (newPos > oldPos) {
- bool found = false;
- while (filter) {
- if (!found && filter->get_int("kdenlive_ix") == oldPos) {
- filter->set("kdenlive_ix", newPos);
- filtersList.append(filter);
- clipService.detach(*filter);
- filter = clipService.filter(ct);
- while (filter && filter->get_int("kdenlive_ix") <= newPos) {
- filter->set("kdenlive_ix", filter->get_int("kdenlive_ix") - 1);
- ct++;
- filter = clipService.filter(ct);
- }
- found = true;
- }
- if (filter && filter->get_int("kdenlive_ix") > newPos) {
- filtersList.append(filter);
- clipService.detach(*filter);
- } else ct++;
- filter = clipService.filter(ct);
- }
- } else {
- while (filter) {
- if (filter->get_int("kdenlive_ix") == oldPos) {
- filter->set("kdenlive_ix", newPos);
- filtersList.append(filter);
- clipService.detach(*filter);
- } else ct++;
- filter = clipService.filter(ct);
- }
-
- ct = 0;
- filter = clipService.filter(ct);
- while (filter) {
- int pos = filter->get_int("kdenlive_ix");
- if (pos >= newPos) {
- if (pos < oldPos) filter->set("kdenlive_ix", pos + 1);
- filtersList.append(filter);
- clipService.detach(*filter);
- } else ct++;
- filter = clipService.filter(ct);
- }
- }
-
- for (int i = 0; i < filtersList.count(); ++i) {
- clipService.attach(*(filtersList.at(i)));
- }
- qDeleteAll(filtersList);
- if (doRefresh) refresh();
-}
-
-void Render::mltMoveTrackEffect(int track, int oldPos, int newPos)
-{
- Mlt::Service service(m_mltProducer->parent().get_service());
- Mlt::Tractor tractor(service);
- //TODO: memleak
- Mlt::Producer trackProducer(tractor.track(track));
- Mlt::Playlist trackPlaylist((mlt_playlist) trackProducer.get_service());
- Mlt::Service clipService(trackPlaylist.get_service());
- int ct = 0;
- QList filtersList;
- Mlt::Filter *filter = clipService.filter(ct);
- if (newPos > oldPos) {
- bool found = false;
- while (filter) {
- if (!found && filter->get_int("kdenlive_ix") == oldPos) {
- filter->set("kdenlive_ix", newPos);
- filtersList.append(filter);
- clipService.detach(*filter);
- filter = clipService.filter(ct);
- while (filter && filter->get_int("kdenlive_ix") <= newPos) {
- filter->set("kdenlive_ix", filter->get_int("kdenlive_ix") - 1);
- ct++;
- filter = clipService.filter(ct);
- }
- found = true;
- }
- if (filter && filter->get_int("kdenlive_ix") > newPos) {
- filtersList.append(filter);
- clipService.detach(*filter);
- } else ct++;
- filter = clipService.filter(ct);
- }
- } else {
- while (filter) {
- if (filter->get_int("kdenlive_ix") == oldPos) {
- filter->set("kdenlive_ix", newPos);
- filtersList.append(filter);
- clipService.detach(*filter);
- } else ct++;
- filter = clipService.filter(ct);
- }
-
- ct = 0;
- filter = clipService.filter(ct);
- while (filter) {
- int pos = filter->get_int("kdenlive_ix");
- if (pos >= newPos) {
- if (pos < oldPos) filter->set("kdenlive_ix", pos + 1);
- filtersList.append(filter);
- clipService.detach(*filter);
- } else ct++;
- filter = clipService.filter(ct);
- }
- }
-
- for (int i = 0; i < filtersList.count(); ++i) {
- clipService.attach(*(filtersList.at(i)));
- }
- qDeleteAll(filtersList);
- refresh();
-}
-
bool Render::mltResizeClipCrop(ItemInfo info, GenTime newCropStart)
{
Mlt::Service service(m_mltProducer->parent().get_service());
int newCropFrame = (int) newCropStart.frames(m_fps);
Mlt::Tractor tractor(service);
Mlt::Producer trackProducer(tractor.track(info.track));
Mlt::Playlist trackPlaylist((mlt_playlist) trackProducer.get_service());
if (trackPlaylist.is_blank_at(info.startPos.frames(m_fps))) {
//qDebug() << "//////// ERROR RSIZING BLANK CLIP!!!!!!!!!!!";
return false;
}
service.lock();
int clipIndex = trackPlaylist.get_clip_index_at(info.startPos.frames(m_fps));
QScopedPointer clip(trackPlaylist.get_clip(clipIndex));
if (clip == NULL) {
//qDebug() << "//////// ERROR RSIZING NULL CLIP!!!!!!!!!!!";
service.unlock();
return false;
}
int previousStart = clip->get_in();
int previousOut = clip->get_out();
if (previousStart == newCropFrame) {
//qDebug() << "//////// No ReSIZING Required";
service.unlock();
return true;
}
int frameOffset = newCropFrame - previousStart;
trackPlaylist.resize_clip(clipIndex, newCropFrame, previousOut + frameOffset);
service.unlock();
m_isRefreshing = true;
m_mltConsumer->set("refresh", 1);
return true;
}
QList Render::checkTrackSequence(int track)
{
QList list;
Mlt::Service service(m_mltProducer->parent().get_service());
if (service.type() != tractor_type) {
qWarning() << "// TRACTOR PROBLEM";
return list;
}
Mlt::Tractor tractor(service);
service.lock();
Mlt::Producer trackProducer(tractor.track(track));
Mlt::Playlist trackPlaylist((mlt_playlist) trackProducer.get_service());
int clipNb = trackPlaylist.count();
////qDebug() << "// PARSING SCENE TRACK: " << t << ", CLIPS: " << clipNb;
for (int i = 0; i < clipNb; ++i) {
QScopedPointer c(trackPlaylist.get_clip(i));
int pos = trackPlaylist.clip_start(i);
if (!list.contains(pos)) list.append(pos);
pos += c->get_playtime();
if (!list.contains(pos)) list.append(pos);
}
return list;
}
void Render::cloneProperties(Mlt::Properties &dest, Mlt::Properties &source)
{
int count = source.count();
int i = 0;
for ( i = 0; i < count; i ++ )
{
char *value = source.get(i);
if ( value != NULL )
{
char *name = source.get_name( i );
if (name != NULL && name[0] != '_') dest.set(name, value);
}
}
}
void Render::fillSlowMotionProducers()
{
if (m_mltProducer == NULL) return;
Mlt::Service service(m_mltProducer->parent().get_service());
if (service.type() != tractor_type) return;
Mlt::Tractor tractor(service);
int trackNb = tractor.count();
for (int t = 1; t < trackNb; ++t) {
Mlt::Producer *tt = tractor.track(t);
Mlt::Producer trackProducer(tt);
delete tt;
Mlt::Playlist trackPlaylist((mlt_playlist) trackProducer.get_service());
if (!trackPlaylist.is_valid()) continue;
int clipNb = trackPlaylist.count();
for (int i = 0; i < clipNb; ++i) {
QScopedPointer c(trackPlaylist.get_clip(i));
Mlt::Producer *nprod = new Mlt::Producer(c->get_parent());
if (nprod) {
QString id = nprod->parent().get("id");
if (id.startsWith(QLatin1String("slowmotion:")) && !nprod->is_blank()) {
// this is a slowmotion producer, add it to the list
QString url = QString::fromUtf8(nprod->get("resource"));
int strobe = nprod->get_int("strobe");
if (strobe > 1) url.append("&strobe=" + QString::number(strobe));
if (!m_slowmotionProducers.contains(url)) {
m_slowmotionProducers.insert(url, nprod);
}
} else delete nprod;
}
}
}
}
//Updates all transitions
QList Render::mltInsertTrack(int ix, const QString &name, bool videoTrack, int lowestVideoTrack)
{
QList transitionInfos;
// Track add / delete was only added recently in MLT (pre 0.9.8 release).
#if (LIBMLT_VERSION_INT < 0x0908)
Q_UNUSED(ix)
Q_UNUSED(name)
Q_UNUSED(videoTrack)
qDebug()<<"Track insertion requires a more recent MLT version";
return transitionInfos;
#else
Mlt::Service service(m_mltProducer->parent().get_service());
if (service.type() != tractor_type) {
qWarning() << "// TRACTOR PROBLEM";
return QList ();
}
blockSignals(true);
service.lock();
Mlt::Tractor tractor(service);
Mlt::Playlist playlist(*service.profile());
playlist.set("kdenlive:track_name", name.toUtf8().constData());
int ct = tractor.count();
if (ix > ct) {
//qDebug() << "// ERROR, TRYING TO insert TRACK " << ix << ", max: " << ct;
ix = ct;
}
tractor.insert_track(playlist, ix);
Mlt::Producer newProd(tractor.track(ix));
if (!videoTrack) {
newProd.set("kdenlive:audio_track", 1);
newProd.set("hide", 1);
}
checkMaxThreads();
tractor.refresh();
Mlt::Field *field = tractor.field();
// Add audio mix transition to last track
Mlt::Transition mix(*m_qmlView->profile(), "mix");
mix.set("a_track", 0);
mix.set("b_track", ix);
mix.set("always_active", 1);
mix.set("internal_added", 237);
mix.set("combine", 1);
field->plant_transition(mix, 0, ix);
if (videoTrack) {
if (ix <= lowestVideoTrack) {
// Track was inserted as lowest video track, it should not have a composite, but previous lowest should
ix = lowestVideoTrack + 1;
}
Mlt::Transition composite(*m_qmlView->profile(), KdenliveSettings::gpu_accel() ? "movit.overlay" : "frei0r.cairoblend");
if (composite.is_valid()) {
composite.set("a_track", ix - 1);
composite.set("b_track", ix);
composite.set("internal_added", 237);
field->plant_transition(composite, ix - 1, ix);
}
}
/*
// re-add transitions
for (int i = trList.count() - 1; i >= 0; --i) {
field->plant_transition(*trList.at(i), trList.at(i)->get_a_track(), trList.at(i)->get_b_track());
}
qDeleteAll(trList);
*/
service.unlock();
blockSignals(false);
return transitionInfos;
#endif
}
void Render::sendFrameUpdate()
{
if (m_mltProducer) {
Mlt::Frame * frame = m_mltProducer->get_frame();
emitFrameUpdated(*frame);
delete frame;
}
}
Mlt::Producer* Render::getProducer()
{
return m_mltProducer;
}
const QString Render::activeClipId()
{
if (m_mltProducer) return m_mltProducer->get("id");
return QString();
}
//static
bool Render::getBlackMagicDeviceList(KComboBox *devicelist, bool force)
{
if (!force && !KdenliveSettings::decklink_device_found()) return false;
Mlt::Profile profile;
Mlt::Producer bm(profile, "decklink");
int found_devices = 0;
if (bm.is_valid()) {
bm.set("list_devices", 1);
found_devices = bm.get_int("devices");
}
else KdenliveSettings::setDecklink_device_found(false);
if (found_devices <= 0) {
devicelist->setEnabled(false);
return false;
}
KdenliveSettings::setDecklink_device_found(true);
for (int i = 0; i < found_devices; ++i) {
char *tmp = qstrdup(QStringLiteral("device.%1").arg(i).toUtf8().constData());
devicelist->addItem(bm.get(tmp));
delete[] tmp;
}
return true;
}
bool Render::getBlackMagicOutputDeviceList(KComboBox *devicelist, bool force)
{
if (!force && !KdenliveSettings::decklink_device_found()) return false;
Mlt::Profile profile;
Mlt::Consumer bm(profile, "decklink");
int found_devices = 0;
if (bm.is_valid()) {
bm.set("list_devices", 1);;
found_devices = bm.get_int("devices");
}
else KdenliveSettings::setDecklink_device_found(false);
if (found_devices <= 0) {
devicelist->setEnabled(false);
return false;
}
KdenliveSettings::setDecklink_device_found(true);
for (int i = 0; i < found_devices; ++i) {
char *tmp = qstrdup(QStringLiteral("device.%1").arg(i).toUtf8().constData());
devicelist->addItem(bm.get(tmp));
delete[] tmp;
}
return true;
}
//static
bool Render::checkX11Grab()
{
if (KdenliveSettings::rendererpath().isEmpty() || KdenliveSettings::ffmpegpath().isEmpty()) return false;
QProcess p;
QStringList args;
args << QStringLiteral("avformat:f-list");
p.start(KdenliveSettings::rendererpath(), args);
if (!p.waitForStarted()) return false;
if (!p.waitForFinished()) return false;
QByteArray result = p.readAllStandardError();
return result.contains("x11grab");
}
double Render::getMltVersionInfo(const QString &tag)
{
double version = 0;
Mlt::Properties *metadata = m_binController->mltRepository()->metadata(producer_type, tag.toUtf8().data());
if (metadata && metadata->is_valid()) {
version = metadata->get_double("version");
}
if (metadata) delete metadata;
return version;
}
Mlt::Producer *Render::getBinProducer(const QString &id)
{
return m_binController->getBinProducer(id);
}
Mlt::Producer *Render::getBinVideoProducer(const QString &id)
{
return m_binController->getBinVideoProducer(id);
}
void Render::loadExtraProducer(const QString &id, Mlt::Producer *prod)
{
m_binController->loadExtraProducer(id, prod);
}
const QString Render::getBinProperty(const QString &name)
{
return m_binController->getProperty(name);
}
void Render::setVolume(double volume)
{
if (m_mltConsumer) {
if (m_mltConsumer->get("mlt_service") == QStringLiteral("multi")) {
m_mltConsumer->set("0.volume", volume);
} else {
m_mltConsumer->set("volume", volume);
}
}
}
bool Render::storeSlowmotionProducer(const QString &url, Mlt::Producer *prod, bool replace)
{
if (!m_slowmotionProducers.contains(url)) {
m_slowmotionProducers.insert(url, prod);
return true;
}
else if (replace) {
Mlt::Producer *old = m_slowmotionProducers.take(url);
delete old;
m_slowmotionProducers.insert(url, prod);
return true;
}
return false;
}
Mlt::Producer *Render::getSlowmotionProducer(const QString &url)
{
if (m_slowmotionProducers.contains(url)) {
return m_slowmotionProducers.value(url);
}
return NULL;
}
void Render::updateSlowMotionProducers(const QString &id, QMap passProperties)
{
QMapIterator i(m_slowmotionProducers);
Mlt::Producer *prod;
while (i.hasNext()) {
i.next();
prod = i.value();
QString currentId = prod->get("id");
if (currentId.startsWith("slowmotion:" + id + ":")) {
QMapIterator j(passProperties);
while (j.hasNext()) {
j.next();
prod->set(j.key().toUtf8().constData(), j.value().toUtf8().constData());
}
}
}
}
void Render::previewRendering(QPoint zone, const QString &cacheDir, const QString &documentId)
{
if (m_previewThread.isRunning()) {
qDebug()<<"/ / /Already processing a preview render, abort";
return;
}
QDir dir(cacheDir);
dir.mkpath(QStringLiteral("."));
// Data is rendered in 100 frames chunks
int startChunk = zone.x() / 100;
int endChunk = rintl(zone.y() / 100);
// Save temporary scenelist
QString sceneListFile = dir.absoluteFilePath(documentId + ".mlt");
Mlt::Consumer xmlConsumer(*m_qmlView->profile(), "xml", sceneListFile.toUtf8().constData());
if (!xmlConsumer.is_valid())
return;
m_mltProducer->optimise();
xmlConsumer.set("terminate_on_pause", 1);
Mlt::Producer prod(m_mltProducer->get_producer());
if (!prod.is_valid())
return;
xmlConsumer.connect(prod);
xmlConsumer.run();
m_previewThread = QtConcurrent::run(this, &Render::doPreviewRender, startChunk, endChunk, dir, documentId, sceneListFile);
}
void Render::doPreviewRender(int start, int end, QDir folder, QString id, QString scene)
{
int progress;
for (int i = start; i <= end; ++i) {
if (m_abortPreview)
break;
QString fileName = id + QString("-%1.mp4").arg(i);
if (end > start) {
progress = (double) (i - start + 1) / (end +1 - start) * 100;
} else {
progress = 100;
}
if (folder.exists(fileName)) {
// This chunk already exists
emit previewRender(i * 100, folder.absoluteFilePath(fileName), progress);
continue;
}
// Build rendering process
QStringList args;
args << scene;
args << "in=" + QString::number(i * 100);
args << "out=" + QString::number(i * 100 + 99);
args << "-consumer" << "avformat:" + folder.absoluteFilePath(fileName);
args << "an=1";
int result = QProcess::execute(KdenliveSettings::rendererpath(), args);
if (result < 0) {
// Something is wrong, abort
break;
}
emit previewRender(i * 100, folder.absoluteFilePath(fileName), progress);
}
QFile::remove(scene);
m_abortPreview = false;
}
diff --git a/src/renderer.h b/src/renderer.h
index c86a6cdd6..f3ee9e974 100644
--- a/src/renderer.h
+++ b/src/renderer.h
@@ -1,402 +1,380 @@
/***************************************************************************
krender.h - description
-------------------
begin : Fri Nov 22 2002
copyright : (C) 2002 by Jason Wood (jasonwood@blueyonder.co.uk)
copyright : (C) 2010 by Jean-Baptiste Mardelle (jb@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) any later version. *
* *
***************************************************************************/
/**
* @class Render
* @brief Client side of the interface to a renderer.
*
* REFACTORING NOTE -- There is most likely no point in trying to refactor
* the renderer, it is better re-written directly (see refactoring branch)
* since there is a lot of code duplication, no documentation, and several
* hacks that have emerged from the previous two problems.
*
* From Kdenlive's point of view, you treat the Render object as the renderer,
* and simply use it as if it was local. Calls are asynchronous - you send a
* call out, and then receive the return value through the relevant signal that
* get's emitted once the call completes.
*/
#ifndef RENDERER_H
#define RENDERER_H
#include "gentime.h"
#include "definitions.h"
#include "monitor/abstractmonitor.h"
#include "mltcontroller/effectscontroller.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
class KComboBox;
class BinController;
class ClipController;
class GLWidget;
namespace Mlt
{
class Consumer;
class Playlist;
class Properties;
class Tractor;
class Transition;
class Frame;
class Field;
class Producer;
class Filter;
class Profile;
class Service;
class Event;
}
class MltErrorEvent : public QEvent
{
public:
explicit MltErrorEvent(const QString &message)
: QEvent(QEvent::User),
m_message(message)
{
}
QString message() const {
return m_message;
}
private:
QString m_message;
};
class Render: public AbstractRender
{
Q_OBJECT public:
enum FailStates { OK = 0,
APP_NOEXIST
};
/** @brief Build a MLT Renderer
* @param rendererName A unique identifier for this renderer
* @param winid The parent widget identifier (required for SDL display). Set to 0 for OpenGL rendering
* @param profile The MLT profile used for the renderer (default one will be used if empty). */
Render(Kdenlive::MonitorId rendererName, BinController *binController, GLWidget *qmlView, QWidget *parent = 0);
/** @brief Destroy the MLT Renderer. */
virtual ~Render();
/** @brief Seeks the renderer clip to the given time. */
void seek(const GenTime &time);
void seekToFrameDiff(int diff);
/** @brief Sets the current MLT producer playlist.
* @param list The xml describing the playlist
* @param position (optional) time to seek to */
int setSceneList(const QDomDocument &list, int position = 0);
/** @brief Sets the current MLT producer playlist.
* @param list new playlist
* @param position (optional) time to seek to
* @return 0 when it has success, different from 0 otherwise
*
* Creates the producer from the text playlist. */
int setSceneList(QString playlist, int position = 0);
bool updateProducer(Mlt::Producer *producer);
bool setProducer(Mlt::Producer *producer, int position, bool isActive);
/** @brief Get the current MLT producer playlist.
* @return A string describing the playlist */
const QString sceneList();
bool saveSceneList(QString path, QDomElement kdenliveData = QDomElement());
/** @brief Tells the renderer to play the scene at the specified speed,
* @param speed speed to play the scene to
*
* The speed is relative to normal playback, e.g. 1.0 is normal speed, 0.0
* is paused, -1.0 means play backwards. It does not specify start/stop */
void play(double speed);
void switchPlay(bool play);
/** @brief Stops playing.
* @param startTime time to seek to */
void stop(const GenTime &startTime);
int volume() const;
QImage extractFrame(int frame_position, const QString &path = QString(), int width = -1, int height = -1);
/** @brief Plays the scene starting from a specific time.
* @param startTime time to start playing the scene from */
void play(const GenTime & startTime);
bool playZone(const GenTime & startTime, const GenTime & stopTime);
void loopZone(const GenTime & startTime, const GenTime & stopTime);
/** @brief Return true if we are currently playing */
bool isPlaying() const;
/** @brief Returns the speed at which the renderer is currently playing.
*
* It returns 0.0 when the renderer is not playing anything. */
double playSpeed() const;
/** @brief Returns the current seek position of the renderer. */
GenTime seekPosition() const;
int seekFramePosition() const;
void emitFrameUpdated(Mlt::Frame&);
double fps() const;
/** @brief Returns the width of a frame for this profile. */
int frameRenderWidth() const;
/** @brief Returns the display width of a frame for this profile. */
int renderWidth() const;
/** @brief Returns the height of a frame for this profile. */
int renderHeight() const;
/** @brief Returns display aspect ratio. */
double dar() const;
/** @brief Returns sample aspect ratio. */
double sar() const;
/** @brief Start the MLT monitor consumer. */
void startConsumer();
/*
* Playlist manipulation.
*/
void mltCheckLength(Mlt::Tractor *tractor);
Mlt::Producer *getSlowmotionProducer(const QString &url);
void mltInsertSpace(QMap trackClipStartList, QMap trackTransitionStartList, int track, const GenTime &duration, const GenTime &timeOffset);
int mltGetSpaceLength(const GenTime &pos, int track, bool fromBlankStart);
bool mltResizeClipCrop(ItemInfo info, GenTime newCropStart);
- /** @brief Enable / disable clip effects.
- * @param track The track where the clip is
- * @param position The start position of the clip
- * @param effectIndexes The list of effect indexes to enable / disable
- * @param disable True if effects should be disabled, false otherwise */
- bool mltEnableEffects(int track, const GenTime &position, const QList &effectIndexes, bool disable);
- /** @brief Enable / disable track effects.
- * @param track The track where the effect is
- * @param effectIndexes The list of effect indexes to enable / disable
- * @param disable True if effects should be disabled, false otherwise */
- bool mltEnableTrackEffects(int track, const QList &effectIndexes, bool disable);
-
- /** @brief Updates the "kdenlive_ix" (index) value of an effect. */
- void mltUpdateEffectPosition(int track, const GenTime &position, int oldPos, int newPos);
-
- /** @brief Changes the order of effects in MLT's playlist.
- *
- * It switches effects from oldPos and newPos, updating the "kdenlive_ix"
- * (index) value. */
- void mltMoveEffect(int track, const GenTime &position, int oldPos, int newPos);
- void mltMoveTrackEffect(int track, int oldPos, int newPos);
-
QList mltInsertTrack(int ix, const QString &name, bool videoTrack, int lowestVideoTrack);
//const QList producersList();
void setDropFrames(bool show);
/** @brief Sets an MLT consumer property. */
void setConsumerProperty(const QString &name, const QString &value);
void showAudio(Mlt::Frame&);
-
+
QList checkTrackSequence(int);
void sendFrameUpdate();
/** @brief Returns a pointer to the main producer. */
Mlt::Producer *getProducer();
/** @brief Returns a pointer to the bin's playlist. */
/** @brief Lock the MLT service */
Mlt::Tractor *lockService();
/** @brief Unlock the MLT service */
void unlockService(Mlt::Tractor *tractor);
const QString activeClipId();
/** @brief Fill a combobox with the found blackmagic devices */
static bool getBlackMagicDeviceList(KComboBox *devicelist, bool force = false);
static bool getBlackMagicOutputDeviceList(KComboBox *devicelist, bool force = false);
/** @brief Get current seek pos requested or SEEK_INACTIVE if we are not currently seeking */
int requestedSeekPosition;
/** @brief Get current seek pos requested of current producer pos if not seeking */
int getCurrentSeekPosition() const;
/** @brief Create a producer from url and load it in the monitor */
void loadUrl(const QString &url);
/** @brief Check if the installed FFmpeg / Libav supports x11grab */
static bool checkX11Grab();
/** @brief Get a track producer from a clip's id
* Deprecated, track producers are now handled in timeline/track.cpp
*/
Q_DECL_DEPRECATED Mlt::Producer *getTrackProducer(const QString &id, int track, bool audioOnly = false, bool videoOnly = false);
/** @brief Ask to set this monitor as active */
void setActiveMonitor();
QSemaphore showFrameSemaphore;
bool externalConsumer;
/** @brief Returns the current version of an MLT's producer module */
double getMltVersionInfo(const QString &tag);
/** @brief Get a clip's master producer */
Mlt::Producer *getBinProducer(const QString &id);
/** @brief Get a clip's video only producer */
Mlt::Producer *getBinVideoProducer(const QString &id);
/** @brief Load extra producers (video only, slowmotion) from timeline */
void loadExtraProducer(const QString &id, Mlt::Producer *prod);
/** @brief Get a property from the bin's playlist */
const QString getBinProperty(const QString &name);
void setVolume(double volume);
/** @brief Stop all activities in preparation for a change in profile */
void prepareProfileReset(double fps);
void finishProfileReset();
void updateSlowMotionProducers(const QString &id, QMap passProperties);
void previewRendering(QPoint zone, const QString &cacheDir, const QString &documentId);
private:
/** @brief The name of this renderer.
*
* Useful to identify the renderers by what they do - e.g. background
* rendering, workspace monitor, etc. */
Kdenlive::MonitorId m_name;
Mlt::Consumer * m_mltConsumer;
Mlt::Producer * m_mltProducer;
Mlt::Event *m_showFrameEvent;
Mlt::Event *m_pauseEvent;
QFuture m_previewThread;
BinController *m_binController;
GLWidget *m_qmlView;
double m_fps;
/** @brief True if we are playing a zone.
*
* It's determined by the "in" and "out" properties being temporarily
* changed. */
bool m_isZoneMode;
bool m_isLoopMode;
GenTime m_loopStart;
Mlt::Producer *m_blackClip;
QTimer m_refreshTimer;
QMutex m_mutex;
QMutex m_infoMutex;
QLocale m_locale;
/** @brief True if this monitor is active. */
bool m_isActive;
/** @brief True if the consumer is currently refreshing itself. */
bool m_isRefreshing;
bool m_abortPreview;
void closeMlt();
QMap m_slowmotionProducers;
/** @brief Build the MLT Consumer object with initial settings.
* @param profileName The MLT profile to use for the consumer */
//void buildConsumer();
/** @brief Restore normal mode */
void resetZoneMode();
void fillSlowMotionProducers();
/** @brief Make sure we inform MLT if we need a lot of threads for avformat producer */
void checkMaxThreads();
/** @brief Clone serialisable properties only */
void cloneProperties(Mlt::Properties &dest, Mlt::Properties &source);
/** @brief Get a track producer from a clip's id */
Mlt::Producer *getProducerForTrack(Mlt::Playlist &trackPlaylist, const QString &clipId);
private slots:
/** @brief Refreshes the monitor display. */
void refresh();
void slotCheckSeeking();
void doPreviewRender(int start, int end, QDir folder, QString id, QString scene);
signals:
/** @brief The renderer stopped, either playing or rendering. */
void stopped();
/** @brief The renderer started playing. */
void playing(double);
/** @brief The renderer started rendering. */
void rendering(const GenTime &);
/** @brief An error occurred within this renderer. */
void error(const QString &, const QString &);
void durationChanged(int, int offset = 0);
void rendererPosition(int);
void rendererStopped(int);
/** @brief A clip has changed, we must reload timeline producers. */
void replaceTimelineProducer(const QString&);
void updateTimelineProducer(const QString&);
/** @brief Load project notes. */
void setDocumentNotes(const QString&);
/** @brief The renderer received a reply to a getFileProperties request. */
void gotFileProperties(requestClipInfo,ClipController *);
/** @brief A frame's image has to be shown.
*
* Used in Mac OS X. */
void showImageSignal(QImage);
void showAudioSignal(const QVector &);
void checkSeeking();
/** @brief Activate current monitor. */
void activateMonitor(Kdenlive::MonitorId);
void mltFrameReceived(Mlt::Frame *);
/** @brief We want to replace a clip with another, but before we need to change clip producer id so that there is no interference*/
void prepareTimelineReplacement(const QString &);
void previewRender(int frame, const QString &file, int progress);
public slots:
/** @brief Starts the consumer. */
void start();
/** @brief Stops the consumer. */
void stop();
int getLength();
/** @brief Checks if the file is readable by MLT. */
bool isValid(const QUrl &url);
void slotSwitchFullscreen();
void seekToFrame(int pos);
/** @brief Starts a timer to query for a refresh. */
void doRefresh();
void emitFrameUpdated(QImage img);
/** @brief Save a part of current timeline to an xml file. */
void saveZone(QPoint zone);
/** @brief Renderer moved to a new frame, check seeking */
bool checkFrameNumber(int pos);
/** @brief Keep a reference to slowmo producer. Returns false is producer is already stored */
bool storeSlowmotionProducer(const QString &url, Mlt::Producer *prod, bool replace = false);
void seek(int time);
};
#endif
diff --git a/src/timeline/customtrackview.cpp b/src/timeline/customtrackview.cpp
index 2106e19b0..08f7a8c26 100644
--- a/src/timeline/customtrackview.cpp
+++ b/src/timeline/customtrackview.cpp
@@ -1,8605 +1,8606 @@
/***************************************************************************
* Copyright (C) 2007 by Jean-Baptiste Mardelle (jb@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) any later version. *
* *
* 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, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
***************************************************************************/
#include "customtrackview.h"
#include "timeline.h"
#include "track.h"
#include "clipitem.h"
#include "timelinecommands.h"
#include "transition.h"
#include "markerdialog.h"
#include "clipdurationdialog.h"
#include "abstractgroupitem.h"
#include "spacerdialog.h"
#include "trackdialog.h"
#include "tracksconfigdialog.h"
#include "mltcontroller/clipcontroller.h"
#include "mltcontroller/effectscontroller.h"
#include "definitions.h"
#include "kdenlivesettings.h"
#include "renderer.h"
#include "bin/projectclip.h"
#include "mainwindow.h"
#include "transitionhandler.h"
#include "project/clipmanager.h"
#include "utils/KoIconUtils.h"
#include "effectslist/initeffects.h"
#include "effectstack/widgets/keyframeimport.h"
#include "dialogs/profilesdialog.h"
#include "managers/guidemanager.h"
#include "managers/razormanager.h"
#include "managers/selectmanager.h"
#include "ui_keyframedialog_ui.h"
#include "ui_addtrack_ui.h"
#include "lib/audio/audioEnvelope.h"
#include "lib/audio/audioCorrelation.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SEEK_INACTIVE (-1)
//#define DEBUG
bool sortGuidesList(const Guide *g1 , const Guide *g2)
{
return (*g1).position() < (*g2).position();
}
CustomTrackView::CustomTrackView(KdenliveDoc *doc, Timeline *timeline, CustomTrackScene* projectscene, QWidget *parent) :
QGraphicsView(projectscene, parent)
, m_tracksHeight(KdenliveSettings::trackheight())
, m_projectDuration(0)
, m_cursorPos(0)
, m_cursorOffset(0)
, m_document(doc)
, m_timeline(timeline)
, m_scene(projectscene)
, m_cursorLine(NULL)
, m_cutLine(NULL)
, m_operationMode(None)
, m_moveOpMode(None)
, m_dragItem(NULL)
, m_dragGuide(NULL)
, m_visualTip(NULL)
, m_keyProperties(NULL)
, m_autoScroll(KdenliveSettings::autoscroll())
, m_timelineContextMenu(NULL)
, m_timelineContextClipMenu(NULL)
, m_timelineContextTransitionMenu(NULL)
, m_timelineContextKeyframeMenu(NULL)
, m_selectKeyframeType(NULL)
, m_markerMenu(NULL)
, m_autoTransition(NULL)
, m_pasteEffectsAction(NULL)
, m_ungroupAction(NULL)
, m_editGuide(NULL)
, m_deleteGuide(NULL)
, m_clipTypeGroup(NULL)
, m_scrollOffset(0)
, m_clipDrag(false)
, m_findIndex(0)
, m_tool(SelectTool)
, m_copiedItems()
, m_menuPosition()
, m_selectionGroup(NULL)
, m_selectedTrack(1)
, m_spacerOffset(0)
, m_audioCorrelator(NULL)
, m_audioAlignmentReference(NULL)
, m_controlModifier(false)
{
if (doc) {
m_commandStack = doc->commandStack();
} else {
m_commandStack = NULL;
}
m_ct = 0;
setMouseTracking(true);
setAcceptDrops(true);
setFrameShape(QFrame::NoFrame);
setLineWidth(0);
//setCacheMode(QGraphicsView::CacheBackground);
setAutoFillBackground(false);
setViewportUpdateMode(QGraphicsView::MinimalViewportUpdate);
setContentsMargins(0, 0, 0, 0);
KColorScheme scheme(palette().currentColorGroup(), KColorScheme::Window, KSharedConfig::openConfig(KdenliveSettings::colortheme()));
m_selectedTrackColor = scheme.background(KColorScheme::ActiveBackground ).color();
m_selectedTrackColor.setAlpha(150);
m_lockedTrackColor = scheme.background(KColorScheme::NegativeBackground ).color();
m_lockedTrackColor.setAlpha(150);
m_keyPropertiesTimer = new QTimeLine(800);
m_keyPropertiesTimer->setFrameRange(0, 5);
m_keyPropertiesTimer->setUpdateInterval(100);
m_keyPropertiesTimer->setLoopCount(0);
m_tipColor = QColor(0, 192, 0, 200);
m_tipPen.setColor(QColor(255, 255, 255, 100));
m_tipPen.setWidth(3);
setSceneRect(0, 0, sceneRect().width(), m_tracksHeight);
verticalScrollBar()->setMaximum(m_tracksHeight);
verticalScrollBar()->setTracking(true);
// repaint guides when using vertical scroll
connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(slotRefreshGuides()));
connect(horizontalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(slotRefreshCutLine()));
m_cursorLine = projectscene->addLine(0, 0, 0, m_tracksHeight);
m_cursorLine->setZValue(1000);
QPen pen1 = QPen();
pen1.setWidth(1);
QColor line(palette().text().color());
line.setAlpha(100);
pen1.setColor(line);
m_cursorLine->setPen(pen1);
connect(&m_scrollTimer, SIGNAL(timeout()), this, SLOT(slotCheckMouseScrolling()));
m_scrollTimer.setInterval(100);
m_scrollTimer.setSingleShot(true);
QIcon razorIcon = KoIconUtils::themedIcon(QStringLiteral("edit-cut"));
m_razorCursor = QCursor(razorIcon.pixmap(32, 32));
m_spacerCursor = QCursor(Qt::SplitHCursor);
connect(m_document->renderer(), SIGNAL(prepareTimelineReplacement(QString)), this, SLOT(slotPrepareTimelineReplacement(QString)), Qt::DirectConnection);
connect(m_document->renderer(), SIGNAL(replaceTimelineProducer(QString)), this, SLOT(slotReplaceTimelineProducer(QString)), Qt::DirectConnection);
connect(m_document->renderer(), SIGNAL(updateTimelineProducer(QString)), this, SLOT(slotUpdateTimelineProducer(QString)));
connect(m_document->renderer(), SIGNAL(rendererPosition(int)), this, SLOT(setCursorPos(int)));
scale(1, 1);
setAlignment(Qt::AlignLeft | Qt::AlignTop);
m_disableClipAction = new QAction(QIcon::fromTheme(QStringLiteral("visibility")), i18n("Disable Clip"), this);
connect(m_disableClipAction, &QAction::triggered, this, &CustomTrackView::disableClip);
m_disableClipAction->setCheckable(true);
m_document->doAddAction(QStringLiteral("clip_disabled"), m_disableClipAction);
}
CustomTrackView::~CustomTrackView()
{
qDeleteAll(m_guides);
m_guides.clear();
delete m_keyPropertiesTimer;
}
//virtual
void CustomTrackView::keyPressEvent(QKeyEvent * event)
{
if (event->key() == Qt::Key_Up) {
slotTrackUp();
event->accept();
} else if (event->key() == Qt::Key_Down) {
slotTrackDown();
event->accept();
} else QWidget::keyPressEvent(event);
}
void CustomTrackView::setDocumentModified()
{
m_document->setModified(true);
}
void CustomTrackView::setContextMenu(QMenu *timeline, QMenu *clip, QMenu *transition, QActionGroup *clipTypeGroup, QMenu *markermenu)
{
m_clipTypeGroup = clipTypeGroup;
m_timelineContextMenu = timeline;
m_timelineContextClipMenu = clip;
m_timelineContextTransitionMenu = transition;
m_timelineContextClipMenu->addAction(m_disableClipAction);
connect(m_timelineContextTransitionMenu, SIGNAL(aboutToHide()), this, SLOT(slotResetMenuPosition()));
connect(m_timelineContextMenu, SIGNAL(aboutToHide()), this, SLOT(slotResetMenuPosition()));
connect(m_timelineContextClipMenu, SIGNAL(aboutToHide()), this, SLOT(slotResetMenuPosition()));
connect(m_timelineContextTransitionMenu, SIGNAL(triggered(QAction*)), this, SLOT(slotContextMenuActivated()));
connect(m_timelineContextMenu, SIGNAL(triggered(QAction*)), this, SLOT(slotContextMenuActivated()));
connect(m_timelineContextClipMenu, SIGNAL(triggered(QAction*)), this, SLOT(slotContextMenuActivated()));
m_markerMenu = new QMenu(i18n("Go to marker..."), this);
m_markerMenu->setEnabled(false);
markermenu->addMenu(m_markerMenu);
connect(m_markerMenu, SIGNAL(triggered(QAction*)), this, SLOT(slotGoToMarker(QAction*)));
QList list = m_timelineContextClipMenu->actions();
for (int i = 0; i < list.count(); ++i) {
if (list.at(i)->data().toString() == QLatin1String("paste_effects")) m_pasteEffectsAction = list.at(i);
else if (list.at(i)->data().toString() == QLatin1String("ungroup_clip")) m_ungroupAction = list.at(i);
else if (list.at(i)->data().toString() == QLatin1String("A")) m_audioActions.append(list.at(i));
else if (list.at(i)->data().toString() == QLatin1String("A+V")) m_avActions.append(list.at(i));
}
list = m_timelineContextTransitionMenu->actions();
for (int i = 0; i < list.count(); ++i) {
if (list.at(i)->data().toString() == QLatin1String("auto")) {
m_autoTransition = list.at(i);
break;
}
}
m_timelineContextMenu->addSeparator();
m_deleteGuide = new QAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18n("Delete Guide"), this);
connect(m_deleteGuide, SIGNAL(triggered()), this, SLOT(slotDeleteTimeLineGuide()));
m_timelineContextMenu->addAction(m_deleteGuide);
m_editGuide = new QAction(QIcon::fromTheme(QStringLiteral("document-properties")), i18n("Edit Guide"), this);
connect(m_editGuide, SIGNAL(triggered()), this, SLOT(slotEditTimeLineGuide()));
m_timelineContextMenu->addAction(m_editGuide);
}
void CustomTrackView::slotDoResetMenuPosition()
{
m_menuPosition = QPoint();
}
void CustomTrackView::slotResetMenuPosition()
{
// after a short time (so that the action is triggered / or menu is closed, we reset the menu pos
QTimer::singleShot(300, this, SLOT(slotDoResetMenuPosition()));
}
void CustomTrackView::slotContextMenuActivated()
{
// Menu disappeared, restore default operation mode
m_operationMode = None;
}
void CustomTrackView::checkAutoScroll()
{
m_autoScroll = KdenliveSettings::autoscroll();
}
int CustomTrackView::getFrameWidth() const
{
return (int) (m_tracksHeight * m_document->dar() + 0.5);
}
void CustomTrackView::updateSceneFrameWidth(bool fpsChanged)
{
int frameWidth = getFrameWidth();
if (fpsChanged && m_projectDuration > 0) {
reloadTimeline();
} else {
QList itemList = items();
ClipItem *item;
for (int i = 0; i < itemList.count(); ++i) {
if (itemList.at(i)->type() == AVWidget) {
item = static_cast(itemList.at(i));
item->resetFrameWidth(frameWidth);
}
}
}
}
bool CustomTrackView::checkTrackHeight(bool force)
{
if (!force && m_tracksHeight == KdenliveSettings::trackheight() && sceneRect().height() == m_tracksHeight * m_timeline->visibleTracksCount()) return false;
int frameWidth = getFrameWidth();
if (m_tracksHeight != KdenliveSettings::trackheight()) {
QList itemList = items();
ClipItem *item;
Transition *transitionitem;
m_tracksHeight = KdenliveSettings::trackheight();
// Remove all items, and re-add them one by one to avoid collisions
for (int i = 0; i < itemList.count(); ++i) {
if (itemList.at(i)->type() == AVWidget || itemList.at(i)->type() == TransitionWidget) {
m_scene->removeItem(itemList.at(i));
}
}
bool snap = KdenliveSettings::snaptopoints();
KdenliveSettings::setSnaptopoints(false);
for (int i = 0; i < itemList.count(); ++i) {
if (itemList.at(i)->type() == AVWidget) {
item = static_cast(itemList.at(i));
item->setRect(0, 0, item->rect().width(), m_tracksHeight - 1);
item->setPos((qreal) item->startPos().frames(m_document->fps()), getPositionFromTrack(item->track()) + 1);
m_scene->addItem(item);
item->resetFrameWidth(frameWidth);
} else if (itemList.at(i)->type() == TransitionWidget) {
transitionitem = static_cast(itemList.at(i));
transitionitem->setRect(0, 0, transitionitem->rect().width(), m_tracksHeight / 3 * 2 - 1);
transitionitem->setPos((qreal) transitionitem->startPos().frames(m_document->fps()), getPositionFromTrack(transitionitem->track()) + transitionitem->itemOffset());
m_scene->addItem(transitionitem);
}
}
KdenliveSettings::setSnaptopoints(snap);
}
double newHeight = m_tracksHeight * m_timeline->visibleTracksCount() * matrix().m22();
m_cursorLine->setLine(0, 0, 0, newHeight - 1);
if (m_cutLine) {
m_cutLine->setLine(0, 0, 0, m_tracksHeight * m_scene->scale().y());
}
for (int i = 0; i < m_guides.count(); ++i) {
m_guides.at(i)->setLine(0, 0, 0, newHeight - 1);
}
setSceneRect(0, 0, sceneRect().width(), m_tracksHeight * m_timeline->visibleTracksCount());
viewport()->update();
return true;
}
/** Zoom or move viewport on mousewheel
*
* If mousewheel+Ctrl, zooms in/out on the timeline.
*
* With Ctrl, moves viewport towards end of timeline if down/back,
* opposite on up/forward.
*
* See also http://www.kdenlive.org/mantis/view.php?id=265 */
void CustomTrackView::wheelEvent(QWheelEvent * e)
{
if (e->modifiers() == Qt::ControlModifier) {
if (m_moveOpMode == None || m_moveOpMode == WaitingForConfirm || m_moveOpMode == ZoomTimeline) {
if (e->delta() > 0) emit zoomIn();
else emit zoomOut();
}
} else if (e->modifiers() == Qt::AltModifier) {
if (m_moveOpMode == None || m_moveOpMode == WaitingForConfirm || m_moveOpMode == ZoomTimeline) {
if (e->delta() > 0) slotSeekToNextSnap();
else slotSeekToPreviousSnap();
}
} else {
if (m_moveOpMode == ResizeStart || m_moveOpMode == ResizeEnd) {
// Don't allow scrolling + resizing
return;
}
if (m_operationMode == None || m_operationMode == ZoomTimeline) {
// Prevent unwanted object move
m_scene->isZooming = true;
}
if (e->delta() <= 0) horizontalScrollBar()->setValue(horizontalScrollBar()->value() + horizontalScrollBar()->singleStep());
else horizontalScrollBar()->setValue(horizontalScrollBar()->value() - horizontalScrollBar()->singleStep());
if (m_operationMode == None || m_operationMode == ZoomTimeline) {
m_scene->isZooming = false;
}
}
}
int CustomTrackView::getPreviousVideoTrack(int track)
{
int i = track - 1;
for (; i > 0; i--) {
if (m_timeline->getTrackInfo(i).type == VideoTrack) break;
}
return i;
}
int CustomTrackView::getNextVideoTrack(int track)
{
for (; track < m_timeline->visibleTracksCount(); track++) {
if (m_timeline->getTrackInfo(track).type == VideoTrack) break;
}
return track;
}
void CustomTrackView::slotCheckMouseScrolling()
{
if (m_scrollOffset == 0) {
m_scrollTimer.stop();
return;
}
horizontalScrollBar()->setValue(horizontalScrollBar()->value() + m_scrollOffset);
m_scrollTimer.start();
}
void CustomTrackView::slotCheckPositionScrolling()
{
// If mouse is at a border of the view, scroll
if (m_moveOpMode != Seek) return;
if (mapFromScene(m_cursorPos, 0).x() < 3) {
if (horizontalScrollBar()->value() == 0) return;
horizontalScrollBar()->setValue(horizontalScrollBar()->value() - 2);
QTimer::singleShot(200, this, SLOT(slotCheckPositionScrolling()));
seekCursorPos(mapToScene(QPoint(-2, 0)).x());
} else if (viewport()->width() - 3 < mapFromScene(m_cursorPos + 1, 0).x()) {
horizontalScrollBar()->setValue(horizontalScrollBar()->value() + 2);
seekCursorPos(mapToScene(QPoint(viewport()->width(), 0)).x() + 1);
QTimer::singleShot(200, this, SLOT(slotCheckPositionScrolling()));
}
}
void CustomTrackView::slotAlignPlayheadToMousePos()
{
/* get curser point ref in screen coord */
QPoint ps = QCursor::pos();
/* get xPos in scene coord */
int mappedXPos = qMax((int)(mapToScene(mapFromGlobal(ps)).x() + 0.5), 0);
/* move playhead to new xPos*/
seekCursorPos(mappedXPos);
}
int CustomTrackView::getMousePos() const
{
return qMax((int)(mapToScene(mapFromGlobal(QCursor::pos())).x() + 0.5), 0);
}
void CustomTrackView::spaceToolMoveToSnapPos(double snappedPos)
{
// Make sure there is no collision
QList children = m_selectionGroup->childItems();
QPainterPath shape = m_selectionGroup->clipGroupSpacerShape(QPointF(snappedPos - m_selectionGroup->sceneBoundingRect().left(), 0));
QList collidingItems = scene()->items(shape, Qt::IntersectsItemShape);
collidingItems.removeAll(m_selectionGroup);
for (int i = 0; i < children.count(); ++i) {
if (children.at(i)->type() == GroupWidget) {
QList subchildren = children.at(i)->childItems();
for (int j = 0; j < subchildren.count(); ++j)
collidingItems.removeAll(subchildren.at(j));
}
collidingItems.removeAll(children.at(i));
}
bool collision = false;
int offset = 0;
for (int i = 0; i < collidingItems.count(); ++i) {
if (!collidingItems.at(i)->isEnabled()) continue;
if (collidingItems.at(i)->type() == AVWidget && snappedPos < m_selectionGroup->sceneBoundingRect().left()) {
AbstractClipItem *item = static_cast (collidingItems.at(i));
// Moving backward, determine best pos
QPainterPath clipPath;
clipPath.addRect(item->sceneBoundingRect());
QPainterPath res = shape.intersected(clipPath);
offset = qMax(offset, (int)(res.boundingRect().width() + 0.5));
}
}
snappedPos += offset;
// make sure we have no collision
shape = m_selectionGroup->clipGroupSpacerShape(QPointF(snappedPos - m_selectionGroup->sceneBoundingRect().left(), 0));
collidingItems = scene()->items(shape, Qt::IntersectsItemShape);
collidingItems.removeAll(m_selectionGroup);
for (int i = 0; i < children.count(); ++i) {
if (children.at(i)->type() == GroupWidget) {
QList subchildren = children.at(i)->childItems();
for (int j = 0; j < subchildren.count(); ++j)
collidingItems.removeAll(subchildren.at(j));
}
collidingItems.removeAll(children.at(i));
}
for (int i = 0; i < collidingItems.count(); ++i) {
if (!collidingItems.at(i)->isEnabled()) continue;
if (collidingItems.at(i)->type() == AVWidget) {
collision = true;
break;
}
}
if (!collision) {
// Check transitions
shape = m_selectionGroup->transitionGroupShape(QPointF(snappedPos - m_selectionGroup->sceneBoundingRect().left(), 0));
collidingItems = scene()->items(shape, Qt::IntersectsItemShape);
collidingItems.removeAll(m_selectionGroup);
for (int i = 0; i < children.count(); ++i) {
if (children.at(i)->type() == GroupWidget) {
QList subchildren = children.at(i)->childItems();
for (int j = 0; j < subchildren.count(); ++j)
collidingItems.removeAll(subchildren.at(j));
}
collidingItems.removeAll(children.at(i));
}
offset = 0;
for (int i = 0; i < collidingItems.count(); ++i) {
if (collidingItems.at(i)->type() == TransitionWidget && snappedPos < m_selectionGroup->sceneBoundingRect().left()) {
AbstractClipItem *item = static_cast (collidingItems.at(i));
// Moving backward, determine best pos
QPainterPath clipPath;
clipPath.addRect(item->sceneBoundingRect());
QPainterPath res = shape.intersected(clipPath);
offset = qMax(offset, (int)(res.boundingRect().width() + 0.5));
}
}
snappedPos += offset;
// make sure we have no collision
shape = m_selectionGroup->transitionGroupShape(QPointF(snappedPos - m_selectionGroup->sceneBoundingRect().left(), 0));
collidingItems = scene()->items(shape, Qt::IntersectsItemShape);
collidingItems.removeAll(m_selectionGroup);
for (int i = 0; i < children.count(); ++i) {
if (children.at(i)->type() == GroupWidget) {
QList subchildren = children.at(i)->childItems();
for (int j = 0; j < subchildren.count(); ++j)
collidingItems.removeAll(subchildren.at(j));
}
collidingItems.removeAll(children.at(i));
}
for (int i = 0; i < collidingItems.count(); ++i) {
if (collidingItems.at(i)->type() == TransitionWidget) {
collision = true;
break;
}
}
}
if (!collision)
m_selectionGroup->setTransform(QTransform::fromTranslate(snappedPos - m_selectionGroup->sceneBoundingRect().left(), 0), true);
}
// virtual
void CustomTrackView::mouseMoveEvent(QMouseEvent * event)
{
int pos = event->x();
int mappedXPos = qMax((int)(mapToScene(event->pos()).x()), 0);
double snappedPos = getSnapPointForPos(mappedXPos);
emit mousePosition(mappedXPos);
if (m_cutLine) {
m_cutLine->setPos(mappedXPos, getPositionFromTrack(getTrackFromPos(mapToScene(event->pos()).y())));
}
if (m_moveOpMode == Seek && event->buttons() != Qt::NoButton) {
QGraphicsView::mouseMoveEvent(event);
if (mappedXPos != m_document->renderer()->getCurrentSeekPosition() && mappedXPos != cursorPos()) {
seekCursorPos(mappedXPos);
slotCheckPositionScrolling();
}
return;
}
if (m_moveOpMode == ScrollTimeline) {
QGraphicsView::mouseMoveEvent(event);
return;
}
if (event->buttons() & Qt::MidButton) return;
if (m_moveOpMode == RubberSelection) {
QGraphicsView::mouseMoveEvent(event);
return;
}
if (m_moveOpMode == WaitingForConfirm && event->buttons() != Qt::NoButton) {
bool move = (event->pos() - m_clickEvent).manhattanLength() >= QApplication::startDragDistance();
if (move) {
m_moveOpMode = m_operationMode;
}
}
if (m_moveOpMode != None && m_moveOpMode != WaitingForConfirm && event->buttons() != Qt::NoButton) {
if (m_dragItem && m_operationMode != ZoomTimeline) m_clipDrag = true;
if (m_dragItem && m_tool == SelectTool) {
if (m_moveOpMode == MoveOperation && m_clipDrag) {
QGraphicsView::mouseMoveEvent(event);
// If mouse is at a border of the view, scroll
if (pos < 5) {
m_scrollOffset = -30;
m_scrollTimer.start();
} else if (viewport()->width() - pos < 10) {
m_scrollOffset = 30;
m_scrollTimer.start();
} else if (m_scrollTimer.isActive()) {
m_scrollTimer.stop();
}
} else if (m_moveOpMode == ResizeStart) {
m_document->renderer()->switchPlay(false);
if (!m_controlModifier && m_dragItem->type() == AVWidget && m_dragItem->parentItem() && m_dragItem->parentItem() != m_selectionGroup) {
AbstractGroupItem *parent = static_cast (m_dragItem->parentItem());
if (parent)
parent->resizeStart((int)(snappedPos - m_dragItemInfo.startPos.frames(m_document->fps())));
} else {
m_dragItem->resizeStart((int)(snappedPos), true, false);
}
QString crop = m_document->timecode().getDisplayTimecode(m_dragItem->cropStart(), KdenliveSettings::frametimecode());
QString duration = m_document->timecode().getDisplayTimecode(m_dragItem->cropDuration(), KdenliveSettings::frametimecode());
QString offset = m_document->timecode().getDisplayTimecode(m_dragItem->cropStart() - m_dragItemInfo.cropStart, KdenliveSettings::frametimecode());
emit displayMessage(i18n("Crop from start:") + ' ' + crop + ' ' + i18n("Duration:") + ' ' + duration + ' ' + i18n("Offset:") + ' ' + offset, InformationMessage);
} else if (m_moveOpMode == ResizeEnd) {
m_document->renderer()->switchPlay(false);
if (!m_controlModifier && m_dragItem->type() == AVWidget && m_dragItem->parentItem() && m_dragItem->parentItem() != m_selectionGroup) {
AbstractGroupItem *parent = static_cast (m_dragItem->parentItem());
if (parent) {
parent->resizeEnd((int)(snappedPos - m_dragItemInfo.endPos.frames(m_document->fps())));
}
} else {
m_dragItem->resizeEnd((int)(snappedPos), false);
}
QString duration = m_document->timecode().getDisplayTimecode(m_dragItem->cropDuration(), KdenliveSettings::frametimecode());
QString offset = m_document->timecode().getDisplayTimecode(m_dragItem->cropDuration() - m_dragItemInfo.cropDuration, KdenliveSettings::frametimecode());
emit displayMessage(i18n("Duration:") + ' ' + duration + ' ' + i18n("Offset:") + ' ' + offset, InformationMessage);
} else if (m_moveOpMode == FadeIn) {
static_cast(m_dragItem)->setFadeIn(static_cast(mappedXPos - m_dragItem->startPos().frames(m_document->fps())));
} else if (m_moveOpMode == FadeOut) {
static_cast(m_dragItem)->setFadeOut(static_cast(m_dragItem->endPos().frames(m_document->fps()) - mappedXPos));
} else if (m_moveOpMode == KeyFrame) {
GenTime keyFramePos = GenTime(mappedXPos, m_document->fps()) - m_dragItem->startPos();
double value = m_dragItem->mapFromScene(mapToScene(event->pos()).toPoint()).y();
m_dragItem->updateKeyFramePos(keyFramePos.frames(fps()), value);
QString position = m_document->timecode().getDisplayTimecodeFromFrames(m_dragItem->selectedKeyFramePos(), KdenliveSettings::frametimecode());
emit displayMessage(position + " : " + QString::number(m_dragItem->editedKeyFrameValue()), InformationMessage);
}
removeTipAnimation();
event->accept();
return;
} else if (m_moveOpMode == MoveGuide) {
removeTipAnimation();
QGraphicsView::mouseMoveEvent(event);
return;
} else if (m_moveOpMode == Spacer && m_selectionGroup) {
// spacer tool
snappedPos = getSnapPointForPos(mappedXPos + m_spacerOffset);
if (snappedPos < 0) snappedPos = 0;
spaceToolMoveToSnapPos(snappedPos);
}
}
if (m_tool == SpacerTool) {
setCursor(m_spacerCursor);
event->accept();
return;
}
QList itemList = items(event->pos());
QGraphicsRectItem *item = NULL;
bool abort = false;
GuideManager::checkOperation(itemList, this, event, m_operationMode, abort);
if (abort) {
return;
}
if (!abort) {
for (int i = 0; i < itemList.count(); ++i) {
if (itemList.at(i)->type() == AVWidget || itemList.at(i)->type() == TransitionWidget) {
item = (QGraphicsRectItem*) itemList.at(i);
break;
}
}
}
switch (m_tool) {
case RazorTool:
setCursor(m_razorCursor);
RazorManager::checkOperation(item, this, event, mappedXPos, m_operationMode, abort);
break;
case SelectTool:
default:
SelectManager::checkOperation(item, this, event, m_selectionGroup, m_operationMode, m_moveOpMode);
break;
}
}
QString CustomTrackView::getDisplayTimecode(const GenTime &time) const
{
return m_document->timecode().getDisplayTimecode(time, KdenliveSettings::frametimecode());
}
QString CustomTrackView::getDisplayTimecodeFromFrames(int frames) const
{
return m_document->timecode().getDisplayTimecodeFromFrames(frames, KdenliveSettings::frametimecode());
}
void CustomTrackView::graphicsViewMouseEvent(QMouseEvent * event)
{
QGraphicsView::mouseMoveEvent(event);
}
void CustomTrackView::createRectangleSelection(QMouseEvent * event)
{
setDragMode(QGraphicsView::RubberBandDrag);
setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
if (!(event->modifiers() & Qt::ControlModifier)) {
resetSelectionGroup();
if (m_dragItem) {
emit clipItemSelected(NULL);
m_dragItem->setMainSelectedClip(false);
m_dragItem = NULL;
}
scene()->clearSelection();
}
m_moveOpMode = RubberSelection;
QGraphicsView::mousePressEvent(event);
}
QList CustomTrackView::selectAllItemsToTheRight(int x)
{
return items(x, 1, mapFromScene(sceneRect().width(), 0).x() - x, sceneRect().height());
}
int CustomTrackView::spaceToolSelectTrackOnly(int track, QList &selection)
{
if (m_timeline->getTrackInfo(track).isLocked) {
// Cannot use spacer on locked track
emit displayMessage(i18n("Cannot use spacer in a locked track"), ErrorMessage);
return -1;
}
QRectF rect(mapToScene(m_clickEvent).x(), getPositionFromTrack(track) + m_tracksHeight / 2, sceneRect().width() - mapToScene(m_clickEvent).x(), m_tracksHeight / 2 - 2);
bool isOk;
selection = checkForGroups(rect, &isOk);
if (!isOk) {
// groups found on track, do not allow the move
emit displayMessage(i18n("Cannot use spacer in a track with a group"), ErrorMessage);
return -1;
}
//qDebug() << "SPACER TOOL + CTRL, SELECTING ALL CLIPS ON TRACK " << track << " WITH SELECTION RECT " << m_clickEvent.x() << '/' << track * m_tracksHeight + 1 << "; " << mapFromScene(sceneRect().width(), 0).x() - m_clickEvent.x() << '/' << m_tracksHeight - 2;
return 0;
}
void CustomTrackView::createGroupForSelectedItems(QList &selection)
{
QList offsetList;
// create group to hold selected items
m_selectionMutex.lock();
m_selectionGroup = new AbstractGroupItem(m_document->fps());
scene()->addItem(m_selectionGroup);
for (int i = 0; i < selection.count(); ++i) {
if (selection.at(i)->parentItem() == 0 && (selection.at(i)->type() == AVWidget || selection.at(i)->type() == TransitionWidget)) {
AbstractClipItem *item = static_cast(selection.at(i));
if (item->isItemLocked()) continue;
offsetList.append(item->startPos());
offsetList.append(item->endPos());
m_selectionGroup->addItem(selection.at(i));
} else if (selection.at(i)->type() == GroupWidget) {
if (static_cast(selection.at(i))->isItemLocked()) continue;
QList children = selection.at(i)->childItems();
for (int j = 0; j < children.count(); ++j) {
AbstractClipItem *item = static_cast(children.at(j));
offsetList.append(item->startPos());
offsetList.append(item->endPos());
}
m_selectionGroup->addItem(selection.at(i));
} else if (selection.at(i)->parentItem() && !selection.contains(selection.at(i)->parentItem())) {
if (static_cast(selection.at(i)->parentItem())->isItemLocked()) continue;
m_selectionGroup->addItem(selection.at(i)->parentItem());
}
}
m_spacerOffset = m_selectionGroup->sceneBoundingRect().left() - (int)(mapToScene(m_clickEvent).x());
m_selectionMutex.unlock();
if (!offsetList.isEmpty()) {
qSort(offsetList);
QList cleandOffsetList;
GenTime startOffset = offsetList.takeFirst();
for (int k = 0; k < offsetList.size(); ++k) {
GenTime newoffset = offsetList.at(k) - startOffset;
if (newoffset != GenTime() && !cleandOffsetList.contains(newoffset)) {
cleandOffsetList.append(newoffset);
}
}
updateSnapPoints(NULL, cleandOffsetList, true);
}
}
void CustomTrackView::spaceToolSelect(QMouseEvent * event)
{
QList selection;
if (event->modifiers() == Qt::ControlModifier) {
// Ctrl + click, select all items on track after click position
int track = getTrackFromPos(mapToScene(m_clickEvent).y());
if (spaceToolSelectTrackOnly(track, selection))
return;
} else {
// Select all items on all tracks after click position
selection = selectAllItemsToTheRight(event->pos().x());
//qDebug() << "SELELCTING ELEMENTS WITHIN =" << event->pos().x() << '/' << 1 << ", " << mapFromScene(sceneRect().width(), 0).x() - event->pos().x() << '/' << sceneRect().height();
}
createGroupForSelectedItems(selection);
m_operationMode = Spacer;
}
void CustomTrackView::selectItemsRightOfFrame(int frame)
{
QList selection = selectAllItemsToTheRight(mapFromScene(frame, 1).x());
createGroupForSelectedItems(selection);
}
void CustomTrackView::updateTimelineSelection()
{
if (m_dragItem) {
m_dragItem->setZValue(99);
if (m_dragItem->parentItem()) m_dragItem->parentItem()->setZValue(99);
// clip selected, update effect stack
if (m_dragItem->type() == AVWidget && !m_dragItem->isItemLocked()) {
ClipItem *selected = static_cast (m_dragItem);
emit clipItemSelected(selected, false);
} else {
emit clipItemSelected(NULL);
}
if (m_dragItem->type() == TransitionWidget && m_dragItem->isEnabled()) {
// update transition menu action
m_autoTransition->setChecked(static_cast(m_dragItem)->isAutomatic());
m_autoTransition->setEnabled(true);
// A transition is selected
QPoint p;
ClipItem *transitionClip = getClipItemAtStart(m_dragItemInfo.startPos, m_dragItemInfo.track);
if (transitionClip && transitionClip->binClip()) {
int frameWidth = transitionClip->binClip()->getProducerIntProperty(QStringLiteral("meta.media.width"));
int frameHeight = transitionClip->binClip()->getProducerIntProperty(QStringLiteral("meta.media.height"));
double factor = transitionClip->binClip()->getProducerProperty(QStringLiteral("aspect_ratio")).toDouble();
if (factor == 0) factor = 1.0;
p.setX((int)(frameWidth * factor + 0.5));
p.setY(frameHeight);
}
emit transitionItemSelected(static_cast (m_dragItem), getPreviousVideoTrack(m_dragItem->track()), p);
} else {
emit transitionItemSelected(NULL);
m_autoTransition->setEnabled(false);
}
} else {
emit clipItemSelected(NULL);
emit transitionItemSelected(NULL);
m_autoTransition->setEnabled(false);
}
}
// virtual
void CustomTrackView::mousePressEvent(QMouseEvent * event)
{
setFocus(Qt::MouseFocusReason);
m_menuPosition = QPoint();
if (m_moveOpMode == MoveOperation) {
// click while dragging, ignore
event->ignore();
return;
}
m_moveOpMode = WaitingForConfirm;
m_clipDrag = false;
// special cases (middle click button or ctrl / shift click)
if (event->button() == Qt::MidButton) {
if (m_operationMode == KeyFrame) {
if (m_dragItem->type() == AVWidget) {
ClipItem *item = static_cast(m_dragItem);
m_dragItem->insertKeyframe(item->getEffectAtIndex(item->selectedEffectIndex()), m_dragItem->selectedKeyFramePos(), -1, true);
m_dragItem->update();
}
} else {
emit playMonitor();
m_operationMode = None;
}
return;
}
if (event->button() == Qt::LeftButton) {
if (event->modifiers() & Qt::ShiftModifier && m_tool == SelectTool) {
createRectangleSelection(event);
return;
}
if (m_tool != RazorTool) activateMonitor();
else if (m_document->renderer()->isPlaying()) {
m_document->renderer()->switchPlay(false);
return;
}
}
m_dragGuide = NULL;
m_clickEvent = event->pos();
// check item under mouse
QList collisionList = items(m_clickEvent);
if (event->button() == Qt::LeftButton && event->modifiers() == Qt::ControlModifier && m_tool != SpacerTool && collisionList.count() == 0) {
// Pressing Ctrl + left mouse button in an empty area scrolls the timeline
setDragMode(QGraphicsView::ScrollHandDrag);
m_moveOpMode = ScrollTimeline;
QGraphicsView::mousePressEvent(event);
return;
}
// if a guide and a clip were pressed, just select the guide
for (int i = 0; i < collisionList.count(); ++i) {
if (collisionList.at(i)->type() == GUIDEITEM) {
// a guide item was pressed
m_dragGuide = static_cast(collisionList.at(i));
if (event->button() == Qt::LeftButton) { // move it
m_dragGuide->setFlag(QGraphicsItem::ItemIsMovable, true);
m_operationMode = MoveGuide;
// deselect all clips so that only the guide will move
m_scene->clearSelection();
resetSelectionGroup(false);
updateSnapPoints(NULL);
QGraphicsView::mousePressEvent(event);
return;
} else // show context menu
break;
}
}
// Find first clip, transition or group under mouse (when no guides selected)
int ct = 0;
AbstractGroupItem *dragGroup = NULL;
AbstractClipItem *collisionClip = NULL;
bool found = false;
QList lockedTracks;
double yOffset = 0;
m_selectionMutex.lock();
while (!m_dragGuide && ct < collisionList.count()) {
if (collisionList.at(ct)->type() == AVWidget || collisionList.at(ct)->type() == TransitionWidget) {
collisionClip = static_cast (collisionList.at(ct));
if (collisionClip->isItemLocked() || !collisionClip->isEnabled()) {
ct++;
continue;
}
if (collisionClip == m_dragItem) {
collisionClip = NULL;
}
else {
if (m_dragItem) {
m_dragItem->setMainSelectedClip(false);
}
m_dragItem = collisionClip;
m_dragItem->setMainSelectedClip(true);
}
found = true;
bool allowAudioOnly = false;
if (KdenliveSettings::splitaudio() && m_dragItem->type() == AVWidget) {
ClipItem *clp = static_cast(m_dragItem);
if (clp) {
if (clp->clipType() == Audio || clp->clipState() == PlaylistState::AudioOnly) {
allowAudioOnly = true;
}
}
}
for (int i = 1; i < m_timeline->tracksCount(); ++i) {
TrackInfo nfo = m_timeline->getTrackInfo(i);
if (nfo.isLocked || (allowAudioOnly && nfo.type == VideoTrack))
lockedTracks << i;
}
yOffset = mapToScene(m_clickEvent).y() - m_dragItem->scenePos().y();
m_dragItem->setProperty("y_absolute", yOffset);
m_dragItem->setProperty("locked_tracks", QVariant::fromValue(lockedTracks));
m_dragItemInfo = m_dragItem->info();
if (m_selectionGroup) {
m_selectionGroup->setProperty("y_absolute", yOffset);
m_selectionGroup->setProperty("locked_tracks", QVariant::fromValue(lockedTracks));
}
if (m_dragItem->parentItem() && m_dragItem->parentItem()->type() == GroupWidget && m_dragItem->parentItem() != m_selectionGroup) {
QGraphicsItem *topGroup = m_dragItem->parentItem();
while (topGroup->parentItem() && topGroup->parentItem()->type() == GroupWidget && topGroup->parentItem() != m_selectionGroup) {
topGroup = topGroup->parentItem();
}
dragGroup = static_cast (topGroup);
dragGroup->setProperty("y_absolute", yOffset);
dragGroup->setProperty("locked_tracks", QVariant::fromValue(lockedTracks));
}
break;
}
ct++;
}
m_selectionMutex.unlock();
if (!found) {
if (m_dragItem)
m_dragItem->setMainSelectedClip(false);
m_dragItem = NULL;
}
// Add shadow to dragged item, currently disabled because of painting artifacts
/*if (m_dragItem) {
QGraphicsDropShadowEffect *eff = new QGraphicsDropShadowEffect();
eff->setBlurRadius(5);
eff->setOffset(3, 3);
m_dragItem->setGraphicsEffect(eff);
}*/
// No item under click
if (m_dragItem == NULL && m_tool != SpacerTool) {
resetSelectionGroup(false);
m_scene->clearSelection();
updateClipTypeActions(NULL);
updateTimelineSelection();
if (event->button() == Qt::LeftButton) {
m_moveOpMode = Seek;
setCursor(Qt::ArrowCursor);
seekCursorPos((int)(mapToScene(event->x(), 0).x()));
event->setAccepted(true);
QGraphicsView::mousePressEvent(event);
return;
}
}
// context menu requested
if (event->button() == Qt::RightButton) {
// Check if we want keyframes context menu
if (!m_dragItem && !m_dragGuide) {
// check if there is a guide close to mouse click
QList guidesCollisionList = items(event->pos().x() - 5, event->pos().y(), 10, 2); // a rect of height < 2 does not always collide with the guide
for (int i = 0; i < guidesCollisionList.count(); ++i) {
if (guidesCollisionList.at(i)->type() == GUIDEITEM) {
m_dragGuide = static_cast (guidesCollisionList.at(i));
break;
}
}
// keep this to support multiple guides context menu in the future (?)
/*if (guidesCollisionList.at(0)->type() != GUIDEITEM)
guidesCollisionList.removeAt(0);
}
if (!guidesCollisionList.isEmpty())
m_dragGuide = static_cast (guidesCollisionList.at(0));*/
}
m_menuPosition = m_clickEvent;
/* if (dragGroup == NULL) {
if (m_dragItem && m_dragItem->parentItem() && m_dragItem->parentItem() != m_selectionGroup)
dragGroup = static_cast (m_dragItem->parentItem());
}
*/
if (m_dragItem) {
if (!m_dragItem->isSelected()) {
resetSelectionGroup();
m_scene->clearSelection();
m_dragItem->setSelected(true);
}
m_dragItem->setZValue(99);
if (m_dragItem->parentItem()) m_dragItem->parentItem()->setZValue(99);
}
event->accept();
updateTimelineSelection();
return;
}
if (event->button() == Qt::LeftButton) {
if (m_tool == SpacerTool) {
resetSelectionGroup(false);
m_scene->clearSelection();
updateClipTypeActions(NULL);
spaceToolSelect(event);
QGraphicsView::mousePressEvent(event);
return;
}
// Razor tool
if (m_tool == RazorTool) {
if (!m_dragItem) {
// clicked in empty area, ignore
event->accept();
return;
}
GenTime cutPos = GenTime((int)(mapToScene(event->pos()).x()), m_document->fps());
if (m_dragItem->type() == TransitionWidget) {
emit displayMessage(i18n("Cannot cut a transition"), ErrorMessage);
} else {
m_document->renderer()->switchPlay(false);
if (m_dragItem->parentItem() && m_dragItem->parentItem() != m_selectionGroup) {
razorGroup(static_cast(m_dragItem->parentItem()), cutPos);
} else {
ClipItem *clip = static_cast (m_dragItem);
if (cutPos > clip->startPos() && cutPos < clip->endPos()) {
RazorClipCommand* command = new RazorClipCommand(this, clip->info(), clip->effectList(), cutPos);
m_commandStack->push(command);
}
}
}
m_dragItem->setMainSelectedClip(false);
m_dragItem = NULL;
event->accept();
return;
}
}
if (m_dragItem) {
bool itemSelected = false;
bool selected = true;
if (m_dragItem->isSelected()) {
itemSelected = true;
selected = false;
} else if (m_dragItem->parentItem() && m_dragItem->parentItem()->isSelected()) {
itemSelected = true;
} else if (dragGroup && dragGroup->isSelected()) {
itemSelected = true;
}
QGraphicsView::mousePressEvent(event);
if (event->modifiers() & Qt::ControlModifier) {
// Handle ctrl click events // Handle ctrl click events
resetSelectionGroup();
m_dragItem->setSelected(selected);
groupSelectedItems(QList (), false, true);
if (selected) {
m_selectionMutex.lock();
if (m_selectionGroup) {
m_selectionGroup->setProperty("y_absolute", yOffset);
m_selectionGroup->setProperty("locked_tracks", QVariant::fromValue(lockedTracks));
}
m_selectionMutex.unlock();
}
else {
m_dragItem->setMainSelectedClip(false);
m_dragItem = NULL;
}
updateTimelineSelection();
return;
resetSelectionGroup();
m_dragItem->setSelected(selected);
groupSelectedItems(QList (), false, true);
if (selected) {
m_selectionMutex.lock();
if (m_selectionGroup) {
m_selectionGroup->setProperty("y_absolute", yOffset);
m_selectionGroup->setProperty("locked_tracks", QVariant::fromValue(lockedTracks));
}
m_selectionMutex.unlock();
}
else {
m_dragItem->setMainSelectedClip(false);
m_dragItem = NULL;
}
updateTimelineSelection();
return;
}
if (itemSelected == false) {
// User clicked a non selected item, select it
resetSelectionGroup(false);
m_scene->clearSelection();
m_dragItem->setSelected(true);
m_dragItem->setZValue(99);
if (m_dragItem->parentItem()) m_dragItem->parentItem()->setZValue(99);
if (m_dragItem && m_dragItem->type() == AVWidget) {
ClipItem *clip = static_cast(m_dragItem);
updateClipTypeActions(dragGroup == NULL ? clip : NULL);
m_pasteEffectsAction->setEnabled(m_copiedItems.count() == 1);
}
else updateClipTypeActions(NULL);
}
else {
m_selectionMutex.lock();
if (m_selectionGroup) {
QList children = m_selectionGroup->childItems();
for (int i = 0; i < children.count(); ++i) {
children.at(i)->setSelected(itemSelected);
}
m_selectionGroup->setSelected(itemSelected);
}
if (dragGroup) {
dragGroup->setSelected(itemSelected);
}
m_dragItem->setSelected(itemSelected);
m_selectionMutex.unlock();
}
m_selectionMutex.lock();
if (m_selectionGroup) {
m_selectionGroup->setProperty("y_absolute", yOffset);
m_selectionGroup->setProperty("locked_tracks", QVariant::fromValue(lockedTracks));
}
m_selectionMutex.unlock();
updateTimelineSelection();
}
if (event->button() == Qt::LeftButton) {
if (m_dragItem) {
if (m_selectionGroup && m_dragItem->parentItem() == m_selectionGroup) {
// all other modes break the selection, so the user probably wants to move it
m_operationMode = MoveOperation;
} else {
if (m_dragItem->rect().width() * transform().m11() < 15) {
// If the item is very small, only allow move
m_operationMode = MoveOperation;
}
else m_operationMode = m_dragItem->operationMode(m_dragItem->mapFromScene(mapToScene(event->pos())));
if (m_operationMode == ResizeEnd) {
// FIXME: find a better way to avoid move in ClipItem::itemChange?
m_dragItem->setProperty("resizingEnd", true);
}
}
}
else m_operationMode = None;
}
m_controlModifier = (event->modifiers() == Qt::ControlModifier);
// Update snap points
if (m_selectionGroup == NULL) {
if (m_operationMode == ResizeEnd || m_operationMode == ResizeStart)
updateSnapPoints(NULL);
else
updateSnapPoints(m_dragItem);
} else {
m_selectionMutex.lock();
QList offsetList;
QList children = m_selectionGroup->childItems();
for (int i = 0; i < children.count(); ++i) {
if (children.at(i)->type() == AVWidget || children.at(i)->type() == TransitionWidget) {
AbstractClipItem *item = static_cast (children.at(i));
offsetList.append(item->startPos());
offsetList.append(item->endPos());
}
}
if (!offsetList.isEmpty()) {
qSort(offsetList);
GenTime startOffset = offsetList.takeFirst();
QList cleandOffsetList;
for (int k = 0; k < offsetList.size(); ++k) {
GenTime newoffset = offsetList.at(k) - startOffset;
if (newoffset != GenTime() && !cleandOffsetList.contains(newoffset)) {
cleandOffsetList.append(newoffset);
}
}
updateSnapPoints(NULL, cleandOffsetList, true);
}
m_selectionMutex.unlock();
}
if (m_operationMode == KeyFrame) {
m_dragItem->prepareKeyframeMove();
return;
} else if (m_operationMode == MoveOperation) {
setCursor(Qt::ClosedHandCursor);
} else if (m_operationMode == TransitionStart && event->modifiers() != Qt::ControlModifier) {
ItemInfo info;
info.startPos = m_dragItem->startPos();
info.track = m_dragItem->track();
int transitiontrack = getPreviousVideoTrack(info.track);
ClipItem *transitionClip = NULL;
if (transitiontrack != 0) transitionClip = getClipItemAtMiddlePoint(info.startPos.frames(m_document->fps()), transitiontrack);
if (transitionClip && transitionClip->endPos() < m_dragItem->endPos()) {
info.endPos = transitionClip->endPos();
} else {
GenTime transitionDuration(65, m_document->fps());
if (m_dragItem->cropDuration() < transitionDuration)
info.endPos = m_dragItem->endPos();
else
info.endPos = info.startPos + transitionDuration;
}
if (info.endPos == info.startPos) info.endPos = info.startPos + GenTime(65, m_document->fps());
// Check there is no other transition at that place
double startY = getPositionFromTrack(info.track) + 1 + m_tracksHeight / 2;
QRectF r(info.startPos.frames(m_document->fps()), startY, (info.endPos - info.startPos).frames(m_document->fps()), m_tracksHeight / 2);
QList selection = m_scene->items(r);
bool transitionAccepted = true;
for (int i = 0; i < selection.count(); ++i) {
if (selection.at(i)->type() == TransitionWidget) {
Transition *tr = static_cast (selection.at(i));
if (tr->startPos() - info.startPos > GenTime(5, m_document->fps())) {
if (tr->startPos() < info.endPos) info.endPos = tr->startPos();
} else transitionAccepted = false;
}
}
if (transitionAccepted) slotAddTransition(static_cast(m_dragItem), info, transitiontrack);
else emit displayMessage(i18n("Cannot add transition"), ErrorMessage);
} else if (m_operationMode == TransitionEnd && event->modifiers() != Qt::ControlModifier) {
ItemInfo info;
info.endPos = GenTime(m_dragItem->endPos().frames(m_document->fps()), m_document->fps());
info.track = m_dragItem->track();
int transitiontrack = getPreviousVideoTrack(info.track);
ClipItem *transitionClip = NULL;
if (transitiontrack != 0) transitionClip = getClipItemAtMiddlePoint(info.endPos.frames(m_document->fps()), transitiontrack);
if (transitionClip && transitionClip->startPos() > m_dragItem->startPos()) {
info.startPos = transitionClip->startPos();
} else {
GenTime transitionDuration(65, m_document->fps());
if (m_dragItem->cropDuration() < transitionDuration)
info.startPos = m_dragItem->startPos();
else
info.startPos = info.endPos - transitionDuration;
}
if (info.endPos == info.startPos) info.startPos = info.endPos - GenTime(65, m_document->fps());
QDomElement transition = MainWindow::transitions.getEffectByTag(QStringLiteral("luma"), QStringLiteral("dissolve")).cloneNode().toElement();
EffectsList::setParameter(transition, QStringLiteral("reverse"), QStringLiteral("1"));
// Check there is no other transition at that place
double startY = getPositionFromTrack(info.track) + 1 + m_tracksHeight / 2;
QRectF r(info.startPos.frames(m_document->fps()), startY, (info.endPos - info.startPos).frames(m_document->fps()), m_tracksHeight / 2);
QList selection = m_scene->items(r);
bool transitionAccepted = true;
for (int i = 0; i < selection.count(); ++i) {
if (selection.at(i)->type() == TransitionWidget) {
Transition *tr = static_cast (selection.at(i));
if (info.endPos - tr->endPos() > GenTime(5, m_document->fps())) {
if (tr->endPos() > info.startPos) info.startPos = tr->endPos();
} else transitionAccepted = false;
}
}
if (transitionAccepted) slotAddTransition(static_cast(m_dragItem), info, transitiontrack, transition);
else emit displayMessage(i18n("Cannot add transition"), ErrorMessage);
} else if ((m_operationMode == ResizeStart || m_operationMode == ResizeEnd) && m_selectionGroup) {
resetSelectionGroup(false);
m_dragItem->setSelected(true);
}
}
void CustomTrackView::rebuildGroup(int childTrack, const GenTime &childPos)
{
const QPointF p((int)childPos.frames(m_document->fps()), getPositionFromTrack(childTrack) + m_tracksHeight / 2);
QList list = scene()->items(p);
AbstractGroupItem *group = NULL;
for (int i = 0; i < list.size(); ++i) {
if (!list.at(i)->isEnabled()) continue;
if (list.at(i)->type() == GroupWidget) {
group = static_cast (list.at(i));
break;
}
}
rebuildGroup(group);
}
void CustomTrackView::rebuildGroup(AbstractGroupItem *group)
{
if (group) {
m_selectionMutex.lock();
if (group == m_selectionGroup) m_selectionGroup = NULL;
QList children = group->childItems();
m_document->clipManager()->removeGroup(group);
for (int i = 0; i < children.count(); ++i) {
group->removeFromGroup(children.at(i));
}
scene()->destroyItemGroup(group);
m_selectionMutex.unlock();
groupSelectedItems(children, group != m_selectionGroup, true);
}
}
void CustomTrackView::resetSelectionGroup(bool selectItems)
{
QMutexLocker lock(&m_selectionMutex);
if (m_selectionGroup) {
// delete selection group
bool snap = KdenliveSettings::snaptopoints();
KdenliveSettings::setSnaptopoints(false);
QList children = m_selectionGroup->childItems();
scene()->destroyItemGroup(m_selectionGroup);
m_selectionGroup = NULL;
for (int i = 0; i < children.count(); ++i) {
if (children.at(i)->parentItem() == 0) {
if ((children.at(i)->type() == AVWidget || children.at(i)->type() == TransitionWidget)) {
if (!static_cast (children.at(i))->isItemLocked()) {
children.at(i)->setFlag(QGraphicsItem::ItemIsMovable, true);
children.at(i)->setSelected(selectItems);
}
} else if (children.at(i)->type() == GroupWidget) {
children.at(i)->setFlag(QGraphicsItem::ItemIsMovable, true);
children.at(i)->setSelected(selectItems);
}
}
}
KdenliveSettings::setSnaptopoints(snap);
}
}
void CustomTrackView::groupSelectedItems(QList selection, bool createNewGroup, bool selectNewGroup)
{
QMutexLocker lock(&m_selectionMutex);
if (m_selectionGroup) {
qDebug() << "///// ERROR, TRYING TO OVERRIDE EXISTING GROUP";
return;
}
if (selection.isEmpty()) selection = m_scene->selectedItems();
// Split groups and items
QSet groupsList;
QSet itemsList;
for (int i = 0; i < selection.count(); ++i) {
if (selectNewGroup) selection.at(i)->setSelected(true);
if (selection.at(i)->type() == GroupWidget) {
AbstractGroupItem *it = static_cast (selection.at(i));
while (it->parentItem() && it->parentItem()->type() == GroupWidget) {
it = static_cast (it->parentItem());
}
if (!it || it->isItemLocked()) continue;
groupsList.insert(it);
}
}
bool lockGroup = false;
for (int i = 0; i < selection.count(); ++i) {
if (selection.at(i)->type() == AVWidget || selection.at(i)->type() == TransitionWidget) {
if (selection.at(i)->parentItem() && selection.at(i)->parentItem()->type() == GroupWidget) {
AbstractGroupItem *it = static_cast (selection.at(i)->parentItem());
while (it->parentItem() && it->parentItem()->type() == GroupWidget) {
it = static_cast (it->parentItem());
}
if (!it || it->isItemLocked()) continue;
groupsList.insert(it);
}
else {
AbstractClipItem *it = static_cast (selection.at(i));
if (!it) continue;
if (it->isItemLocked()) lockGroup = true;
itemsList.insert(selection.at(i));
}
}
}
if (itemsList.isEmpty() && groupsList.isEmpty()) return;
if (itemsList.count() == 1 && groupsList.isEmpty()) {
// only one item selected:
QSetIterator it(itemsList);
m_dragItem = static_cast(it.next());
m_dragItem->setMainSelectedClip(true);
m_dragItem->setSelected(true);
return;
}
QRectF rectUnion;
// Find top left position of selection
foreach (const QGraphicsItemGroup *value, groupsList) {
rectUnion = rectUnion.united(value->sceneBoundingRect());
}
foreach (const QGraphicsItem *value, itemsList) {
rectUnion = rectUnion.united(value->sceneBoundingRect());
}
bool snap = KdenliveSettings::snaptopoints();
KdenliveSettings::setSnaptopoints(false);
if (createNewGroup) {
AbstractGroupItem *newGroup = m_document->clipManager()->createGroup();
newGroup->setPos(rectUnion.left(), rectUnion.top() - 1);
QPointF diff = newGroup->pos();
newGroup->setTransform(QTransform::fromTranslate(-diff.x(), -diff.y()), true);
//newGroup->translate((int) -rectUnion.left(), (int) -rectUnion.top() + 1);
// Check if we are trying to include a group in a group
foreach (QGraphicsItemGroup *value, groupsList) {
newGroup->addItem(value);
}
foreach (QGraphicsItemGroup *value, groupsList) {
QList children = value->childItems();
for (int i = 0; i < children.count(); ++i) {
if (children.at(i)->type() == AVWidget || children.at(i)->type() == TransitionWidget)
itemsList.insert(children.at(i));
}
AbstractGroupItem *grp = static_cast(value);
m_document->clipManager()->removeGroup(grp);
if (grp == m_selectionGroup) m_selectionGroup = NULL;
scene()->destroyItemGroup(grp);
}
foreach (QGraphicsItem *value, itemsList) {
newGroup->addItem(value);
}
if (lockGroup)
newGroup->setItemLocked(true);
scene()->addItem(newGroup);
KdenliveSettings::setSnaptopoints(snap);
if (selectNewGroup) newGroup->setSelected(true);
} else {
m_selectionGroup = new AbstractGroupItem(m_document->fps());
m_selectionGroup->setPos(rectUnion.left(), rectUnion.top() - 1);
QPointF diff = m_selectionGroup->pos();
//m_selectionGroup->translate((int) - rectUnion.left(), (int) -rectUnion.top() + 1);
m_selectionGroup->setTransform(QTransform::fromTranslate(- diff.x(), -diff.y()), true);
scene()->addItem(m_selectionGroup);
foreach (QGraphicsItemGroup *value, groupsList) {
m_selectionGroup->addItem(value);
}
foreach (QGraphicsItem *value, itemsList) {
m_selectionGroup->addItem(value);
}
KdenliveSettings::setSnaptopoints(snap);
if (m_selectionGroup) {
m_selectionGroupInfo.startPos = GenTime(m_selectionGroup->scenePos().x(), m_document->fps());
m_selectionGroupInfo.track = m_selectionGroup->track();
if (selectNewGroup) m_selectionGroup->setSelected(true);
}
}
}
void CustomTrackView::mouseDoubleClickEvent(QMouseEvent *event)
{
if (m_dragItem && m_dragItem->keyframesCount() > 0) {
// add keyframe
GenTime keyFramePos = GenTime((int)(mapToScene(event->pos()).x()), m_document->fps()) - m_dragItem->startPos();// + m_dragItem->cropStart();
int single = m_dragItem->keyframesCount();
double val = m_dragItem->getKeyFrameClipHeight(mapToScene(event->pos()).y() - m_dragItem->scenePos().y());
ClipItem * item = static_cast (m_dragItem);
QDomElement oldEffect = item->selectedEffect().cloneNode().toElement();
if (single == 1) {
item->insertKeyframe(item->getEffectAtIndex(item->selectedEffectIndex()), (item->cropDuration()).frames(m_document->fps()) - 1, -1, true);
}
//QString previous = item->keyframes(item->selectedEffectIndex());
item->insertKeyframe(item->getEffectAtIndex(item->selectedEffectIndex()), keyFramePos.frames(m_document->fps()), val);
//QString next = item->keyframes(item->selectedEffectIndex());
QDomElement newEffect = item->selectedEffect().cloneNode().toElement();
EditEffectCommand *command = new EditEffectCommand(this, item->track(), item->startPos(), oldEffect, newEffect, item->selectedEffectIndex(), false, false);
m_commandStack->push(command);
updateEffect(item->track(), item->startPos(), item->selectedEffect());
emit clipItemSelected(item, item->selectedEffectIndex());
} else if (m_dragItem && !m_dragItem->isItemLocked()) {
editItemDuration();
} else {
QList collisionList = items(event->pos());
if (collisionList.count() == 1 && collisionList.at(0)->type() == GUIDEITEM) {
Guide *editGuide = static_cast(collisionList.at(0));
if (editGuide) slotEditGuide(editGuide->info());
}
}
}
void CustomTrackView::editItemDuration()
{
AbstractClipItem *item;
if (m_dragItem) {
item = m_dragItem;
} else {
if (m_scene->selectedItems().count() == 1) {
item = static_cast (m_scene->selectedItems().at(0));
} else {
if (m_scene->selectedItems().empty())
emit displayMessage(i18n("Cannot find clip to edit"), ErrorMessage);
else
emit displayMessage(i18n("Cannot edit the duration of multiple items"), ErrorMessage);
return;
}
}
if (!item) {
emit displayMessage(i18n("Cannot find clip to edit"), ErrorMessage);
return;
}
if (item->type() == GroupWidget || (item->parentItem() && item->parentItem()->type() == GroupWidget)) {
emit displayMessage(i18n("Cannot edit an item in a group"), ErrorMessage);
return;
}
if (!item->isItemLocked()) {
GenTime minimum;
GenTime maximum;
if (item->type() == TransitionWidget)
getTransitionAvailableSpace(item, minimum, maximum);
else
getClipAvailableSpace(item, minimum, maximum);
QPointer d = new ClipDurationDialog(item,
m_document->timecode(), minimum, maximum, this);
if (d->exec() == QDialog::Accepted) {
ItemInfo clipInfo = item->info();
ItemInfo startInfo = clipInfo;
if (item->type() == TransitionWidget) {
// move & resize transition
clipInfo.startPos = d->startPos();
clipInfo.endPos = clipInfo.startPos + d->duration();
clipInfo.track = item->track();
MoveTransitionCommand *command = new MoveTransitionCommand(this, startInfo, clipInfo, true);
updateTrackDuration(clipInfo.track, command);
m_commandStack->push(command);
} else {
// move and resize clip
ClipItem *clip = static_cast(item);
QUndoCommand *moveCommand = new QUndoCommand();
moveCommand->setText(i18n("Edit clip"));
if (d->duration() < item->cropDuration() || d->cropStart() != clipInfo.cropStart) {
// duration was reduced, so process it first
clipInfo.endPos = clipInfo.startPos + d->duration();
clipInfo.cropStart = d->cropStart();
resizeClip(startInfo, clipInfo);
// TODO: find a way to apply adjusteffect after the resize command was done / undone
new ResizeClipCommand(this, startInfo, clipInfo, false, true, moveCommand);
adjustEffects(clip, startInfo, moveCommand);
}
if (d->startPos() != clipInfo.startPos) {
startInfo = clipInfo;
clipInfo.startPos = d->startPos();
clipInfo.endPos = item->endPos() + (clipInfo.startPos - startInfo.startPos);
new MoveClipCommand(this, startInfo, clipInfo, false, true, moveCommand);
}
if (d->duration() > item->cropDuration()) {
// duration was increased, so process it after move
startInfo = clipInfo;
clipInfo.endPos = clipInfo.startPos + d->duration();
clipInfo.cropStart = d->cropStart();
resizeClip(startInfo, clipInfo);
// TODO: find a way to apply adjusteffect after the resize command was done / undone
new ResizeClipCommand(this, startInfo, clipInfo, false, true, moveCommand);
adjustEffects(clip, startInfo, moveCommand);
}
updateTrackDuration(clipInfo.track, moveCommand);
m_commandStack->push(moveCommand);
}
}
delete d;
} else {
emit displayMessage(i18n("Item is locked"), ErrorMessage);
}
}
void CustomTrackView::contextMenuEvent(QContextMenuEvent * event)
{
if (m_operationMode == KeyFrame) {
displayKeyframesMenu(event->globalPos(), m_dragItem);
} else {
displayContextMenu(event->globalPos(), m_dragItem);
}
event->accept();
}
void CustomTrackView::displayKeyframesMenu(QPoint pos, AbstractClipItem *clip)
{
if (!m_timelineContextKeyframeMenu) {
m_timelineContextKeyframeMenu = new QMenu(this);
// Keyframe type widget
m_selectKeyframeType = new KSelectAction(KoIconUtils::themedIcon(QStringLiteral("keyframes")), i18n("Interpolation"), this);
QAction *discrete = new QAction(KoIconUtils::themedIcon(QStringLiteral("discrete")), i18n("Discrete"), this);
discrete->setData((int) mlt_keyframe_discrete);
discrete->setCheckable(true);
m_selectKeyframeType->addAction(discrete);
QAction *linear = new QAction(KoIconUtils::themedIcon(QStringLiteral("linear")), i18n("Linear"), this);
linear->setData((int) mlt_keyframe_linear);
linear->setCheckable(true);
m_selectKeyframeType->addAction(linear);
QAction *curve = new QAction(KoIconUtils::themedIcon(QStringLiteral("smooth")), i18n("Smooth"), this);
curve->setData((int) mlt_keyframe_smooth);
curve->setCheckable(true);
m_selectKeyframeType->addAction(curve);
m_timelineContextKeyframeMenu->addAction(m_selectKeyframeType);
m_attachKeyframeToEnd = new QAction(i18n("Attach keyframe to end"), this);
m_attachKeyframeToEnd->setCheckable(true);
m_timelineContextKeyframeMenu->addAction(m_attachKeyframeToEnd);
connect(m_selectKeyframeType, SIGNAL(triggered(QAction*)), this, SLOT(slotEditKeyframeType(QAction*)));
connect(m_attachKeyframeToEnd, SIGNAL(triggered(bool)), this, SLOT(slotAttachKeyframeToEnd(bool)));
}
m_attachKeyframeToEnd->setChecked(clip->isAttachedToEnd());
m_selectKeyframeType->setCurrentAction(clip->parseKeyframeActions(m_selectKeyframeType->actions()));
m_timelineContextKeyframeMenu->exec(pos);
}
void CustomTrackView::slotAttachKeyframeToEnd(bool attach)
{
ClipItem * item = static_cast (m_dragItem);
QDomElement oldEffect = item->selectedEffect().cloneNode().toElement();
item->attachKeyframeToEnd(item->getEffectAtIndex(item->selectedEffectIndex()), attach);
QDomElement newEffect = item->selectedEffect().cloneNode().toElement();
EditEffectCommand *command = new EditEffectCommand(this, item->track(), item->startPos(), oldEffect, newEffect, item->selectedEffectIndex(), false, false);
m_commandStack->push(command);
updateEffect(item->track(), item->startPos(), item->selectedEffect());
emit clipItemSelected(item, item->selectedEffectIndex());
}
void CustomTrackView::slotEditKeyframeType(QAction *action)
{
int type = action->data().toInt();
ClipItem * item = static_cast (m_dragItem);
QDomElement oldEffect = item->selectedEffect().cloneNode().toElement();
item->editKeyframeType(item->getEffectAtIndex(item->selectedEffectIndex()), type);
QDomElement newEffect = item->selectedEffect().cloneNode().toElement();
EditEffectCommand *command = new EditEffectCommand(this, item->track(), item->startPos(), oldEffect, newEffect, item->selectedEffectIndex(), false, false);
m_commandStack->push(command);
updateEffect(item->track(), item->startPos(), item->selectedEffect());
emit clipItemSelected(item, item->selectedEffectIndex());
}
void CustomTrackView::displayContextMenu(QPoint pos, AbstractClipItem *clip)
{
bool isGroup = clip != NULL && clip->parentItem() && clip->parentItem()->type() == GroupWidget && clip->parentItem() != m_selectionGroup;
m_deleteGuide->setEnabled(m_dragGuide != NULL);
m_editGuide->setEnabled(m_dragGuide != NULL);
m_markerMenu->clear();
m_markerMenu->setEnabled(false);
if (clip == NULL) {
m_timelineContextMenu->popup(pos);
} else if (isGroup) {
m_pasteEffectsAction->setEnabled(m_copiedItems.count() == 1);
m_ungroupAction->setEnabled(true);
if (clip->type() == AVWidget) {
ClipItem *item = static_cast (clip);
m_disableClipAction->setChecked(item->clipState() == PlaylistState::Disabled);
} else {
m_disableClipAction->setChecked(false);
}
updateClipTypeActions(NULL);
m_timelineContextClipMenu->popup(pos);
} else {
m_ungroupAction->setEnabled(false);
if (clip->type() == AVWidget) {
ClipItem *item = static_cast (clip);
m_disableClipAction->setChecked(item->clipState() == PlaylistState::Disabled);
//build go to marker menu
ClipController *controller = m_document->getClipController(item->getBinId());
if (controller) {
QList markers = controller->commentedSnapMarkers();
int offset = (item->startPos()- item->cropStart()).frames(m_document->fps());
if (!markers.isEmpty()) {
for (int i = 0; i < markers.count(); ++i) {
int pos = (int) markers.at(i).time().frames(m_document->timecode().fps());
QString position = m_document->timecode().getTimecode(markers.at(i).time()) + ' ' + markers.at(i).comment();
QAction *go = m_markerMenu->addAction(position);
go->setData(pos + offset);
}
}
m_markerMenu->setEnabled(!m_markerMenu->isEmpty());
}
updateClipTypeActions(item);
m_pasteEffectsAction->setEnabled(m_copiedItems.count() == 1);
m_timelineContextClipMenu->popup(pos);
} else if (clip->type() == TransitionWidget) {
m_timelineContextTransitionMenu->exec(pos);
}
}
}
void CustomTrackView::activateMonitor()
{
emit activateDocumentMonitor();
}
void CustomTrackView::insertClipCut(const QString &id, int in, int out)
{
resetSelectionGroup();
ItemInfo info;
info.startPos = GenTime();
info.cropStart = GenTime(in, m_document->fps());
info.endPos = GenTime(out - in, m_document->fps());
info.cropDuration = info.endPos - info.startPos;
info.track = 0;
// Check if clip can be inserted at that position
ItemInfo pasteInfo = info;
pasteInfo.startPos = GenTime(m_cursorPos, m_document->fps());
pasteInfo.endPos = pasteInfo.startPos + info.endPos;
PlaylistState::ClipState state = PlaylistState::Original;
if (KdenliveSettings::splitaudio()) {
if (m_timeline->videoTarget > -1) {
pasteInfo.track = m_timeline->videoTarget;
if (m_timeline->audioTarget == -1)
state = PlaylistState::VideoOnly;
}
else if (m_timeline->audioTarget > -1) {
pasteInfo.track = m_timeline->audioTarget;
state = PlaylistState::AudioOnly;
} else {
emit displayMessage(i18n("Please select target track(s) to perform operation"), ErrorMessage);
return;
}
} else {
pasteInfo.track = selectedTrack();
}
if (m_timeline->getTrackInfo(pasteInfo.track).isLocked) {
emit displayMessage(i18n("Cannot perform operation on a locked track"), ErrorMessage);
return;
}
bool ok = canBePastedTo(pasteInfo, AVWidget);
if (!ok) {
// Cannot be inserted at cursor pos, insert at end of track
int duration = GenTime(m_timeline->track(pasteInfo.track)->length()).frames(m_document->fps());
pasteInfo.startPos = GenTime(duration, m_document->fps());
pasteInfo.endPos = pasteInfo.startPos + info.endPos;
ok = canBePastedTo(pasteInfo, AVWidget);
}
if (!ok) {
emit displayMessage(i18n("Cannot insert clip in timeline"), ErrorMessage);
return;
}
// Add refresh command for undo
QUndoCommand *addCommand = new QUndoCommand();
addCommand->setText(i18n("Add timeline clip"));
new RefreshMonitorCommand(this, pasteInfo, false, true, addCommand);
new AddTimelineClipCommand(this, id, pasteInfo, EffectsList(), state, true, false, addCommand);
new RefreshMonitorCommand(this, pasteInfo, true, false, addCommand);
// Automatic audio split
if (KdenliveSettings::splitaudio() && m_timeline->audioTarget > -1 && m_timeline->videoTarget > -1) {
if (!m_timeline->getTrackInfo(m_timeline->audioTarget).isLocked && m_document->getBinClip(id)->isSplittable())
splitAudio(false, pasteInfo, m_timeline->audioTarget, addCommand);
}
else
updateTrackDuration(pasteInfo.track, addCommand);
m_commandStack->push(addCommand);
selectClip(true, false);
}
bool CustomTrackView::insertDropClips(const QMimeData *data, const QPoint &pos)
{
m_clipDrag = data->hasFormat(QStringLiteral("kdenlive/clip")) || data->hasFormat(QStringLiteral("kdenlive/producerslist"));
// This is not a clip drag, maybe effect or other...
if (!m_clipDrag) return false;
m_scene->clearSelection();
if (m_dragItem)
m_dragItem->setMainSelectedClip(false);
m_dragItem = NULL;
resetSelectionGroup(false);
QPointF framePos = mapToScene(pos);
int track = getTrackFromPos(framePos.y());
QMutexLocker lock(&m_selectionMutex);
if (track <= 0 || track > m_timeline->tracksCount() - 1 || m_timeline->getTrackInfo(track).isLocked) return true;
if (data->hasFormat(QStringLiteral("kdenlive/clip"))) {
QStringList list = QString(data->data(QStringLiteral("kdenlive/clip"))).split(';');
ProjectClip *clip = m_document->getBinClip(list.at(0));
if (clip == NULL) {
//qDebug() << " WARNING))))))))) CLIP NOT FOUND : " << list.at(0);
return false;
}
if (!clip->isReady()) {
emit displayMessage(i18n("Clip not ready"), ErrorMessage);
return false;
}
ItemInfo info;
info.startPos = GenTime();
info.cropStart = GenTime(list.at(1).toInt(), m_document->fps());
info.endPos = GenTime(list.at(2).toInt() - list.at(1).toInt(), m_document->fps());
info.cropDuration = info.endPos;// - info.startPos;
info.track = 0;
// Check if clip can be inserted at that position
ItemInfo pasteInfo = info;
pasteInfo.startPos = GenTime((int)(framePos.x() + 0.5), m_document->fps());
pasteInfo.endPos = pasteInfo.startPos + info.endPos;
pasteInfo.track = track;
framePos.setX((int)(framePos.x() + 0.5));
framePos.setY(getPositionFromTrack(track));
if (m_scene->editMode() == TimelineMode::NormalEdit && !canBePastedTo(pasteInfo, AVWidget)) {
return true;
}
QList lockedTracks;
bool allowAudioOnly = false;
if (KdenliveSettings::splitaudio()) {
if (clip) {
if (clip->clipType() == Audio) {
allowAudioOnly = true;
}
}
}
for (int i = 1; i < m_timeline->tracksCount(); ++i) {
TrackInfo nfo = m_timeline->getTrackInfo(i);
if (nfo.isLocked || (allowAudioOnly && nfo.type == VideoTrack))
lockedTracks << i;
}
if (lockedTracks.contains(track)) {
return false;
}
m_selectionGroup = new AbstractGroupItem(m_document->fps());
ClipItem *item = new ClipItem(clip, info, m_document->fps(), 1.0, 1, getFrameWidth());
connect(item, &AbstractClipItem::selectItem, this, &CustomTrackView::slotSelectItem);
m_selectionGroup->addItem(item);
QList offsetList;
offsetList.append(info.endPos);
updateSnapPoints(NULL, offsetList);
m_selectionGroup->setProperty("locked_tracks", QVariant::fromValue(lockedTracks));
m_selectionGroup->setPos(framePos);
scene()->addItem(m_selectionGroup);
m_selectionGroup->setSelected(true);
} else if (data->hasFormat(QStringLiteral("kdenlive/producerslist"))) {
QStringList ids = QString(data->data(QStringLiteral("kdenlive/producerslist"))).split(';');
QList offsetList;
QList infoList;
GenTime start = GenTime((int)(framePos.x() + 0.5), m_document->fps());
framePos.setX((int)(framePos.x() + 0.5));
framePos.setY(getPositionFromTrack(track));
// Check if user dragged a folder
for (int i = 0; i < ids.size(); ++i) {
if (ids.at(i).startsWith(QLatin1String("#"))) {
QString folderId = ids.at(i);
folderId.remove(0, 1);
QStringList clipsInFolder = m_document->getBinFolderClipIds(folderId);
ids.removeAt(i);
for (int j = 0; j < clipsInFolder.count(); j++) {
ids.insert(i + j, clipsInFolder.at(j));
}
}
}
// Check if clips can be inserted at that position
bool allowAudioOnly = false;
for (int i = 0; i < ids.size(); ++i) {
QString clipData = ids.at(i);
QString clipId = clipData.section('/', 0, 0);
ProjectClip *clip = m_document->getBinClip(clipId);
if (!clip || !clip->isReady()) {
emit displayMessage(i18n("Clip not ready"), ErrorMessage);
return false;
}
ItemInfo info;
info.startPos = start;
if (clipData.contains('/')) {
// this is a clip zone, set in / out
int in = clipData.section('/', 1, 1).toInt();
int out = clipData.section('/', 2, 2).toInt();
info.cropStart = GenTime(in, m_document->fps());
info.cropDuration = GenTime(out - in, m_document->fps());
}
else {
info.cropDuration = clip->duration();
}
info.endPos = info.startPos + info.cropDuration;
info.track = track;
infoList.append(info);
start += info.cropDuration;
if (KdenliveSettings::splitaudio() && clip->clipType() == Audio)
allowAudioOnly = true;
}
if (m_scene->editMode() == TimelineMode::NormalEdit && !canBePastedTo(infoList, AVWidget)) {
return true;
}
QList lockedTracks;
bool locked = false;
for (int i = 1; i < m_timeline->tracksCount(); ++i) {
TrackInfo nfo = m_timeline->getTrackInfo(i);
if (nfo.isLocked) {
lockedTracks << i;
} else if (allowAudioOnly && nfo.type == VideoTrack) {
if (track == i) {
locked = true;
}
lockedTracks << i;
}
}
if (locked)
return false;
if (ids.size() > 1) {
m_selectionGroup = new AbstractGroupItem(m_document->fps());
}
start = GenTime();
for (int i = 0; i < ids.size(); ++i) {
QString clipData = ids.at(i);
QString clipId = clipData.section('/', 0, 0);
ProjectClip *clip = m_document->getBinClip(clipId);
ItemInfo info;
info.startPos = start;
if (clipData.contains('/')) {
// this is a clip zone, set in / out
int in = clipData.section('/', 1, 1).toInt();
int out = clipData.section('/', 2, 2).toInt();
info.cropStart = GenTime(in, m_document->fps());
info.cropDuration = GenTime(out - in, m_document->fps());
}
else {
info.cropDuration = clip->duration();
}
info.endPos = info.startPos + info.cropDuration;
info.track = 0;
start += info.cropDuration;
offsetList.append(start);
ClipItem *item = new ClipItem(clip, info, m_document->fps(), 1.0, 1, getFrameWidth(), true);
connect(item, &AbstractClipItem::selectItem, this, &CustomTrackView::slotSelectItem);
item->setPos(info.startPos.frames(m_document->fps()), item->itemOffset());
if (ids.size() > 1) m_selectionGroup->addItem(item);
else {
m_dragItem = item;
m_dragItem->setMainSelectedClip(true);
}
item->setSelected(true);
}
updateSnapPoints(NULL, offsetList);
if (m_selectionGroup) {
m_selectionGroup->setProperty("locked_tracks", QVariant::fromValue(lockedTracks));
scene()->addItem(m_selectionGroup);
m_selectionGroup->setPos(framePos);
}
else if (m_dragItem) {
m_dragItem->setProperty("locked_tracks", QVariant::fromValue(lockedTracks));
scene()->addItem(m_dragItem);
m_dragItem->setPos(framePos);
}
//m_selectionGroup->setZValue(10);
}
return true;
}
void CustomTrackView::dragEnterEvent(QDragEnterEvent * event)
{
if (insertDropClips(event->mimeData(), event->pos())) {
if (event->source() == this) {
event->setDropAction(Qt::MoveAction);
event->accept();
} else {
event->setDropAction(Qt::MoveAction);
event->acceptProposedAction();
}
} else {
QGraphicsView::dragEnterEvent(event);
}
}
bool CustomTrackView::itemCollision(AbstractClipItem *item, const ItemInfo &newPos)
{
QRectF shape = QRectF(newPos.startPos.frames(m_document->fps()), getPositionFromTrack(newPos.track) + 1, (newPos.endPos - newPos.startPos).frames(m_document->fps()) - 0.02, m_tracksHeight - 1);
QList collindingItems = scene()->items(shape, Qt::IntersectsItemShape);
collindingItems.removeAll(item);
if (collindingItems.isEmpty()) {
return false;
} else {
for (int i = 0; i < collindingItems.count(); ++i) {
QGraphicsItem *collision = collindingItems.at(i);
if (collision->type() == item->type()) {
// Collision
//qDebug() << "// COLLISIION DETECTED";
return true;
}
}
return false;
}
}
void CustomTrackView::slotRefreshEffects(ClipItem *clip)
{
int track = clip->track();
GenTime pos = clip->startPos();
if (!m_timeline->track(track)->removeEffect(pos.seconds(), -1, false)) {
emit displayMessage(i18n("Problem deleting effect"), ErrorMessage);
return;
}
bool success = true;
for (int i = 0; i < clip->effectsCount(); ++i) {
if (!m_timeline->track(track)->addEffect(pos.seconds(), EffectsController::getEffectArgs(m_document->getProfileInfo(), clip->effect(i)))) success = false;
}
if (!success) emit displayMessage(i18n("Problem adding effect to clip"), ErrorMessage);
m_document->renderer()->doRefresh();
}
void CustomTrackView::addEffect(int track, GenTime pos, QDomElement effect)
{
if (pos < GenTime()) {
// Add track effect
if (effect.attribute(QStringLiteral("id")) == QLatin1String("speed")) {
emit displayMessage(i18n("Cannot add speed effect to track"), ErrorMessage);
return;
}
clearSelection();
m_timeline->addTrackEffect(track, effect);
emit updateTrackEffectState(track);
emit showTrackEffects(track, m_timeline->getTrackInfo(track));
return;
}
ClipItem *clip = getClipItemAtStart(pos, track);
if (clip) {
// Special case: speed effect
if (effect.attribute(QStringLiteral("id")) == QLatin1String("speed")) {
if (clip->clipType() != Video && clip->clipType() != AV && clip->clipType() != Playlist) {
emit displayMessage(i18n("Problem adding effect to clip"), ErrorMessage);
return;
}
QLocale locale;
locale.setNumberOptions(QLocale::OmitGroupSeparator);
double speed = locale.toDouble(EffectsList::parameter(effect, QStringLiteral("speed"))) / 100.0;
int strobe = EffectsList::parameter(effect, QStringLiteral("strobe")).toInt();
if (strobe == 0) strobe = 1;
doChangeClipSpeed(clip->info(), clip->speedIndependantInfo(), clip->clipState(), speed, strobe, clip->getBinId());
EffectsParameterList params = clip->addEffect(m_document->getProfileInfo(), effect);
if (clip->isSelected()) emit clipItemSelected(clip);
return;
}
EffectsParameterList params = clip->addEffect(m_document->getProfileInfo(), effect);
if (!m_timeline->track(track)->addEffect(pos.seconds(), params)) {
//if (!m_document->renderer()->mltAddEffect(track, pos, params)) {
emit displayMessage(i18n("Problem adding effect to clip"), ErrorMessage);
clip->deleteEffect(params.paramValue(QStringLiteral("kdenlive_ix")).toInt());
}
else clip->setSelectedEffect(params.paramValue(QStringLiteral("kdenlive_ix")).toInt());
if (clip->isMainSelectedClip()) emit clipItemSelected(clip);
} else emit displayMessage(i18n("Cannot find clip to add effect"), ErrorMessage);
}
void CustomTrackView::deleteEffect(int track, const GenTime &pos, const QDomElement &effect)
{
int index = effect.attribute(QStringLiteral("kdenlive_ix")).toInt();
if (pos < GenTime()) {
// Delete track effect
if (!m_timeline->removeTrackEffect(track, index, effect)) {
emit displayMessage(i18n("Problem deleting effect"), ErrorMessage);
}
emit updateTrackEffectState(track);
emit showTrackEffects(track, m_timeline->getTrackInfo(track));
return;
}
// Special case: speed effect
if (effect.attribute(QStringLiteral("id")) == QLatin1String("speed")) {
ClipItem *clip = getClipItemAtStart(pos, track);
if (clip) {
doChangeClipSpeed(clip->info(), clip->speedIndependantInfo(), clip->clipState(), 1.0, 1, clip->getBinId(), true);
clip->deleteEffect(index);
emit clipItemSelected(clip);
return;
}
}
if (!m_timeline->track(track)->removeEffect(pos.seconds(), index, true)) {
//qDebug() << "// ERROR REMOV EFFECT: " << index << ", DISABLE: " << effect.attribute("disable");
emit displayMessage(i18n("Problem deleting effect"), ErrorMessage);
return;
}
ClipItem *clip = getClipItemAtStart(pos, track);
if (clip) {
clip->deleteEffect(index);
if (clip->isMainSelectedClip()) emit clipItemSelected(clip);
}
}
void CustomTrackView::slotAddGroupEffect(QDomElement effect, AbstractGroupItem *group, AbstractClipItem *dropTarget)
{
QList itemList = group->childItems();
QUndoCommand *effectCommand = new QUndoCommand();
QString effectName;
int offset = effect.attribute(QStringLiteral("clipstart")).toInt();
QDomElement namenode = effect.firstChildElement(QStringLiteral("name"));
if (!namenode.isNull()) effectName = i18n(namenode.text().toUtf8().data());
else effectName = i18n("effect");
effectCommand->setText(i18n("Add %1", effectName));
for (int i = 0; i < itemList.count(); ++i) {
if (itemList.at(i)->type() == GroupWidget) {
itemList << itemList.at(i)->childItems();
}
if (itemList.at(i)->type() == AVWidget) {
ClipItem *item = static_cast (itemList.at(i));
if (effect.tagName() == QLatin1String("effectgroup")) {
QDomNodeList effectlist = effect.elementsByTagName(QStringLiteral("effect"));
for (int j = 0; j < effectlist.count(); ++j) {
QDomElement subeffect = effectlist.at(j).toElement();
if (subeffect.hasAttribute(QStringLiteral("kdenlive_info"))) {
// effect is in a group
EffectInfo effectInfo;
effectInfo.fromString(subeffect.attribute(QStringLiteral("kdenlive_info")));
if (effectInfo.groupIndex < 0) {
// group needs to be appended
effectInfo.groupIndex = item->nextFreeEffectGroupIndex();
subeffect.setAttribute(QStringLiteral("kdenlive_info"), effectInfo.toString());
}
}
processEffect(item, subeffect.cloneNode().toElement(), offset, effectCommand);
}
}
else {
processEffect(item, effect.cloneNode().toElement(), offset, effectCommand);
}
}
}
if (effectCommand->childCount() > 0) {
m_commandStack->push(effectCommand);
} else delete effectCommand;
if (dropTarget) {
clearSelection(false);
m_dragItem = dropTarget;
m_dragItem->setSelected(true);
m_dragItem->setMainSelectedClip(true);
emit clipItemSelected(static_cast(dropTarget));
}
}
void CustomTrackView::slotAddEffect(ClipItem *clip, const QDomElement &effect, int track)
{
if (clip == NULL) {
// delete track effect
AddEffectCommand *command = new AddEffectCommand(this, track, GenTime(-1), effect, true);
m_commandStack->push(command);
return;
}
else slotAddEffect(effect, clip->startPos(), clip->track());
}
void CustomTrackView::slotDropEffect(ClipItem *clip, QDomElement effect, GenTime pos, int track)
{
if (clip == NULL) return;
slotAddEffect(effect, pos, track);
if (clip->parentItem()) {
// Clip is in a group, should not happen
//qDebug()<<"/// DROPPED ON ITEM IN GRP";
}
else if (clip != m_dragItem) {
clearSelection(false);
m_dragItem = clip;
m_dragItem->setMainSelectedClip(true);
clip->setSelected(true);
emit clipItemSelected(clip);
}
}
void CustomTrackView::slotSelectItem(AbstractClipItem *item)
{
clearSelection(false);
m_dragItem = item;
m_dragItem->setMainSelectedClip(true);
item->setSelected(true);
if (item->type() == AVWidget)
emit clipItemSelected((ClipItem*)item);
else if (item->type() == TransitionWidget)
emit transitionItemSelected((Transition*)item);
}
void CustomTrackView::slotDropTransition(ClipItem *clip, QDomElement transition, QPointF scenePos)
{
if (clip == NULL) return;
m_menuPosition = mapFromScene(scenePos);
slotAddTransitionToSelectedClips(transition, QList() << clip);
m_menuPosition = QPoint();
}
void CustomTrackView::removeSplitOverlay()
{
m_timeline->removeSplitOverlay();
}
bool CustomTrackView::createSplitOverlay(Mlt::Filter *filter)
{
if (!m_dragItem || m_dragItem->type() != AVWidget) {
//TODO manage split clip
return false;
}
return m_timeline->createOverlay(filter, m_dragItem->track(), m_dragItem->startPos().frames(m_document->fps()));
}
void CustomTrackView::slotAddEffectToCurrentItem(QDomElement effect)
{
slotAddEffect(effect, GenTime(), -1);
}
void CustomTrackView::slotAddEffect(QDomElement effect, const GenTime &pos, int track)
{
QList itemList;
QUndoCommand *effectCommand = new QUndoCommand();
QString effectName;
int offset = effect.attribute(QStringLiteral("clipstart")).toInt();
if (effect.tagName() == QLatin1String("effectgroup")) {
effectName = effect.attribute(QStringLiteral("name"));
} else {
QDomElement namenode = effect.firstChildElement(QStringLiteral("name"));
if (!namenode.isNull()) effectName = i18n(namenode.text().toUtf8().data());
else effectName = i18n("effect");
}
effectCommand->setText(i18n("Add %1", effectName));
if (track == -1) {
itemList = scene()->selectedItems();
}
else if (itemList.isEmpty()) {
ClipItem *clip = getClipItemAtStart(pos, track);
if (clip) itemList.append(clip);
}
if (itemList.isEmpty()) emit displayMessage(i18n("Select a clip if you want to apply an effect"), ErrorMessage);
//expand groups
for (int i = 0; i < itemList.count(); ++i) {
if (itemList.at(i)->type() == GroupWidget) {
QList subitems = itemList.at(i)->childItems();
for (int j = 0; j < subitems.count(); ++j) {
if (!itemList.contains(subitems.at(j))) itemList.append(subitems.at(j));
}
}
}
for (int i = 0; i < itemList.count(); ++i) {
if (itemList.at(i)->type() == AVWidget) {
ClipItem *item = static_cast (itemList.at(i));
if (effect.tagName() == QLatin1String("effectgroup")) {
QDomNodeList effectlist = effect.elementsByTagName(QStringLiteral("effect"));
for (int j = 0; j < effectlist.count(); ++j) {
QDomElement subeffect = effectlist.at(j).toElement();
if (subeffect.hasAttribute(QStringLiteral("kdenlive_info"))) {
// effect is in a group
EffectInfo effectInfo;
effectInfo.fromString(subeffect.attribute(QStringLiteral("kdenlive_info")));
if (effectInfo.groupIndex < 0) {
// group needs to be appended
effectInfo.groupIndex = item->nextFreeEffectGroupIndex();
subeffect.setAttribute(QStringLiteral("kdenlive_info"), effectInfo.toString());
}
}
processEffect(item, subeffect, offset, effectCommand);
}
}
else processEffect(item, effect, offset, effectCommand);
}
}
if (effectCommand->childCount() > 0) {
m_commandStack->push(effectCommand);
} else delete effectCommand;
}
void CustomTrackView::processEffect(ClipItem *item, QDomElement effect, int offset, QUndoCommand *effectCommand)
{
if (effect.attribute(QStringLiteral("type")) == QLatin1String("audio")) {
// Don't add audio effects on video clips
if (item->clipState() == PlaylistState::VideoOnly || (item->clipType() != Audio && item->clipType() != AV && item->clipType() != Playlist)) {
/* do not show error message when item is part of a group as the user probably knows what he does then
* and the message is annoying when working with the split audio feature */
if (!item->parentItem() || item->parentItem() == m_selectionGroup)
emit displayMessage(i18n("Cannot add an audio effect to this clip"), ErrorMessage);
return;
}
} else if (effect.attribute(QStringLiteral("type")) == QLatin1String("video") || !effect.hasAttribute(QStringLiteral("type"))) {
// Don't add video effect on audio clips
if (item->clipState() == PlaylistState::AudioOnly || item->clipType() == Audio) {
/* do not show error message when item is part of a group as the user probably knows what he does then
* and the message is annoying when working with the split audio feature */
if (!item->parentItem() || item->parentItem() == m_selectionGroup)
emit displayMessage(i18n("Cannot add a video effect to this clip"), ErrorMessage);
return;
}
}
if (effect.attribute(QStringLiteral("unique"), QStringLiteral("0")) != QLatin1String("0") && item->hasEffect(effect.attribute(QStringLiteral("tag")), effect.attribute(QStringLiteral("id"))) != -1) {
emit displayMessage(i18n("Effect already present in clip"), ErrorMessage);
return;
}
if (item->isItemLocked()) {
return;
}
if (effect.attribute(QStringLiteral("id")) == QLatin1String("freeze") && m_cursorPos > item->startPos().frames(m_document->fps()) && m_cursorPos < item->endPos().frames(m_document->fps())) {
item->initEffect(m_document->getProfileInfo() , effect, m_cursorPos - item->startPos().frames(m_document->fps()), offset);
} else {
item->initEffect(m_document->getProfileInfo() , effect, 0, offset);
}
new AddEffectCommand(this, item->track(), item->startPos(), effect, true, effectCommand);
}
void CustomTrackView::slotDeleteEffectGroup(ClipItem *clip, int track, QDomDocument doc, bool affectGroup)
{
QUndoCommand *delCommand = new QUndoCommand();
QString effectName = doc.documentElement().attribute(QStringLiteral("name"));
delCommand->setText(i18n("Delete %1", effectName));
QDomNodeList effects = doc.elementsByTagName(QStringLiteral("effect"));
for (int i = 0; i < effects.count(); ++i) {
slotDeleteEffect(clip, track, effects.at(i).toElement(), affectGroup, delCommand);
}
m_commandStack->push(delCommand);
}
void CustomTrackView::slotDeleteEffect(ClipItem *clip, int track, QDomElement effect, bool affectGroup, QUndoCommand *parentCommand)
{
if (clip == NULL) {
// delete track effect
AddEffectCommand *command = new AddEffectCommand(this, track, GenTime(-1), effect, false, parentCommand);
if (parentCommand == NULL)
m_commandStack->push(command);
return;
}
if (affectGroup && clip->parentItem() && clip->parentItem() == m_selectionGroup) {
//clip is in a group, also remove the effect in other clips of the group
QList items = m_selectionGroup->childItems();
QUndoCommand *delCommand = parentCommand == NULL ? new QUndoCommand() : parentCommand;
QString effectName;
QDomElement namenode = effect.firstChildElement(QStringLiteral("name"));
if (!namenode.isNull()) effectName = i18n(namenode.text().toUtf8().data());
else effectName = i18n("effect");
delCommand->setText(i18n("Delete %1", effectName));
//expand groups
for (int i = 0; i < items.count(); ++i) {
if (items.at(i)->type() == GroupWidget) {
QList subitems = items.at(i)->childItems();
for (int j = 0; j < subitems.count(); ++j) {
if (!items.contains(subitems.at(j))) items.append(subitems.at(j));
}
}
}
for (int i = 0; i < items.count(); ++i) {
if (items.at(i)->type() == AVWidget) {
ClipItem *item = static_cast (items.at(i));
int ix = item->hasEffect(effect.attribute(QStringLiteral("tag")), effect.attribute(QStringLiteral("id")));
if (ix != -1) {
QDomElement eff = item->effectAtIndex(ix);
new AddEffectCommand(this, item->track(), item->startPos(), eff, false, delCommand);
}
}
}
if (parentCommand == NULL) {
if (delCommand->childCount() > 0)
m_commandStack->push(delCommand);
else
delete delCommand;
}
return;
}
if (parentCommand == NULL) {
AddEffectCommand *command = new AddEffectCommand(this, clip->track(), clip->startPos(), effect, false, parentCommand);
m_commandStack->push(command);
}
}
void CustomTrackView::updateEffect(int track, GenTime pos, QDomElement insertedEffect, bool updateEffectStack, bool replaceEffect)
{
if (insertedEffect.isNull()) {
//qDebug()<<"// Trying to add null effect";
emit displayMessage(i18n("Problem editing effect"), ErrorMessage);
return;
}
int ix = insertedEffect.attribute(QStringLiteral("kdenlive_ix")).toInt();
QDomElement effect = insertedEffect.cloneNode().toElement();
//qDebug() << "// update effect ix: " << effect.attribute("kdenlive_ix")<<", TAG: "<< insertedEffect.attribute("tag");
if (pos < GenTime()) {
// editing a track effect
EffectsParameterList effectParams = EffectsController::getEffectArgs(m_document->getProfileInfo(), effect);
// check if we are trying to reset a keyframe effect
/*if (effectParams.hasParam("keyframes") && effectParams.paramValue("keyframes").isEmpty()) {
clip->initEffect(m_document->getProfileInfo() , effect);
effectParams = EffectsController::getEffectArgs(effect);
}*/
if (!m_timeline->track(track)->editTrackEffect(effectParams, replaceEffect)) {
emit displayMessage(i18n("Problem editing effect"), ErrorMessage);
}
m_timeline->setTrackEffect(track, ix, effect);
emit updateTrackEffectState(track);
return;
}
ClipItem *clip = getClipItemAtStart(pos, track);
if (clip) {
// Special case: speed effect
if (effect.attribute(QStringLiteral("id")) == QLatin1String("speed")) {
if (effect.attribute(QStringLiteral("disable")) == QLatin1String("1")) {
doChangeClipSpeed(clip->info(), clip->speedIndependantInfo(), clip->clipState(), 1.0, 1, clip->getBinId());
} else {
QLocale locale;
locale.setNumberOptions(QLocale::OmitGroupSeparator);
double speed = locale.toDouble(EffectsList::parameter(effect, QStringLiteral("speed"))) / 100.0;
int strobe = EffectsList::parameter(effect, QStringLiteral("strobe")).toInt();
if (strobe == 0) strobe = 1;
doChangeClipSpeed(clip->info(), clip->speedIndependantInfo(), clip->clipState(), speed, strobe, clip->getBinId());
}
clip->updateEffect(effect);
if (updateEffectStack && clip->isSelected())
emit clipItemSelected(clip);
if (ix == clip->selectedEffectIndex()) {
// make sure to update display of clip keyframes
clip->setSelectedEffect(ix);
}
return;
}
EffectsParameterList effectParams = EffectsController::getEffectArgs(m_document->getProfileInfo(), effect);
// check if we are trying to reset a keyframe effect
if (effectParams.hasParam(QStringLiteral("keyframes")) && effectParams.paramValue(QStringLiteral("keyframes")).isEmpty()) {
clip->initEffect(m_document->getProfileInfo() , effect);
effectParams = EffectsController::getEffectArgs(m_document->getProfileInfo(), effect);
}
// Check if a fade effect was changed
QString effectId = effect.attribute(QStringLiteral("id"));
if (effectId == QLatin1String("fadein") || effectId == QLatin1String("fade_from_black") || effectId == QLatin1String("fadeout") || effectId == QLatin1String("fade_to_black")) {
clip->setSelectedEffect(clip->selectedEffectIndex());
}
bool success = m_timeline->track(clip->track())->editEffect(clip->startPos().seconds(), effectParams, replaceEffect);
if (success) {
clip->updateEffect(effect);
if (updateEffectStack && clip->isSelected()) {
emit clipItemSelected(clip);
}
if (ix == clip->selectedEffectIndex()) {
// make sure to update display of clip keyframes
clip->setSelectedEffect(ix);
}
}
else emit displayMessage(i18n("Problem editing effect"), ErrorMessage);
}
else emit displayMessage(i18n("Cannot find clip to update effect"), ErrorMessage);
}
void CustomTrackView::updateEffectState(int track, GenTime pos, QList effectIndexes, bool disable, bool updateEffectStack)
{
if (pos < GenTime()) {
// editing a track effect
- if (!m_document->renderer()->mltEnableEffects(track, pos, effectIndexes, disable)) {
+ if (!m_timeline->track(track)->enableTrackEffects(effectIndexes, disable)) {
emit displayMessage(i18n("Problem editing effect"), ErrorMessage);
return;
}
m_timeline->enableTrackEffects(track, effectIndexes, disable);
emit updateTrackEffectState(track);
emit showTrackEffects(track, m_timeline->getTrackInfo(track));
return;
}
// editing a clip effect
ClipItem *clip = getClipItemAtStart(pos, track);
if (clip) {
- bool success = m_document->renderer()->mltEnableEffects(clip->track(), clip->startPos(), effectIndexes, disable);
+ bool success = m_timeline->track(clip->track())->enableEffects(clip->startPos().seconds(), effectIndexes, disable);
if (success) {
clip->enableEffects(effectIndexes, disable);
if (updateEffectStack && clip->isSelected()) {
emit clipItemSelected(clip);
}
if (effectIndexes.contains(clip->selectedEffectIndex())) {
// make sure to update display of clip keyframes
clip->setSelectedEffect(clip->selectedEffectIndex());
}
}
else emit displayMessage(i18n("Problem editing effect"), ErrorMessage);
}
else emit displayMessage(i18n("Cannot find clip to update effect"), ErrorMessage);
}
void CustomTrackView::moveEffect(int track, const GenTime &pos, const QList &oldPos, const QList &newPos)
{
if (pos < GenTime()) {
// Moving track effect
int documentTrack = track - 1;
int max = m_timeline->getTrackEffects(documentTrack).count();
int new_position = newPos.at(0);
if (new_position > max) {
new_position = max;
}
int old_position = oldPos.at(0);
for (int i = 0; i < newPos.count(); ++i) {
QDomElement act = m_timeline->getTrackEffect(documentTrack, new_position);
if (old_position > new_position) {
// Moving up, we need to adjust index
old_position = oldPos.at(i);
new_position = newPos.at(i);
}
QDomElement before = m_timeline->getTrackEffect(documentTrack, old_position);
if (!act.isNull() && !before.isNull()) {
m_timeline->setTrackEffect(documentTrack, new_position, before);
- m_document->renderer()->mltMoveEffect(track, pos, old_position, new_position);
+ m_timeline->track(track)->moveTrackEffect(old_position, new_position);
} else emit displayMessage(i18n("Cannot move effect"), ErrorMessage);
}
emit showTrackEffects(track, m_timeline->getTrackInfo(documentTrack));
return;
}
ClipItem *clip = getClipItemAtStart(pos, track);
if (clip) {
int new_position = newPos.at(0);
if (new_position > clip->effectsCount()) {
new_position = clip->effectsCount();
}
int old_position = oldPos.at(0);
for (int i = 0; i < newPos.count(); ++i) {
QDomElement act = clip->effectAtIndex(new_position);
if (old_position > new_position) {
// Moving up, we need to adjust index
old_position = oldPos.at(i);
new_position = newPos.at(i);
}
QDomElement before = clip->effectAtIndex(old_position);
if (act.isNull() || before.isNull()) {
emit displayMessage(i18n("Cannot move effect"), ErrorMessage);
return;
}
clip->moveEffect(before, new_position);
+ if (clip->hasEffect(QStringLiteral("timewarp"), QStringLiteral("speed")) != -1) {
+ // special case, speed effect is not in MLT's filter list, so decrease indexes
+ old_position--;
+ new_position--;
+ }
// special case: speed effect, which is a pseudo-effect, not appearing in MLT's effects
- if (act.attribute(QStringLiteral("id")) == QLatin1String("speed")) {
- m_document->renderer()->mltUpdateEffectPosition(track, pos, old_position, new_position);
- } else if (before.attribute(QStringLiteral("id")) == QLatin1String("speed")) {
- m_document->renderer()->mltUpdateEffectPosition(track, pos, new_position, old_position);
- } else m_document->renderer()->mltMoveEffect(track, pos, old_position, new_position);
+ m_timeline->track(track)->moveEffect(pos.seconds(), old_position, new_position);
}
clip->setSelectedEffect(newPos.at(0));
emit clipItemSelected(clip);
} else emit displayMessage(i18n("Cannot move effect"), ErrorMessage);
}
void CustomTrackView::slotChangeEffectState(ClipItem *clip, int track, QList effectIndexes, bool disable)
{
ChangeEffectStateCommand *command;
if (clip == NULL) {
// editing track effect
command = new ChangeEffectStateCommand(this, track, GenTime(-1), effectIndexes, disable, false, true);
} else {
// Check if we have a speed effect, disabling / enabling it needs a special procedure since it is a pseudoo effect
QList speedEffectIndexes;
for (int i = 0; i < effectIndexes.count(); ++i) {
QDomElement effect = clip->effectAtIndex(effectIndexes.at(i));
if (effect.attribute(QStringLiteral("id")) == QLatin1String("speed")) {
// speed effect
speedEffectIndexes << effectIndexes.at(i);
QDomElement newEffect = effect.cloneNode().toElement();
newEffect.setAttribute(QStringLiteral("disable"), (int) disable);
EditEffectCommand *editcommand = new EditEffectCommand(this, clip->track(), clip->startPos(), effect, newEffect, effectIndexes.at(i), false, true);
m_commandStack->push(editcommand);
}
}
for (int j = 0; j < speedEffectIndexes.count(); ++j) {
effectIndexes.removeAll(speedEffectIndexes.at(j));
}
command = new ChangeEffectStateCommand(this, clip->track(), clip->startPos(), effectIndexes, disable, false, true);
}
m_commandStack->push(command);
}
void CustomTrackView::slotChangeEffectPosition(ClipItem *clip, int track, QList currentPos, int newPos)
{
MoveEffectCommand *command;
if (clip == NULL) {
// editing track effect
command = new MoveEffectCommand(this, track, GenTime(-1), currentPos, newPos);
} else command = new MoveEffectCommand(this, clip->track(), clip->startPos(), currentPos, newPos);
m_commandStack->push(command);
}
void CustomTrackView::slotUpdateClipEffect(ClipItem *clip, int track, QDomElement oldeffect, QDomElement effect, int ix, bool refreshEffectStack)
{
EditEffectCommand *command;
if (clip) command = new EditEffectCommand(this, clip->track(), clip->startPos(), oldeffect.cloneNode().toElement(), effect.cloneNode().toElement(), ix, refreshEffectStack, true);
else command = new EditEffectCommand(this, track, GenTime(-1), oldeffect.cloneNode().toElement(), effect.cloneNode().toElement(), ix, refreshEffectStack, true);
m_commandStack->push(command);
}
void CustomTrackView::slotUpdateClipRegion(ClipItem *clip, int ix, QString region)
{
QDomElement effect = clip->getEffectAtIndex(ix);
QDomElement oldeffect = effect.cloneNode().toElement();
effect.setAttribute(QStringLiteral("region"), region);
EditEffectCommand *command = new EditEffectCommand(this, clip->track(), clip->startPos(), oldeffect, effect, ix, true, true);
m_commandStack->push(command);
}
void CustomTrackView::cutClip(const ItemInfo &info, const GenTime &cutTime, bool cut, const EffectsList &oldStack, bool execute)
{
if (cut) {
// cut clip
ClipItem *item = getClipItemAtStart(info.startPos, info.track);
bool selectDup = false;
if (item == m_dragItem) {
emit clipItemSelected(NULL);
selectDup = true;
}
if (!item || cutTime >= item->endPos() || cutTime <= item->startPos()) {
emit displayMessage(i18n("Cannot find clip to cut"), ErrorMessage);
if (item)
qDebug() << "///////// ERROR CUTTING CLIP : (" << item->startPos().frames(25) << '-' << item->endPos().frames(25) << "), INFO: (" << info.startPos.frames(25) << '-' << info.endPos.frames(25) << ')' << ", CUT: " << cutTime.frames(25);
else
qDebug() << "/// ERROR NO CLIP at: " << info.startPos.frames(m_document->fps()) << ", track: " << info.track;
return;
}
if (execute) {
if (!m_timeline->track(info.track)->cut(cutTime.seconds())) {
// Error cuting clip in playlist
return;
}
}
m_timeline->reloadTrack(info.track, info.startPos.frames(m_document->fps()), info.endPos.frames(m_document->fps()));
// remove unwanted effects
// fade in from 2nd part of the clip
item = getClipItemAtStart(info.startPos, info.track);
ClipItem *dup = getClipItemAtStart(cutTime, info.track);
dup->binClip()->addRef();
int ix = dup->hasEffect(QString(), QStringLiteral("fadein"));
if (ix != -1) {
QDomElement oldeffect = dup->effectAtIndex(ix);
dup->deleteEffect(oldeffect.attribute(QStringLiteral("kdenlive_ix")).toInt());
}
ix = dup->hasEffect(QString(), QStringLiteral("fade_from_black"));
if (ix != -1) {
QDomElement oldeffect = dup->effectAtIndex(ix);
dup->deleteEffect(oldeffect.attribute(QStringLiteral("kdenlive_ix")).toInt());
}
// fade out from 1st part of the clip
ix = item->hasEffect(QString(), QStringLiteral("fadeout"));
if (ix != -1) {
QDomElement oldeffect = item->effectAtIndex(ix);
item->deleteEffect(oldeffect.attribute(QStringLiteral("kdenlive_ix")).toInt());
}
ix = item->hasEffect(QString(), QStringLiteral("fade_to_black"));
if (ix != -1) {
QDomElement oldeffect = item->effectAtIndex(ix);
item->deleteEffect(oldeffect.attribute(QStringLiteral("kdenlive_ix")).toInt());
}
if (item->checkKeyFrames(m_document->width(), m_document->height(), (info.cropDuration + info.cropStart).frames(m_document->fps())))
slotRefreshEffects(item);
if (dup->checkKeyFrames(m_document->width(), m_document->height(), (info.cropDuration + info.cropStart).frames(m_document->fps()), (cutTime - item->startPos()).frames(m_document->fps())))
slotRefreshEffects(dup);
if (selectDup) {
dup->setSelected(true);
dup->setMainSelectedClip(true);
m_dragItem = dup;
emit clipItemSelected(dup);
}
return;
} else {
// uncut clip
ClipItem *item = getClipItemAtStart(info.startPos, info.track);
ClipItem *dup = getClipItemAtStart(cutTime, info.track);
bool selectDup = false;
if (m_dragItem == item || m_dragItem == dup) {
emit clipItemSelected(NULL);
selectDup = true;
}
if (!item || !dup || item == dup) {
emit displayMessage(i18n("Cannot find clip to uncut"), ErrorMessage);
return;
}
if (!m_timeline->track(info.track)->del(cutTime.seconds())) {
emit displayMessage(i18n("Error removing clip at %1 on track %2", m_document->timecode().getTimecodeFromFrames(cutTime.frames(m_document->fps())), m_timeline->getTrackInfo(info.track).trackName), ErrorMessage);
return;
}
dup->binClip()->removeRef();
m_timeline->track(info.track)->resize(info.startPos.seconds(), (info.endPos - cutTime).seconds(), true);
m_timeline->reloadTrack(info.track, info.startPos.frames(m_document->fps()), info.endPos.frames(m_document->fps()));
item = getClipItemAtStart(info.startPos, info.track);
// Restore original effects
item->setEffectList(oldStack);
if (selectDup) {
item->setSelected(true);
item->setMainSelectedClip(true);
m_dragItem = item;
emit clipItemSelected(item);
}
}
}
Transition *CustomTrackView::cutTransition(const ItemInfo &info, const GenTime &cutTime, bool cut, const QDomElement &oldStack, bool execute)
{
if (cut) {
// cut clip
Transition *item = getTransitionItemAtStart(info.startPos, info.track);
if (!item || cutTime >= item->endPos() || cutTime <= item->startPos()) {
emit displayMessage(i18n("Cannot find transition to cut"), ErrorMessage);
if (item)
qDebug() << "///////// ERROR CUTTING transition : (" << item->startPos().frames(25) << '-' << item->endPos().frames(25) << "), INFO: (" << info.startPos.frames(25) << '-' << info.endPos.frames(25) << ')' << ", CUT: " << cutTime.frames(25);
else
qDebug() << "/// ERROR NO transition at: " << info.startPos.frames(m_document->fps()) << ", track: " << info.track;
return NULL;
}
bool success = true;
if (execute) {
success = m_timeline->transitionHandler->moveTransition(item->transitionTag(), info.track, info.track, item->transitionEndTrack(), info.startPos, info.endPos, info.startPos, cutTime);
if (success) {
success = m_timeline->transitionHandler->addTransition(item->transitionTag(), item->transitionEndTrack(), info.track, cutTime, info.endPos, item->toXML(), false);
}
}
int cutPos = (int) cutTime.frames(m_document->fps());
ItemInfo newPos;
newPos.startPos = cutTime;
newPos.endPos = info.endPos;
newPos.cropStart = item->info().cropStart + (cutTime - info.startPos);
newPos.track = info.track;
newPos.cropDuration = newPos.endPos - newPos.startPos;
bool snap = KdenliveSettings::snaptopoints();
KdenliveSettings::setSnaptopoints(false);
Transition *dup = item->clone(newPos);
connect(dup, &AbstractClipItem::selectItem, this, &CustomTrackView::slotSelectItem);
dup->setPos(newPos.startPos.frames(m_document->fps()), getPositionFromTrack(newPos.track) + 1 + dup->itemOffset());
item->resizeEnd(cutPos);
scene()->addItem(dup);
if (item->checkKeyFrames(m_document->width(), m_document->height(), (info.cropDuration + info.cropStart).frames(m_document->fps()))) {
m_timeline->transitionHandler->updateTransitionParams(item->transitionTag(), item->transitionEndTrack(), info.track, info.startPos, cutTime, item->toXML());
}
if (dup->checkKeyFrames(m_document->width(), m_document->height(), (info.cropDuration + info.cropStart).frames(m_document->fps()), (cutTime - item->startPos()).frames(m_document->fps()))) {
m_timeline->transitionHandler->updateTransitionParams(item->transitionTag(), item->transitionEndTrack(), info.track, cutTime, info.endPos, dup->toXML());
}
KdenliveSettings::setSnaptopoints(snap);
return dup;
} else {
// uncut transition
Transition *item = getTransitionItemAtStart(info.startPos, info.track);
Transition *dup = getTransitionItemAtStart(cutTime, info.track);
if (!item || !dup || item == dup) {
emit displayMessage(i18n("Cannot find transition to uncut"), ErrorMessage);
return NULL;
}
ItemInfo transitionInfo = dup->info();
if (!m_timeline->transitionHandler->deleteTransition(dup->transitionTag(), dup->transitionEndTrack(), transitionInfo.track, cutTime, transitionInfo.endPos, dup->toXML(), false)) {
emit displayMessage(i18n("Error removing transition at %1 on track %2", m_document->timecode().getTimecodeFromFrames(cutTime.frames(m_document->fps())), m_timeline->getTrackInfo(info.track).trackName), ErrorMessage);
return NULL;
}
bool snap = KdenliveSettings::snaptopoints();
KdenliveSettings::setSnaptopoints(false);
if (dup->isSelected() && dup == m_dragItem) {
item->setSelected(true);
item->setMainSelectedClip(true);
m_dragItem = item;
emit transitionItemSelected(item);
}
scene()->removeItem(dup);
delete dup;
dup = NULL;
ItemInfo clipinfo = item->info();
bool success = m_timeline->transitionHandler->moveTransition(item->transitionTag(), clipinfo.track, clipinfo.track, item->transitionEndTrack(), clipinfo.startPos, clipinfo.endPos, clipinfo.startPos, transitionInfo.endPos);
if (success) {
item->resizeEnd((int) info.endPos.frames(m_document->fps()));
item->setTransitionParameters(oldStack);
m_timeline->transitionHandler->updateTransitionParams(item->transitionTag(), item->transitionEndTrack(), info.track, info.startPos, info.endPos, oldStack);
} else {
emit displayMessage(i18n("Error when resizing clip"), ErrorMessage);
}
KdenliveSettings::setSnaptopoints(snap);
return item;
}
}
void CustomTrackView::slotAddTransitionToSelectedClips(QDomElement transition, QList itemList)
{
if (itemList.isEmpty()) itemList = scene()->selectedItems();
if (itemList.count() == 1) {
if (itemList.at(0)->type() == AVWidget) {
ClipItem *item = static_cast(itemList.at(0));
ItemInfo info;
info.track = item->track();
ClipItem *transitionClip = NULL;
const int transitiontrack = getPreviousVideoTrack(info.track);
GenTime pos = GenTime((int)(mapToScene(m_menuPosition).x()), m_document->fps());
if (pos < item->startPos() + item->cropDuration() / 2) {
// add transition to clip start
info.startPos = item->startPos();
if (transitiontrack != 0) transitionClip = getClipItemAtMiddlePoint(info.startPos.frames(m_document->fps()), transitiontrack);
if (transitionClip && transitionClip->endPos() < item->endPos()) {
info.endPos = transitionClip->endPos();
} else info.endPos = info.startPos + GenTime(65, m_document->fps());
// Check there is no other transition at that place
double startY = getPositionFromTrack(info.track) + 1 + m_tracksHeight / 2;
QRectF r(info.startPos.frames(m_document->fps()), startY, (info.endPos - info.startPos).frames(m_document->fps()), m_tracksHeight / 2);
QList selection = m_scene->items(r);
bool transitionAccepted = true;
for (int i = 0; i < selection.count(); ++i) {
if (selection.at(i)->type() == TransitionWidget) {
Transition *tr = static_cast (selection.at(i));
if (tr->startPos() - info.startPos > GenTime(5, m_document->fps())) {
if (tr->startPos() < info.endPos) info.endPos = tr->startPos();
} else transitionAccepted = false;
}
}
if (transitionAccepted) slotAddTransition(item, info, transitiontrack, transition);
else emit displayMessage(i18n("Cannot add transition"), ErrorMessage);
} else {
// add transition to clip end
info.endPos = item->endPos();
if (transitiontrack != 0) transitionClip = getClipItemAtMiddlePoint(info.endPos.frames(m_document->fps()), transitiontrack);
if (transitionClip && transitionClip->startPos() > item->startPos()) {
info.startPos = transitionClip->startPos();
} else info.startPos = info.endPos - GenTime(65, m_document->fps());
if (transition.attribute(QStringLiteral("tag")) == QLatin1String("luma")) EffectsList::setParameter(transition, QStringLiteral("reverse"), QStringLiteral("1"));
else if (transition.attribute(QStringLiteral("id")) == QLatin1String("slide")) EffectsList::setParameter(transition, QStringLiteral("invert"), QStringLiteral("1"));
// Check there is no other transition at that place
double startY = getPositionFromTrack(info.track) + 1 + m_tracksHeight / 2;
QRectF r(info.startPos.frames(m_document->fps()), startY, (info.endPos - info.startPos).frames(m_document->fps()), m_tracksHeight / 2);
QList selection = m_scene->items(r);
bool transitionAccepted = true;
for (int i = 0; i < selection.count(); ++i) {
if (selection.at(i)->type() == TransitionWidget) {
Transition *tr = static_cast (selection.at(i));
if (info.endPos - tr->endPos() > GenTime(5, m_document->fps())) {
if (tr->endPos() > info.startPos) info.startPos = tr->endPos();
} else transitionAccepted = false;
}
}
if (transitionAccepted) slotAddTransition(item, info, transitiontrack, transition);
else emit displayMessage(i18n("Cannot add transition"), ErrorMessage);
}
}
} else for (int i = 0; i < itemList.count(); ++i) {
if (itemList.at(i)->type() == AVWidget) {
ClipItem *item = static_cast(itemList.at(i));
ItemInfo info;
info.startPos = item->startPos();
info.endPos = info.startPos + GenTime(65, m_document->fps());
info.track = item->track();
// Check there is no other transition at that place
double startY = getPositionFromTrack(info.track) + 1 + m_tracksHeight / 2;
QRectF r(info.startPos.frames(m_document->fps()), startY, (info.endPos - info.startPos).frames(m_document->fps()), m_tracksHeight / 2);
QList selection = m_scene->items(r);
bool transitionAccepted = true;
for (int i = 0; i < selection.count(); ++i) {
if (selection.at(i)->type() == TransitionWidget) {
Transition *tr = static_cast (selection.at(i));
if (tr->startPos() - info.startPos > GenTime(5, m_document->fps())) {
if (tr->startPos() < info.endPos) info.endPos = tr->startPos();
} else transitionAccepted = false;
}
}
int transitiontrack = getPreviousVideoTrack(info.track);
if (transitionAccepted) slotAddTransition(item, info, transitiontrack, transition);
else emit displayMessage(i18n("Cannot add transition"), ErrorMessage);
}
}
}
void CustomTrackView::slotAddTransition(ClipItem* /*clip*/, ItemInfo transitionInfo, int endTrack, QDomElement transition)
{
if (transitionInfo.startPos >= transitionInfo.endPos) {
emit displayMessage(i18n("Invalid transition"), ErrorMessage);
return;
}
AddTransitionCommand* command = new AddTransitionCommand(this, transitionInfo, endTrack, transition, false, true);
m_commandStack->push(command);
}
void CustomTrackView::addTransition(const ItemInfo &transitionInfo, int endTrack, const QDomElement ¶ms, bool refresh)
{
Transition *tr = new Transition(transitionInfo, endTrack, m_document->fps(), params, true);
connect(tr, &AbstractClipItem::selectItem, this, &CustomTrackView::slotSelectItem);
tr->setPos(transitionInfo.startPos.frames(m_document->fps()), getPositionFromTrack(transitionInfo.track) + tr->itemOffset() + 1);
////qDebug() << "---- ADDING transition " << params.attribute("value");
if (m_timeline->transitionHandler->addTransition(tr->transitionTag(), endTrack, transitionInfo.track, transitionInfo.startPos, transitionInfo.endPos, tr->toXML(), refresh)) {
scene()->addItem(tr);
} else {
emit displayMessage(i18n("Cannot add transition"), ErrorMessage);
delete tr;
}
}
void CustomTrackView::deleteTransition(const ItemInfo &transitionInfo, int endTrack, QDomElement /*params*/, bool refresh)
{
Transition *item = getTransitionItemAt(transitionInfo.startPos, transitionInfo.track);
if (!item) {
emit displayMessage(i18n("Select clip to delete"), ErrorMessage);
return;
}
m_timeline->transitionHandler->deleteTransition(item->transitionTag(), endTrack, transitionInfo.track, transitionInfo.startPos, transitionInfo.endPos, item->toXML(), refresh);
if (m_dragItem == item) {
m_dragItem->setMainSelectedClip(false);
m_dragItem = NULL;
}
// animate item deletion
item->closeAnimation();
emit transitionItemSelected(NULL);
}
void CustomTrackView::slotTransitionUpdated(Transition *tr, QDomElement old)
{
////qDebug() << "TRANS UPDATE, TRACKS: " << old.attribute("transition_btrack") << ", NEW: " << tr->toXML().attribute("transition_btrack");
QDomElement xml = tr->toXML();
if (old.isNull() || xml.isNull()) {
emit displayMessage(i18n("Cannot update transition"), ErrorMessage);
return;
}
EditTransitionCommand *command = new EditTransitionCommand(this, tr->track(), tr->startPos(), old, xml, false);
updateTrackDuration(tr->track(), command);
m_commandStack->push(command);
}
void CustomTrackView::updateTransition(int track, const GenTime &pos, const QDomElement &oldTransition, const QDomElement &transition, bool updateTransitionWidget)
{
Transition *item = getTransitionItemAt(pos, track);
if (!item) {
qWarning() << "Unable to find transition at pos :" << pos.frames(m_document->fps()) << ", ON track: " << track;
return;
}
bool force = false;
if (oldTransition.attribute(QStringLiteral("transition_atrack")) != transition.attribute(QStringLiteral("transition_atrack")) || oldTransition.attribute(QStringLiteral("transition_btrack")) != transition.attribute(QStringLiteral("transition_btrack")))
force = true;
m_timeline->transitionHandler->updateTransition(oldTransition.attribute(QStringLiteral("tag")), transition.attribute(QStringLiteral("tag")), transition.attribute(QStringLiteral("transition_btrack")).toInt(), transition.attribute(QStringLiteral("transition_atrack")).toInt(), item->startPos(), item->endPos(), transition, force);
////qDebug() << "ORIGINAL TRACK: "<< oldTransition.attribute("transition_btrack") << ", NEW TRACK: "<setTransitionParameters(transition);
if (updateTransitionWidget) {
ItemInfo info = item->info();
QPoint p;
ClipItem *transitionClip = getClipItemAtStart(info.startPos, info.track);
if (transitionClip && transitionClip->binClip()) {
int frameWidth = transitionClip->binClip()->getProducerIntProperty(QStringLiteral("meta.media.width"));
int frameHeight = transitionClip->binClip()->getProducerIntProperty(QStringLiteral("meta.media.height"));
double factor = transitionClip->binClip()->getProducerProperty(QStringLiteral("aspect_ratio")).toDouble();
if (factor == 0) factor = 1.0;
p.setX((int)(frameWidth * factor + 0.5));
p.setY(frameHeight);
}
emit transitionItemSelected(item, getPreviousVideoTrack(info.track), p, true);
}
}
void CustomTrackView::dragMoveEvent(QDragMoveEvent * event)
{
if (m_clipDrag) {
const QPointF pos = mapToScene(event->pos());
if (m_selectionGroup) {
m_selectionGroup->setPos(pos);
emit mousePosition((int)(m_selectionGroup->scenePos().x() + 0.5));
event->acceptProposedAction();
} else if (m_dragItem) {
m_dragItem->setPos(pos);
emit mousePosition((int)(m_dragItem->scenePos().x() + 0.5));
event->acceptProposedAction();
} else {
// Drag enter was not possible, try again at mouse position
insertDropClips(event->mimeData(), event->pos());
event->accept();
}
}
else {
QGraphicsView::dragMoveEvent(event);
}
}
void CustomTrackView::dragLeaveEvent(QDragLeaveEvent * event)
{
if ((m_selectionGroup || m_dragItem) && m_clipDrag) {
QList items;
QMutexLocker lock(&m_selectionMutex);
if (m_selectionGroup) items = m_selectionGroup->childItems();
else if (m_dragItem) {
m_dragItem->setMainSelectedClip(false);
items.append(m_dragItem);
}
qDeleteAll(items);
if (m_selectionGroup) scene()->destroyItemGroup(m_selectionGroup);
m_selectionGroup = NULL;
m_dragItem = NULL;
event->accept();
}
else {
QGraphicsView::dragLeaveEvent(event);
}
}
void CustomTrackView::enterEvent(QEvent * event)
{
if (m_tool == RazorTool && !m_cutLine) {
m_cutLine = m_scene->addLine(0, 0, 0, m_tracksHeight * m_scene->scale().y());
m_cutLine->setZValue(1000);
QPen pen1 = QPen();
pen1.setWidth(1);
QColor line(Qt::red);
pen1.setColor(line);
m_cutLine->setPen(pen1);
m_cutLine->setFlag(QGraphicsItem::ItemIgnoresTransformations, true);
}
QGraphicsView::enterEvent(event);
}
void CustomTrackView::leaveEvent(QEvent * event)
{
removeTipAnimation();
if (m_cutLine) {
delete m_cutLine;
m_cutLine = NULL;
}
QGraphicsView::leaveEvent(event);
}
void CustomTrackView::dropEvent(QDropEvent * event)
{
GenTime startPos;
GenTime duration;
if ((m_selectionGroup || m_dragItem) && m_clipDrag) {
QList items;
if (m_selectionGroup) {
items = m_selectionGroup->childItems();
startPos = GenTime((int) m_selectionGroup->scenePos().x(), m_document->fps());
duration = m_selectionGroup->duration();
} else if (m_dragItem) {
m_dragItem->setMainSelectedClip(false);
startPos = m_dragItem->startPos();
duration = m_dragItem->cropDuration();
items.append(m_dragItem);
}
resetSelectionGroup();
m_dragItem = NULL;
m_scene->clearSelection();
bool hasVideoClip = false;
QUndoCommand *addCommand = new QUndoCommand();
addCommand->setText(i18n("Add timeline clip"));
QList brokenClips;
// Add refresh command for undo
ItemInfo undoRange;
undoRange.startPos = startPos;
undoRange.endPos = startPos + duration;
new RefreshMonitorCommand(this, undoRange, false, true, addCommand);
for (int i = 0; i < items.count(); ++i) {
m_scene->removeItem(items.at(i));
}
if (m_scene->editMode() == TimelineMode::InsertEdit) {
cutTimeline(startPos.frames(m_document->fps()), QList (), QList (), addCommand);
ItemInfo info;
info.startPos = startPos;
info.cropDuration = duration;
new AddSpaceCommand(this, info, QList (), true, addCommand);
} else if (m_scene->editMode() == TimelineMode::OverwriteEdit) {
extractZone(QPoint(startPos.frames(m_document->fps()), (startPos+duration).frames(m_document->fps())), false, QList (), addCommand);
}
for (int i = 0; i < items.count(); ++i) {
ClipItem *item = static_cast (items.at(i));
ClipType cType = item->clipType();
if (!hasVideoClip && (cType == AV || cType == Video)) hasVideoClip = true;
if (items.count() == 1) {
updateClipTypeActions(item);
} else {
updateClipTypeActions(NULL);
}
ItemInfo info = item->info();
QString clipBinId = item->getBinId();
PlaylistState::ClipState pState = item->clipState();
if (KdenliveSettings::splitaudio()) {
if (m_timeline->getTrackInfo(info.track).type == AudioTrack) {
if (cType != Audio)
pState = PlaylistState::AudioOnly;
} else if (item->isSplittable()) {
pState = PlaylistState::VideoOnly;
}
}
new AddTimelineClipCommand(this, clipBinId, info, item->effectList(), pState, true, false, addCommand);
// Automatic audio split
if (KdenliveSettings::splitaudio() && item->isSplittable() && pState != PlaylistState::AudioOnly)
splitAudio(false, info, m_timeline->audioTarget, addCommand);
else
updateTrackDuration(info.track, addCommand);
if (item->binClip()->isTransparent() && getTransitionItemAtStart(info.startPos, info.track) == NULL) {
// add transparency transition if space is available
if (canBePastedTo(info, TransitionWidget)) {
QDomElement trans = MainWindow::transitions.getEffectByTag(QStringLiteral("affine"), QString()).cloneNode().toElement();
new AddTransitionCommand(this, info, getPreviousVideoTrack(info.track), trans, false, true, addCommand);
}
}
}
qDeleteAll(items);
// Add refresh command for redo
new RefreshMonitorCommand(this, undoRange, true, false, addCommand);
if (addCommand->childCount() > 0) m_commandStack->push(addCommand);
else delete addCommand;
/*
// debug info
QRectF rect(0, 1 * m_tracksHeight + m_tracksHeight / 2, sceneRect().width(), 2);
QList selection = m_scene->items(rect);
QStringList timelineList;
//qDebug()<<"// ITEMS on TRACK: "<type() == AVWidget) {
ClipItem *clip = static_cast (selection.at(i));
int start = clip->startPos().frames(m_document->fps());
int end = clip->endPos().frames(m_document->fps());
timelineList.append(QString::number(start) + '-' + QString::number(end));
}
}
//qDebug() << "// COMPARE:\n" << timelineList << "\n-------------------";
*/
/*
m_pasteEffectsAction->setEnabled(m_copiedItems.count() == 1);
if (items.count() > 1) {
groupSelectedItems(items);
} else if (items.count() == 1) {
m_dragItem = static_cast (items.at(0));
m_dragItem->setMainSelectedClip(true);
emit clipItemSelected(static_cast(m_dragItem), false);
}*/
event->setDropAction(Qt::MoveAction);
event->accept();
/// \todo enable when really working
// alignAudio();
}
else {
QGraphicsView::dropEvent(event);
}
setFocus();
}
void CustomTrackView::cutTimeline(int cutPos, QList excludedClips, QList excludedTransitions, QUndoCommand *masterCommand, int track)
{
QRectF rect;
if (track == -1) {
// Cut all tracks
rect = QRectF(cutPos, 0, 1, m_timeline->visibleTracksCount() * m_tracksHeight);
} else {
// Cut only selected track
rect = QRectF(cutPos, getPositionFromTrack(track) + m_tracksHeight / 2, 1, 2);
}
QList selection = m_scene->items(rect);
// We are going to move clips that are after zone, so break locked groups first.
QList clipsToCut;
QList transitionsToCut;
for (int i = 0; i < selection.count(); ++i) {
AbstractClipItem *item = static_cast (selection.at(i));
if (!item || !item->isEnabled() || !item->parentItem()) continue;
ItemInfo moveInfo = item->info();
// Skip locked tracks
if (m_timeline->getTrackInfo(moveInfo.track).isLocked)
continue;
if (item->type() == AVWidget) {
if (excludedClips.contains(moveInfo)) {
continue;
}
clipsToCut.append(moveInfo);
} else if (item->type() == TransitionWidget) {
if (excludedTransitions.contains(moveInfo)) {
continue;
}
transitionsToCut.append(moveInfo);
}
}
if (!clipsToCut.isEmpty() || !transitionsToCut.isEmpty()) {
breakLockedGroups(clipsToCut, transitionsToCut, masterCommand);
}
// Razor
GenTime cutPosition = GenTime(cutPos, m_document->fps());
for (int i = 0; i < selection.count(); ++i) {
if (!selection.at(i)->isEnabled()) continue;
if (selection.at(i)->type() == AVWidget) {
ClipItem *clip = static_cast(selection.at(i));
// Skip locked tracks
if (m_timeline->getTrackInfo(clip->track()).isLocked)
continue;
ItemInfo info = clip->info();
if (excludedClips.contains(info) || cutPosition == info.startPos) {
continue;
}
new RazorClipCommand(this, info, clip->effectList(), cutPosition, true, masterCommand);
} else if (selection.at(i)->type() == TransitionWidget) {
Transition *trans = static_cast(selection.at(i));
// Skip locked tracks
if (m_timeline->getTrackInfo(trans->track()).isLocked)
continue;
ItemInfo info = trans->info();
if (excludedTransitions.contains(info) || cutPosition == info.startPos) {
continue;
}
new RazorTransitionCommand(this, info, trans->toXML(), cutPosition, true, masterCommand);
}
}
}
void CustomTrackView::extractZone(QPoint z, bool closeGap, QList excludedClips, QUndoCommand *masterCommand, int track)
{
// remove track zone and close gap
if (closeGap && m_timeline->getTrackInfo(m_selectedTrack).isLocked) {
// Cannot perform an Extract operation on a locked track
emit displayMessage(i18n("Cannot perform operation on a locked track"), ErrorMessage);
return;
}
if (z.isNull()) {
z = m_document->zone();
z.setY(z.y() + 1);
}
QRectF rect;
if (track == -1) {
// All tracks
rect = QRectF(z.x(), 0, z.y() - z.x() - 1, m_timeline->visibleTracksCount() * m_tracksHeight);
} else {
// All tracks
rect = QRectF(z.x(), getPositionFromTrack(track) + m_tracksHeight / 2, z.y() - z.x() - 1, 2);
}
QList selection = m_scene->items(rect);
QList gapSelection;
if (selection.isEmpty())
return;
GenTime inPoint(z.x(), m_document->fps());
GenTime outPoint(z.y(), m_document->fps());
bool hasMasterCommand = masterCommand != NULL;
if (!hasMasterCommand) {
masterCommand = new QUndoCommand();
masterCommand->setText(i18n("Remove Zone"));
}
if (closeGap) {
// We are going to move clips that are after zone, so break locked groups first.
QRectF gapRect = QRectF(z.x(), 0, sceneRect().width() - z.x(), m_timeline->visibleTracksCount() * m_tracksHeight);
gapSelection = m_scene->items(gapRect);
QList clipsToMove;
QList transitionsToMove;
for (int i = 0; i < gapSelection.count(); ++i) {
if (!gapSelection.at(i)->isEnabled()) continue;
if (gapSelection.at(i)->type() == AVWidget) {
ClipItem *clip = static_cast(gapSelection.at(i));
// Skip locked tracks
if (m_timeline->getTrackInfo(clip->track()).isLocked)
continue;
ItemInfo moveInfo = clip->info();
if (clip->type() == AVWidget) {
clipsToMove.append(moveInfo);
} else if (clip->type() == TransitionWidget) {
transitionsToMove.append(moveInfo);
}
}
}
if (!clipsToMove.isEmpty() || !transitionsToMove.isEmpty()) {
breakLockedGroups(clipsToMove, transitionsToMove, masterCommand);
}
}
for (int i = 0; i < selection.count(); ++i) {
if (!selection.at(i)->isEnabled()) continue;
if (selection.at(i)->type() == AVWidget) {
ClipItem *clip = static_cast(selection.at(i));
// Skip locked tracks
if (m_timeline->getTrackInfo(clip->track()).isLocked)
continue;
ItemInfo baseInfo = clip->info();
if (excludedClips.contains(baseInfo))
continue;
if (clip->startPos() < inPoint) {
ItemInfo info = baseInfo;
info.startPos = inPoint;
info.cropDuration = info.endPos - info.startPos;
if (clip->endPos() > outPoint) {
new RazorClipCommand(this, baseInfo, clip->effectList(), outPoint, true, masterCommand);
info.cropDuration = outPoint - inPoint;
info.endPos = outPoint;
baseInfo.endPos = outPoint;
baseInfo.cropDuration = outPoint - baseInfo.startPos;
}
new RazorClipCommand(this, baseInfo, clip->effectList(), inPoint, true, masterCommand);
new AddTimelineClipCommand(this, clip->getBinId(), info, clip->effectList(), clip->clipState(), true, true, masterCommand);
} else if (clip->endPos() > outPoint) {
new RazorClipCommand(this, baseInfo, clip->effectList(), outPoint, true, masterCommand);
ItemInfo newInfo = baseInfo;
newInfo.endPos = outPoint;
newInfo.cropDuration = newInfo.endPos - newInfo.startPos;
new AddTimelineClipCommand(this, clip->getBinId(), newInfo, clip->effectList(), clip->clipState(), true, true, masterCommand);
} else {
// Clip is entirely inside zone, delete it
new AddTimelineClipCommand(this, clip->getBinId(), baseInfo, clip->effectList(), clip->clipState(), true, true, masterCommand);
}
} else if (selection.at(i)->type() == TransitionWidget) {
Transition *trans = static_cast(selection.at(i));
// Skip locked tracks
if (m_timeline->getTrackInfo(trans->track()).isLocked)
continue;
ItemInfo baseInfo = trans->info();
if (excludedClips.contains(baseInfo))
continue;
QDomElement xml = trans->toXML();
if (trans->startPos() < inPoint) {
ItemInfo info = baseInfo;
ItemInfo newInfo = baseInfo;
info.startPos = inPoint;
info.cropDuration = info.endPos - info.startPos;
if (trans->endPos() > outPoint) {
// Transition starts before and ends after zone, proceed cuts
new RazorTransitionCommand(this, baseInfo, xml, outPoint, true, masterCommand);
info.cropDuration = outPoint - inPoint;
info.endPos = outPoint;
newInfo.endPos = outPoint;
newInfo.cropDuration = outPoint - newInfo.startPos;
}
new RazorTransitionCommand(this, newInfo, xml, inPoint, true, masterCommand);
new AddTransitionCommand(this, info, trans->transitionEndTrack(), xml, true, true, masterCommand);
} else if (trans->endPos() > outPoint) {
// Cut and remove first part
new RazorTransitionCommand(this, baseInfo, xml, outPoint, true, masterCommand);
ItemInfo info = baseInfo;
info.endPos = outPoint;
info.cropDuration = info.endPos - info.startPos;
new AddTransitionCommand(this, info, trans->transitionEndTrack(), xml, true, true, masterCommand);
} else {
// Transition is entirely inside zone, delete it
new AddTransitionCommand(this, baseInfo, trans->transitionEndTrack(), xml, true, true, masterCommand);
}
}
}
if (closeGap) {
// Remove empty zone
QList clipsToMove;
QList transitionsToMove;
for (int i = 0; i < gapSelection.count(); ++i) {
if (gapSelection.at(i)->type() == AVWidget || gapSelection.at(i)->type() == TransitionWidget) {
AbstractClipItem *item = static_cast (gapSelection.at(i));
if (m_timeline->getTrackInfo(item->track()).isLocked) {
continue;
}
ItemInfo moveInfo = item->info();
if (item->type() == AVWidget) {
if (moveInfo.startPos < outPoint) {
if (moveInfo.endPos <= outPoint) {
continue;
}
moveInfo.startPos = outPoint;
moveInfo.cropDuration = moveInfo.endPos - moveInfo.startPos;
}
clipsToMove.append(moveInfo);
}
else if (item->type() == TransitionWidget) {
if (moveInfo.startPos < outPoint) {
if (moveInfo.endPos <= outPoint) {
continue;
}
moveInfo.startPos = outPoint;
moveInfo.cropDuration = moveInfo.endPos - moveInfo.startPos;
}
transitionsToMove.append(moveInfo);
}
}
}
if (!clipsToMove.isEmpty() || !transitionsToMove.isEmpty()) {
new InsertSpaceCommand(this, clipsToMove, transitionsToMove, -1, -(outPoint - inPoint), true, masterCommand);
updateTrackDuration(-1, masterCommand);
}
}
if (!hasMasterCommand)
m_commandStack->push(masterCommand);
}
void CustomTrackView::adjustTimelineTransitions(TimelineMode::EditMode mode, Transition *item, QUndoCommand *command)
{
if (mode == TimelineMode::OverwriteEdit) {
// if we are in overwrite or push mode, move clips accordingly
bool snap = KdenliveSettings::snaptopoints();
KdenliveSettings::setSnaptopoints(false);
ItemInfo info = item->info();
QRectF rect(info.startPos.frames(m_document->fps()), getPositionFromTrack(info.track) + m_tracksHeight, (info.endPos - info.startPos).frames(m_document->fps()) - 1, 5);
QList selection = m_scene->items(rect);
selection.removeAll(item);
for (int i = 0; i < selection.count(); ++i) {
if (!selection.at(i)->isEnabled()) continue;
if (selection.at(i)->type() == TransitionWidget) {
Transition *tr = static_cast(selection.at(i));
if (tr->startPos() < info.startPos) {
ItemInfo firstPos = tr->info();
ItemInfo newPos = firstPos;
firstPos.endPos = item->startPos();
newPos.startPos = item->endPos();
new MoveTransitionCommand(this, tr->info(), firstPos, true, command);
if (tr->endPos() > info.endPos) {
// clone transition
new AddTransitionCommand(this, newPos, tr->transitionEndTrack(), tr->toXML(), false, true, command);
}
} else if (tr->endPos() > info.endPos) {
// just resize
ItemInfo firstPos = tr->info();
firstPos.startPos = item->endPos();
new MoveTransitionCommand(this, tr->info(), firstPos, true, command);
} else {
// remove transition
new AddTransitionCommand(this, tr->info(), tr->transitionEndTrack(), tr->toXML(), true, true, command);
}
}
}
KdenliveSettings::setSnaptopoints(snap);
}
}
QStringList CustomTrackView::mimeTypes() const
{
QStringList qstrList;
// list of accepted mime types for drop
qstrList.append(QStringLiteral("text/plain"));
qstrList.append(QStringLiteral("kdenlive/producerslist"));
qstrList.append(QStringLiteral("kdenlive/clip"));
return qstrList;
}
Qt::DropActions CustomTrackView::supportedDropActions() const
{
// returns what actions are supported when dropping
return Qt::MoveAction;
}
void CustomTrackView::setDuration(int duration)
{
if (m_projectDuration == duration) return;
int diff = qAbs(duration - sceneRect().width());
if (diff * matrix().m11() > -50) {
if (matrix().m11() < 0.4) setSceneRect(0, 0, (duration + 100 / matrix().m11()), sceneRect().height());
else setSceneRect(0, 0, (duration + 300), sceneRect().height());
}
m_projectDuration = duration;
}
int CustomTrackView::duration() const
{
return m_projectDuration;
}
void CustomTrackView::addTrack(const TrackInfo &type, int ix)
{
clearSelection();
emit transitionItemSelected(NULL);
QList transitionInfos;
if (ix == -1 || ix > m_timeline->tracksCount()) {
ix = m_timeline->tracksCount() + 1;
}
if (ix <= m_timeline->videoTarget)
m_timeline->videoTarget++;
if (ix <= m_timeline->audioTarget)
m_timeline->audioTarget++;
int lowestVideoTrack = m_timeline->getLowestVideoTrack();
// Prepare groups for reload
QDomDocument doc;
doc.setContent(m_document->groupsXml());
QDomNodeList groups;
if (!doc.isNull()) {
groups = doc.documentElement().elementsByTagName("group");
for (int nodeindex = 0; nodeindex < groups.count(); ++nodeindex) {
QDomNode grp = groups.item(nodeindex);
QDomNodeList nodes = grp.childNodes();
for (int itemindex = 0; itemindex < nodes.count(); ++itemindex) {
QDomElement elem = nodes.item(itemindex).toElement();
if (!elem.hasAttribute("track")) continue;
int track = elem.attribute("track").toInt();
if (track <= ix) {
// No change
continue;
}
else {
elem.setAttribute("track", track + 1);
}
}
}
}
// insert track in MLT's playlist
transitionInfos = m_document->renderer()->mltInsertTrack(ix, type.trackName, type.type == VideoTrack, lowestVideoTrack);
Mlt::Tractor *tractor = m_document->renderer()->lockService();
// When adding a track, MLT sometimes incorrectly updates transition's tracks
if (ix < m_timeline->tracksCount()) {
Mlt::Transition *tr = m_timeline->transitionHandler->getTransition(KdenliveSettings::gpu_accel() ? "movit.overlay" : "frei0r.cairoblend", ix+1, ix - 1, true);
if (tr) {
tr->set_tracks(ix, ix + 1);
delete tr;
}
}
// Check we have composite transitions where necessary
m_timeline->updateComposites();
m_document->renderer()->unlockService(tractor);
// Reload timeline and m_tracks structure from MLT's playlist
reloadTimeline();
loadGroups(groups);
}
void CustomTrackView::reloadTimeline()
{
removeTipAnimation();
emit clipItemSelected(NULL);
emit transitionItemSelected(NULL);
QList selection = m_scene->items();
selection.removeAll(m_cursorLine);
for (int i = 0; i < m_guides.count(); ++i) {
selection.removeAll(m_guides.at(i));
}
qDeleteAll<>(selection);
m_timeline->getTracks();
m_timeline->getTransitions();
int maxHeight = m_tracksHeight * m_timeline->visibleTracksCount() * matrix().m22();
for (int i = 0; i < m_guides.count(); ++i) {
m_guides.at(i)->setLine(0, 0, 0, maxHeight - 1);
}
m_cursorLine->setLine(0, 0, 0, maxHeight - 1);
viewport()->update();
}
void CustomTrackView::removeTrack(int ix)
{
// Clear effect stack
clearSelection();
emit transitionItemSelected(NULL);
// Make sure the selected track index is not outside range
m_selectedTrack = qBound(1, m_selectedTrack, m_timeline->tracksCount() - 2);
if (ix == m_timeline->audioTarget) {
m_timeline->audioTarget = -1;
} else if (m_timeline->audioTarget > ix) {
m_timeline->audioTarget--;
}
if (ix == m_timeline->videoTarget) {
m_timeline->videoTarget = -1;
} else if (m_timeline->videoTarget > ix) {
m_timeline->videoTarget--;
}
//Delete composite transition
Mlt::Tractor *tractor = m_document->renderer()->lockService();
QScopedPointer field(tractor->field());
if (m_timeline->getTrackInfo(ix).type == VideoTrack) {
QScopedPointer tr(m_timeline->transitionHandler->getTransition(KdenliveSettings::gpu_accel() ? "movit.overlay" : "frei0r.cairoblend", ix, -1, true));
if (tr) {
field->disconnect_service(*tr.data());
}
QScopedPointer mixTr(m_timeline->transitionHandler->getTransition(QStringLiteral("mix"), ix, -1, true));
if (mixTr) {
field->disconnect_service(*mixTr.data());
}
}
// Prepare groups for reload
QDomDocument doc;
doc.setContent(m_document->groupsXml());
QDomNodeList groups;
if (!doc.isNull()) {
groups = doc.documentElement().elementsByTagName("group");
for (int nodeindex = 0; nodeindex < groups.count(); ++nodeindex) {
QDomNode grp = groups.item(nodeindex);
QDomNodeList nodes = grp.childNodes();
for (int itemindex = 0; itemindex < nodes.count(); ++itemindex) {
QDomElement elem = nodes.item(itemindex).toElement();
if (!elem.hasAttribute("track")) continue;
int track = elem.attribute("track").toInt();
if (track < ix) {
// No change
continue;
}
else if (track > ix) {
elem.setAttribute("track", track - 1);
}
else {
// track == ix
// A grouped item was on deleted track, remove it from group
elem.setAttribute("track", -1);
}
}
}
}
//Manually remove all transitions issued from track ix, otherwise MLT will relocate it to another track
m_timeline->transitionHandler->deleteTrackTransitions(ix);
// Delete track in MLT playlist
tractor->remove_track(ix);
// Make sure lowest video track has no composite
m_timeline->updateComposites();
m_document->renderer()->unlockService(tractor);
reloadTimeline();
loadGroups(groups);
}
void CustomTrackView::configTracks(const QList < TrackInfo > &trackInfos)
{
//TODO: fix me, use UNDO/REDO
for (int i = 0; i < trackInfos.count(); ++i) {
m_timeline->setTrackInfo(m_timeline->visibleTracksCount() - i, trackInfos.at(i));
}
viewport()->update();
}
void CustomTrackView::slotSwitchTrackAudio(int ix, bool enable)
{
m_timeline->switchTrackAudio(ix, enable);
m_document->renderer()->doRefresh();
}
void CustomTrackView::slotSwitchTrackLock(int ix, bool enable, bool applyToAll)
{
QUndoCommand *command = NULL;
if (!applyToAll) {
command = new LockTrackCommand(this, ix, enable);
} else {
command = new QUndoCommand;
command->setText(i18n("Switch All Track Lock"));
for (int i = 1; i <= m_timeline->visibleTracksCount(); ++i) {
if (i == ix)
continue;
new LockTrackCommand(this, i, enable, command);
}
}
m_commandStack->push(command);
}
void CustomTrackView::lockTrack(int ix, bool lock, bool requestUpdate)
{
m_timeline->lockTrack(ix, lock);
if (requestUpdate)
emit doTrackLock(ix, lock);
AbstractClipItem *clip = NULL;
QList selection = m_scene->items(QRectF(0, getPositionFromTrack(ix) + m_tracksHeight / 2, sceneRect().width(), m_tracksHeight / 2 - 2));
for (int i = 0; i < selection.count(); ++i) {
if (selection.at(i)->type() == GroupWidget && static_cast(selection.at(i)) != m_selectionGroup) {
if (selection.at(i)->parentItem() && m_selectionGroup) {
selection.removeAll(static_cast(m_selectionGroup));
resetSelectionGroup();
}
bool changeGroupLock = true;
bool hasClipOnTrack = false;
QList children = selection.at(i)->childItems();
for (int j = 0; j < children.count(); ++j) {
if (children.at(j)->isSelected()) {
if (children.at(j)->type() == AVWidget)
emit clipItemSelected(NULL);
else if (children.at(j)->type() == TransitionWidget)
emit transitionItemSelected(NULL);
else
continue;
}
AbstractClipItem * child = static_cast (children.at(j));
if (child) {
if (child == m_dragItem) {
m_dragItem->setMainSelectedClip(false);
m_dragItem = NULL;
}
// only unlock group, if it is not locked by another track too
if (!lock && child->track() != ix && m_timeline->getTrackInfo(child->track()).isLocked)
changeGroupLock = false;
// only (un-)lock if at least one clip is on the track
if (child->track() == ix)
hasClipOnTrack = true;
}
}
if (changeGroupLock && hasClipOnTrack)
static_cast(selection.at(i))->setItemLocked(lock);
} else if((selection.at(i)->type() == AVWidget || selection.at(i)->type() == TransitionWidget)) {
if (selection.at(i)->parentItem()) {
if (selection.at(i)->parentItem() == m_selectionGroup) {
selection.removeAll(static_cast(m_selectionGroup));
resetSelectionGroup();
} else {
// groups are handled separately
continue;
}
}
if (selection.at(i)->isSelected()) {
if (selection.at(i)->type() == AVWidget)
emit clipItemSelected(NULL);
else
emit transitionItemSelected(NULL);
}
clip = static_cast (selection.at(i));
clip->setItemLocked(lock);
if (clip == m_dragItem) {
m_dragItem->setMainSelectedClip(false);
m_dragItem = NULL;
}
}
}
//qDebug() << "NEXT TRK STATE: " << m_timeline->getTrackInfo(tracknumber).isLocked;
viewport()->update();
}
void CustomTrackView::slotSwitchTrackVideo(int ix, bool enable)
{
m_timeline->switchTrackVideo(ix, enable);
m_document->renderer()->doRefresh();
//TODO: create undo/redo command for this
setDocumentModified();
}
QList CustomTrackView::checkForGroups(const QRectF &rect, bool *ok)
{
// Check there is no group going over several tracks there, or that would result in timeline corruption
QList selection = scene()->items(rect);
*ok = true;
int maxHeight = m_tracksHeight * 1.5;
for (int i = 0; i < selection.count(); ++i) {
// Check that we don't try to move a group with clips on other tracks
if (selection.at(i)->type() == GroupWidget && (selection.at(i)->boundingRect().height() >= maxHeight)) {
*ok = false;
break;
} else if (selection.at(i)->parentItem() && (selection.at(i)->parentItem()->boundingRect().height() >= maxHeight)) {
*ok = false;
break;
}
}
return selection;
}
void CustomTrackView::slotRemoveSpace()
{
GenTime pos;
int track = 0;
if (m_menuPosition.isNull()) {
pos = GenTime(cursorPos(), m_document->fps());
QPointer d = new TrackDialog(m_timeline, parentWidget());
d->comboTracks->setCurrentIndex(m_selectedTrack);
d->label->setText(i18n("Track"));
d->track_name->setHidden(true);
d->name_label->setHidden(true);
d->before_select->setHidden(true);
d->setWindowTitle(i18n("Remove Space"));
d->video_track->setHidden(true);
d->audio_track->setHidden(true);
if (d->exec() != QDialog::Accepted) {
delete d;
return;
}
// TODO check that this is the correct index
track = m_timeline->visibleTracksCount() - d->comboTracks->currentIndex();
delete d;
} else {
pos = GenTime((int)(mapToScene(m_menuPosition).x()), m_document->fps());
track = getTrackFromPos(mapToScene(m_menuPosition).y());
}
if (m_timeline->isTrackLocked(track)) {
emit displayMessage(i18n("Cannot remove space in a locked track"), ErrorMessage);
return;
}
ClipItem *item = getClipItemAtMiddlePoint(pos.frames(m_document->fps()), track);
if (item) {
emit displayMessage(i18n("You must be in an empty space to remove space (time: %1, track: %2)", m_document->timecode().getTimecodeFromFrames(mapToScene(m_menuPosition).x()), track), ErrorMessage);
return;
}
int length = m_timeline->getSpaceLength(pos, track, true);
if (length <= 0) {
emit displayMessage(i18n("You must be in an empty space to remove space (time: %1, track: %2)", m_document->timecode().getTimecodeFromFrames(mapToScene(m_menuPosition).x()), track), ErrorMessage);
return;
}
// Make sure there is no group in the way
QRectF rect(pos.frames(m_document->fps()), getPositionFromTrack(track) + m_tracksHeight / 2, sceneRect().width() - pos.frames(m_document->fps()), m_tracksHeight / 2 - 2);
bool isOk;
QList items = checkForGroups(rect, &isOk);
if (!isOk) {
// groups found on track, do not allow the move
emit displayMessage(i18n("Cannot remove space in a track with a group"), ErrorMessage);
return;
}
QList clipsToMove;
QList transitionsToMove;
for (int i = 0; i < items.count(); ++i) {
if (items.at(i)->type() == AVWidget || items.at(i)->type() == TransitionWidget) {
AbstractClipItem *item = static_cast (items.at(i));
ItemInfo info = item->info();
if (item->type() == AVWidget) {
clipsToMove.append(info);
} else if (item->type() == TransitionWidget) {
transitionsToMove.append(info);
}
}
}
if (!transitionsToMove.isEmpty()) {
// Make sure that by moving the items, we don't get a transition collision
// Find first transition
ItemInfo info = transitionsToMove.at(0);
for (int i = 1; i < transitionsToMove.count(); ++i)
if (transitionsToMove.at(i).startPos < info.startPos) info = transitionsToMove.at(i);
// make sure there are no transitions on the way
QRectF rect(info.startPos.frames(m_document->fps()) - length, getPositionFromTrack(track) + m_tracksHeight / 2, length - 1, m_tracksHeight / 2 - 2);
items = scene()->items(rect);
int transitionCorrection = -1;
for (int i = 0; i < items.count(); ++i) {
if (items.at(i)->type() == TransitionWidget) {
// There is a transition on the way
AbstractClipItem *item = static_cast (items.at(i));
int transitionEnd = item->endPos().frames(m_document->fps());
if (transitionEnd > transitionCorrection) transitionCorrection = transitionEnd;
}
}
if (transitionCorrection > 0) {
// We need to fix the move length
length = info.startPos.frames(m_document->fps()) - transitionCorrection;
}
// Make sure we don't send transition before 0
if (info.startPos.frames(m_document->fps()) < length) {
// reduce length to maximum possible
length = info.startPos.frames(m_document->fps());
}
}
QUndoCommand *command = new QUndoCommand;
command->setText(length > 0 ? i18n("Remove space") : i18n("Insert space"));
breakLockedGroups(clipsToMove, transitionsToMove, command);
new InsertSpaceCommand(this, clipsToMove, transitionsToMove, track, GenTime(-length, m_document->fps()), true, command);
updateTrackDuration(track, command);
m_commandStack->push(command);
}
void CustomTrackView::slotInsertSpace()
{
int pos;
int track = 0;
if (m_menuPosition.isNull()) {
pos = cursorPos();
} else {
pos = (int) mapToScene(m_menuPosition).x();
track = getTrackFromPos(mapToScene(m_menuPosition).y());
}
QPointer d = new SpacerDialog(GenTime(65, m_document->fps()),
m_document->timecode(), track, m_timeline->getTracksInfo(), this);
if (d->exec() != QDialog::Accepted) {
delete d;
return;
}
GenTime spaceDuration = d->selectedDuration();
track = d->selectedTrack();
delete d;
QList items;
if (track >= 0) {
if (m_timeline->isTrackLocked(track)) {
emit displayMessage(i18n("Cannot insert space in a locked track"), ErrorMessage);
return;
}
ClipItem *item = getClipItemAtMiddlePoint(pos, track);
if (item) pos = item->startPos().frames(m_document->fps());
// Make sure there is no group in the way
QRectF rect(pos, getPositionFromTrack(track) + m_tracksHeight / 2, sceneRect().width() - pos, m_tracksHeight / 2 - 2);
bool isOk;
items = checkForGroups(rect, &isOk);
if (!isOk) {
// groups found on track, do not allow the move
emit displayMessage(i18n("Cannot insert space in a track with a group"), ErrorMessage);
return;
}
} else {
QRectF rect(pos, 0, sceneRect().width() - pos, m_timeline->visibleTracksCount() * m_tracksHeight);
items = scene()->items(rect);
}
QList clipsToMove;
QList transitionsToMove;
for (int i = 0; i < items.count(); ++i) {
if (items.at(i)->type() == AVWidget || items.at(i)->type() == TransitionWidget) {
AbstractClipItem *item = static_cast