diff --git a/src/doc/kdenlivedoc.cpp b/src/doc/kdenlivedoc.cpp index bdac317dc..729d99a0e 100644 --- a/src/doc/kdenlivedoc.cpp +++ b/src/doc/kdenlivedoc.cpp @@ -1,1673 +1,1714 @@ /*************************************************************************** * 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 "kdenlivedoc.h" #include "documentchecker.h" #include "documentvalidator.h" #include "mltcontroller/clipcontroller.h" #include "mltcontroller/producerqueue.h" #include #include "kdenlivesettings.h" #include "renderer.h" #include "mainwindow.h" #include "project/clipmanager.h" #include "project/projectcommands.h" #include "bin/bincommands.h" #include "effectslist/initeffects.h" #include "dialogs/profilesdialog.h" #include "titler/titlewidget.h" #include "project/notesplugin.h" #include "project/dialogs/noteswidget.h" #include "core.h" #include "bin/bin.h" #include "bin/projectclip.h" #include "utils/KoIconUtils.h" #include "mltcontroller/bincontroller.h" #include "mltcontroller/effectscontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_MAC #include #endif const double DOCUMENTVERSION = 0.94; KdenliveDoc::KdenliveDoc(const QUrl &url, const QUrl &projectFolder, QUndoGroup *undoGroup, const QString &profileName, const QMap & properties, const QMap & metadata, const QPoint &tracks, Render *render, NotesPlugin *notes, bool *openBackup, MainWindow *parent) : QObject(parent), m_autosave(NULL), m_url(url), m_width(0), m_height(0), m_render(render), m_notesWidget(notes->widget()), m_commandStack(new QUndoStack(undoGroup)), m_modified(false), - m_projectFolder(projectFolder) + m_projectFolder(projectFolder), + m_undoPreviewIndex(-1) { // init m_profile struct m_profile.frame_rate_num = 0; m_profile.frame_rate_den = 0; m_profile.width = 0; m_profile.height = 0; m_profile.progressive = 0; m_profile.sample_aspect_num = 0; m_profile.sample_aspect_den = 0; m_profile.display_aspect_num = 0; m_profile.display_aspect_den = 0; m_profile.colorspace = 0; m_clipManager = new ClipManager(this); connect(m_clipManager, SIGNAL(displayMessage(QString,int)), parent, SLOT(slotGotProgressInfo(QString,int))); bool success = false; - connect(m_commandStack, SIGNAL(indexChanged(int)), this, SLOT(slotModified())); + connect(m_commandStack, SIGNAL(indexChanged(int)), this, SLOT(slotModified(int))); connect(m_render, SIGNAL(setDocumentNotes(QString)), this, SLOT(slotSetDocumentNotes(QString))); connect(pCore->producerQueue(), &ProducerQueue::switchProfile, this, &KdenliveDoc::switchProfile); //connect(m_commandStack, SIGNAL(cleanChanged(bool)), this, SLOT(setModified(bool))); // Init clip modification tracker m_modifiedTimer.setInterval(1500); connect(&m_fileWatcher, &KDirWatch::dirty, this, &KdenliveDoc::slotClipModified); connect(&m_fileWatcher, &KDirWatch::deleted, this, &KdenliveDoc::slotClipMissing); connect(&m_modifiedTimer, &QTimer::timeout, this, &KdenliveDoc::slotProcessModifiedClips); // init default document properties m_documentProperties[QStringLiteral("zoom")] = '7'; m_documentProperties[QStringLiteral("verticalzoom")] = '1'; m_documentProperties[QStringLiteral("zonein")] = '0'; m_documentProperties[QStringLiteral("zoneout")] = QStringLiteral("100"); m_documentProperties[QStringLiteral("enableproxy")] = QString::number((int) KdenliveSettings::enableproxy()); m_documentProperties[QStringLiteral("proxyparams")] = KdenliveSettings::proxyparams(); m_documentProperties[QStringLiteral("proxyextension")] = KdenliveSettings::proxyextension(); m_documentProperties[QStringLiteral("generateproxy")] = QString::number((int) KdenliveSettings::generateproxy()); m_documentProperties[QStringLiteral("proxyminsize")] = QString::number(KdenliveSettings::proxyminsize()); m_documentProperties[QStringLiteral("generateimageproxy")] = QString::number((int) KdenliveSettings::generateimageproxy()); m_documentProperties[QStringLiteral("proxyimageminsize")] = QString::number(KdenliveSettings::proxyimageminsize()); m_documentProperties[QStringLiteral("documentid")] = QString::number(QDateTime::currentMSecsSinceEpoch()); // Load properties QMapIterator i(properties); while (i.hasNext()) { i.next(); m_documentProperties[i.key()] = i.value(); } // Load metadata QMapIterator j(metadata); while (j.hasNext()) { j.next(); m_documentMetadata[j.key()] = j.value(); } if (QLocale().decimalPoint() != QLocale::system().decimalPoint()) { setlocale(LC_NUMERIC, ""); QLocale systemLocale = QLocale::system(); systemLocale.setNumberOptions(QLocale::OmitGroupSeparator); QLocale::setDefault(systemLocale); // locale conversion might need to be redone initEffects::parseEffectFiles(pCore->binController()->mltRepository(), setlocale(LC_NUMERIC, NULL)); } *openBackup = false; if (url.isValid()) { QFile file(url.toLocalFile()); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { // The file cannot be opened if (KMessageBox::warningContinueCancel(parent, i18n("Cannot open the project file,\nDo you want to open a backup file?"), i18n("Error opening file"), KGuiItem(i18n("Open Backup"))) == KMessageBox::Continue) { *openBackup = true; } //KMessageBox::error(parent, KIO::NetAccess::lastErrorString()); } else { qDebug()<<" // / processing file open"; QString errorMsg; int line; int col; QDomImplementation::setInvalidDataPolicy(QDomImplementation::DropInvalidChars); success = m_document.setContent(&file, false, &errorMsg, &line, &col); file.close(); if (!success) { // It is corrupted int answer = KMessageBox::warningYesNoCancel (parent, i18n("Cannot open the project file, error is:\n%1 (line %2, col %3)\nDo you want to open a backup file?", errorMsg, line, col), i18n("Error opening file"), KGuiItem(i18n("Open Backup")), KGuiItem(i18n("Recover"))); if (answer == KMessageBox::Yes) { *openBackup = true; } else if (answer == KMessageBox::No) { // Try to recover broken file produced by Kdenlive 0.9.4 if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { int correction = 0; QString playlist = file.readAll(); while (!success && correction < 2) { int errorPos = 0; line--; col = col - 2; for (int j = 0; j < line && errorPos < playlist.length(); ++j) { errorPos = playlist.indexOf(QStringLiteral("\n"), errorPos); errorPos++; } errorPos += col; if (errorPos >= playlist.length()) break; playlist.remove(errorPos, 1); line = 0; col = 0; success = m_document.setContent(playlist, false, &errorMsg, &line, &col); correction++; } if (!success) { KMessageBox::sorry(parent, i18n("Cannot recover this project file")); } else { // Document was modified, ask for backup QDomElement mlt = m_document.documentElement(); mlt.setAttribute(QStringLiteral("modified"), 1); } } } } else { qDebug()<<" // / processing file open: validate"; parent->slotGotProgressInfo(i18n("Validating"), 0); qApp->processEvents(); DocumentValidator validator(m_document, url); success = validator.isProject(); if (!success) { // It is not a project file parent->slotGotProgressInfo(i18n("File %1 is not a Kdenlive project file", m_url.path()), 100); if (KMessageBox::warningContinueCancel(parent, i18n("File %1 is not a valid project file.\nDo you want to open a backup file?", m_url.path()), i18n("Error opening file"), KGuiItem(i18n("Open Backup"))) == KMessageBox::Continue) { *openBackup = true; } } else { /* * Validate the file against the current version (upgrade * and recover it if needed). It is NOT a passive operation */ // TODO: backup the document or alert the user? success = validator.validate(DOCUMENTVERSION); if (success && !KdenliveSettings::gpu_accel()) { success = validator.checkMovit(); } if (success) { // Let the validator handle error messages qDebug()<<" // / processing file validate ok"; parent->slotGotProgressInfo(i18n("Check missing clips"), 0); qApp->processEvents(); DocumentChecker d(m_url, m_document); success = !d.hasErrorInClips(); if (success) { loadDocumentProperties(); if (m_document.documentElement().attribute(QStringLiteral("modified")) == QLatin1String("1")) setModified(true); if (validator.isModified()) setModified(true); } } } } } } // Something went wrong, or a new file was requested: create a new project if (!success) { m_url.clear(); m_profile = ProfilesDialog::getVideoProfile(profileName); m_document = createEmptyDocument(tracks.x(), tracks.y()); updateProjectProfile(false); } // Ask to create the project directory if it does not exist QFileInfo checkProjectFolder(m_projectFolder.toString(QUrl::RemoveFilename | QUrl::RemoveScheme)); if (!QFile::exists(m_projectFolder.path()) && checkProjectFolder.isWritable()) { int create = KMessageBox::questionYesNo(parent, i18n("Project directory %1 does not exist. Create it?", m_projectFolder.path())); if (create == KMessageBox::Yes) { QDir projectDir(m_projectFolder.path()); bool ok = projectDir.mkpath(m_projectFolder.path()); if (!ok) { KMessageBox::sorry(parent, i18n("The directory %1, could not be created.\nPlease make sure you have the required permissions.", m_projectFolder.path())); } } } // Make sure the project folder is usable if (m_projectFolder.isEmpty() || !QFile::exists(m_projectFolder.path())) { KMessageBox::information(parent, i18n("Document project folder is invalid, setting it to the default one: %1", KdenliveSettings::defaultprojectfolder())); m_projectFolder = QUrl::fromLocalFile(KdenliveSettings::defaultprojectfolder()); } // Make sure that the necessary folders exist QDir dir(m_projectFolder.path()); dir.mkdir(QStringLiteral("titles")); dir.mkdir(QStringLiteral("thumbs")); dir.mkdir(QStringLiteral("proxy")); dir.mkdir(QStringLiteral(".backup")); updateProjectFolderPlacesEntry(); } void KdenliveDoc::slotSetDocumentNotes(const QString ¬es) { m_notesWidget->setHtml(notes); } KdenliveDoc::~KdenliveDoc() { delete m_commandStack; //qDebug() << "// DEL CLP MAN"; delete m_clipManager; //qDebug() << "// DEL CLP MAN done"; if (m_autosave) { if (!m_autosave->fileName().isEmpty()) m_autosave->remove(); delete m_autosave; } } int KdenliveDoc::setSceneList() { //m_render->resetProfile(m_profile); pCore->bin()->isLoading = true; pCore->producerQueue()->abortOperations(); if (m_render->setSceneList(m_document.toString(), m_documentProperties.value(QStringLiteral("position")).toInt()) == -1) { // INVALID MLT Consumer, something is wrong return -1; } pCore->bin()->isLoading = false; pCore->binController()->checkThumbnails(projectFolder().path() + "/thumbs/"); m_documentProperties.remove(QStringLiteral("position")); pCore->monitorManager()->activateMonitor(Kdenlive::ClipMonitor, true); return 0; } QDomDocument KdenliveDoc::createEmptyDocument(int videotracks, int audiotracks) { QList tracks; // Tracks are added «backwards», so we need to reverse the track numbering // mbt 331: http://www.kdenlive.org/mantis/view.php?id=331 // Better default names for tracks: Audio 1 etc. instead of blank numbers for (int i = 0; i < audiotracks; ++i) { TrackInfo audioTrack; audioTrack.type = AudioTrack; audioTrack.isMute = false; audioTrack.isBlind = true; audioTrack.isLocked = false; audioTrack.trackName = i18n("Audio %1", audiotracks - i); audioTrack.duration = 0; audioTrack.effectsList = EffectsList(true); tracks.append(audioTrack); } for (int i = 0; i < videotracks; ++i) { TrackInfo videoTrack; videoTrack.type = VideoTrack; videoTrack.isMute = false; videoTrack.isBlind = false; videoTrack.isLocked = false; videoTrack.trackName = i18n("Video %1", i + 1); videoTrack.duration = 0; videoTrack.effectsList = EffectsList(true); tracks.append(videoTrack); } return createEmptyDocument(tracks); } QDomDocument KdenliveDoc::createEmptyDocument(const QList &tracks) { // Creating new document QDomDocument doc; QDomElement mlt = doc.createElement(QStringLiteral("mlt")); mlt.setAttribute(QStringLiteral("LC_NUMERIC"), QLatin1String("")); doc.appendChild(mlt); QDomElement blk = doc.createElement(QStringLiteral("producer")); blk.setAttribute(QStringLiteral("in"), 0); blk.setAttribute(QStringLiteral("out"), 500); blk.setAttribute(QStringLiteral("id"), QStringLiteral("black")); QDomElement property = doc.createElement(QStringLiteral("property")); property.setAttribute(QStringLiteral("name"), QStringLiteral("mlt_type")); QDomText value = doc.createTextNode(QStringLiteral("producer")); property.appendChild(value); blk.appendChild(property); property = doc.createElement(QStringLiteral("property")); property.setAttribute(QStringLiteral("name"), QStringLiteral("aspect_ratio")); value = doc.createTextNode(QString::number(0)); property.appendChild(value); blk.appendChild(property); property = doc.createElement(QStringLiteral("property")); property.setAttribute(QStringLiteral("name"), QStringLiteral("length")); value = doc.createTextNode(QString::number(15000)); property.appendChild(value); blk.appendChild(property); property = doc.createElement(QStringLiteral("property")); property.setAttribute(QStringLiteral("name"), QStringLiteral("eof")); value = doc.createTextNode(QStringLiteral("pause")); property.appendChild(value); blk.appendChild(property); property = doc.createElement(QStringLiteral("property")); property.setAttribute(QStringLiteral("name"), QStringLiteral("resource")); value = doc.createTextNode(QStringLiteral("black")); property.appendChild(value); blk.appendChild(property); property = doc.createElement(QStringLiteral("property")); property.setAttribute(QStringLiteral("name"), QStringLiteral("mlt_service")); value = doc.createTextNode(QStringLiteral("colour")); property.appendChild(value); blk.appendChild(property); mlt.appendChild(blk); QDomElement tractor = doc.createElement(QStringLiteral("tractor")); tractor.setAttribute(QStringLiteral("id"), QStringLiteral("maintractor")); tractor.setAttribute(QStringLiteral("global_feed"), 1); //QDomElement multitrack = doc.createElement("multitrack"); QDomElement playlist = doc.createElement(QStringLiteral("playlist")); playlist.setAttribute(QStringLiteral("id"), QStringLiteral("black_track")); mlt.appendChild(playlist); QDomElement blank0 = doc.createElement(QStringLiteral("entry")); blank0.setAttribute(QStringLiteral("in"), QStringLiteral("0")); blank0.setAttribute(QStringLiteral("out"), QStringLiteral("1")); blank0.setAttribute(QStringLiteral("producer"), QStringLiteral("black")); playlist.appendChild(blank0); // create playlists int total = tracks.count(); // The lower video track will recieve composite transitions int lowerVideoTrack = -1; for (int i = 0; i < total; ++i) { QDomElement playlist = doc.createElement(QStringLiteral("playlist")); playlist.setAttribute(QStringLiteral("id"), "playlist" + QString::number(i+1)); playlist.setAttribute(QStringLiteral("kdenlive:track_name"), tracks.at(i).trackName); if (tracks.at(i).type == AudioTrack) { playlist.setAttribute(QStringLiteral("kdenlive:audio_track"), 1); } else if (lowerVideoTrack == -1) { // Register first video track lowerVideoTrack = i + 1; } mlt.appendChild(playlist); } QDomElement track0 = doc.createElement(QStringLiteral("track")); track0.setAttribute(QStringLiteral("producer"), QStringLiteral("black_track")); tractor.appendChild(track0); // create audio and video tracks for (int i = 0; i < total; ++i) { QDomElement track = doc.createElement(QStringLiteral("track")); track.setAttribute(QStringLiteral("producer"), "playlist" + QString::number(i+1)); if (tracks.at(i).type == AudioTrack) { track.setAttribute(QStringLiteral("hide"), QStringLiteral("video")); } else if (tracks.at(i).isBlind) { if (tracks.at(i).isMute) { track.setAttribute(QStringLiteral("hide"), QStringLiteral("all")); } else track.setAttribute(QStringLiteral("hide"), QStringLiteral("video")); } else if (tracks.at(i).isMute) track.setAttribute(QStringLiteral("hide"), QStringLiteral("audio")); tractor.appendChild(track); } for (int i = 0; i < total; i++) { QDomElement transition = doc.createElement(QStringLiteral("transition")); transition.setAttribute(QStringLiteral("always_active"), QStringLiteral("1")); QDomElement property = doc.createElement(QStringLiteral("property")); property.setAttribute(QStringLiteral("name"), QStringLiteral("mlt_service")); value = doc.createTextNode(QStringLiteral("mix")); property.appendChild(value); transition.appendChild(property); property = doc.createElement(QStringLiteral("property")); property.setAttribute(QStringLiteral("name"), QStringLiteral("a_track")); QDomText value = doc.createTextNode(QStringLiteral("0")); property.appendChild(value); transition.appendChild(property); property = doc.createElement(QStringLiteral("property")); property.setAttribute(QStringLiteral("name"), QStringLiteral("b_track")); value = doc.createTextNode(QString::number(i + 1)); property.appendChild(value); transition.appendChild(property); property = doc.createElement(QStringLiteral("property")); property.setAttribute(QStringLiteral("name"), QStringLiteral("combine")); value = doc.createTextNode(QStringLiteral("1")); property.appendChild(value); transition.appendChild(property); property = doc.createElement(QStringLiteral("property")); property.setAttribute(QStringLiteral("name"), QStringLiteral("internal_added")); value = doc.createTextNode(QStringLiteral("237")); property.appendChild(value); transition.appendChild(property); tractor.appendChild(transition); if (i >= lowerVideoTrack && tracks.at(i).type == VideoTrack) { // Only add composite transition if both tracks are video transition = doc.createElement(QStringLiteral("transition")); property = doc.createElement(QStringLiteral("property")); property.setAttribute(QStringLiteral("name"), QStringLiteral("mlt_service")); property.appendChild(doc.createTextNode(KdenliveSettings::gpu_accel() ? "movit.overlay" : "frei0r.cairoblend")); transition.appendChild(property); property = doc.createElement(QStringLiteral("property")); property.setAttribute(QStringLiteral("name"), QStringLiteral("a_track")); property.appendChild(doc.createTextNode(QString::number(lowerVideoTrack))); transition.appendChild(property); property = doc.createElement(QStringLiteral("property")); property.setAttribute(QStringLiteral("name"), QStringLiteral("b_track")); property.appendChild(doc.createTextNode(QString::number(i+1))); transition.appendChild(property); property = doc.createElement(QStringLiteral("property")); property.setAttribute(QStringLiteral("name"), QStringLiteral("internal_added")); property.appendChild(doc.createTextNode(QStringLiteral("237"))); transition.appendChild(property); tractor.appendChild(transition); } } mlt.appendChild(tractor); return doc; } bool KdenliveDoc::useProxy() const { return m_documentProperties.value(QStringLiteral("enableproxy")).toInt(); } bool KdenliveDoc::autoGenerateProxy(int width) const { return m_documentProperties.value(QStringLiteral("generateproxy")).toInt() && width > m_documentProperties.value(QStringLiteral("proxyminsize")).toInt(); } bool KdenliveDoc::autoGenerateImageProxy(int width) const { return m_documentProperties.value(QStringLiteral("generateimageproxy")).toInt() && width > m_documentProperties.value(QStringLiteral("proxyimageminsize")).toInt(); } void KdenliveDoc::slotAutoSave() { if (m_render && m_autosave) { if (!m_autosave->isOpen() && !m_autosave->open(QIODevice::ReadWrite)) { // show error: could not open the autosave file qDebug() << "ERROR; CANNOT CREATE AUTOSAVE FILE"; } //qDebug() << "// AUTOSAVE FILE: " << m_autosave->fileName(); QDomDocument sceneList = xmlSceneList(m_render->sceneList()); if (sceneList.isNull()) { //Make sure we don't save if scenelist is corrupted KMessageBox::error(QApplication::activeWindow(), i18n("Cannot write to file %1, scene list is corrupted.", m_autosave->fileName())); return; } m_autosave->resize(0); m_autosave->write(sceneList.toString().toUtf8()); m_autosave->flush(); } } void KdenliveDoc::setZoom(int horizontal, int vertical) { m_documentProperties[QStringLiteral("zoom")] = QString::number(horizontal); m_documentProperties[QStringLiteral("verticalzoom")] = QString::number(vertical); } QPoint KdenliveDoc::zoom() const { return QPoint(m_documentProperties.value(QStringLiteral("zoom")).toInt(), m_documentProperties.value(QStringLiteral("verticalzoom")).toInt()); } void KdenliveDoc::setZone(int start, int end) { m_documentProperties[QStringLiteral("zonein")] = QString::number(start); m_documentProperties[QStringLiteral("zoneout")] = QString::number(end); } QPoint KdenliveDoc::zone() const { return QPoint(m_documentProperties.value(QStringLiteral("zonein")).toInt(), m_documentProperties.value(QStringLiteral("zoneout")).toInt()); } QDomDocument KdenliveDoc::xmlSceneList(const QString &scene) { QDomDocument sceneList; sceneList.setContent(scene, true); QDomElement mlt = sceneList.firstChildElement(QStringLiteral("mlt")); if (mlt.isNull() || !mlt.hasChildNodes()) { //scenelist is corrupted return sceneList; } // Set playlist audio volume to 100% QDomElement tractor = mlt.firstChildElement(QStringLiteral("tractor")); if (!tractor.isNull()) { QDomNodeList props = tractor.elementsByTagName(QStringLiteral("property")); for (int i = 0; i < props.count(); ++i) { if (props.at(i).toElement().attribute(QStringLiteral("name")) == QLatin1String("meta.volume")) { props.at(i).firstChild().setNodeValue(QStringLiteral("1")); break; } } } QDomNodeList pls = mlt.elementsByTagName(QStringLiteral("playlist")); QDomElement mainPlaylist; for (int i = 0; i < pls.count(); ++i) { if (pls.at(i).toElement().attribute(QStringLiteral("id")) == pCore->binController()->binPlaylistId()) { mainPlaylist = pls.at(i).toElement(); break; } } // check if project contains custom effects to embed them in project file QDomNodeList effects = mlt.elementsByTagName(QStringLiteral("filter")); int maxEffects = effects.count(); //qDebug() << "// FOUD " << maxEffects << " EFFECTS+++++++++++++++++++++"; QMap effectIds; for (int i = 0; i < maxEffects; ++i) { QDomNode m = effects.at(i); QDomNodeList params = m.childNodes(); QString id; QString tag; for (int j = 0; j < params.count(); ++j) { QDomElement e = params.item(j).toElement(); if (e.attribute(QStringLiteral("name")) == QLatin1String("kdenlive_id")) { id = e.firstChild().nodeValue(); } if (e.attribute(QStringLiteral("name")) == QLatin1String("tag")) { tag = e.firstChild().nodeValue(); } if (!id.isEmpty() && !tag.isEmpty()) effectIds.insert(id, tag); } } //TODO: find a way to process this before rendering MLT scenelist to xml QDomDocument customeffects = initEffects::getUsedCustomEffects(effectIds); if (customeffects.documentElement().childNodes().count() > 0) { EffectsList::setProperty(mainPlaylist, QStringLiteral("kdenlive:customeffects"), customeffects.toString()); } //addedXml.appendChild(sceneList.importNode(customeffects.documentElement(), true)); //TODO: move metadata to previous step in saving process QDomElement docmetadata = sceneList.createElement(QStringLiteral("documentmetadata")); QMapIterator j(m_documentMetadata); while (j.hasNext()) { j.next(); docmetadata.setAttribute(j.key(), j.value()); } //addedXml.appendChild(docmetadata); return sceneList; } QString KdenliveDoc::documentNotes() const { QString text = m_notesWidget->toPlainText().simplified(); if (text.isEmpty()) return QString(); return m_notesWidget->toHtml(); } bool KdenliveDoc::saveSceneList(const QString &path, const QString &scene) { QDomDocument sceneList = xmlSceneList(scene); if (sceneList.isNull()) { //Make sure we don't save if scenelist is corrupted KMessageBox::error(QApplication::activeWindow(), i18n("Cannot write to file %1, scene list is corrupted.", path)); return false; } // Backup current version backupLastSavedVersion(path); QFile file(path); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qWarning() << "////// ERROR writing to file: " << path; KMessageBox::error(QApplication::activeWindow(), i18n("Cannot write to file %1", path)); return false; } file.write(sceneList.toString().toUtf8()); if (file.error() != QFile::NoError) { KMessageBox::error(QApplication::activeWindow(), i18n("Cannot write to file %1", path)); file.close(); return false; } file.close(); cleanupBackupFiles(); QFileInfo info(file); QString fileName = QUrl::fromLocalFile(path).fileName().section('.', 0, -2); fileName.append('-' + m_documentProperties.value(QStringLiteral("documentid"))); fileName.append(info.lastModified().toString(QStringLiteral("-yyyy-MM-dd-hh-mm"))); fileName.append(".kdenlive.png"); QDir backupFolder(m_projectFolder.path() + "/.backup"); emit saveTimelinePreview(backupFolder.absoluteFilePath(fileName)); return true; } ClipManager *KdenliveDoc::clipManager() { return m_clipManager; } QString KdenliveDoc::groupsXml() const { return m_clipManager->groupsXml(); } QUrl KdenliveDoc::projectFolder() const { //if (m_projectFolder.isEmpty()) return QUrl(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "//projects/"); return m_projectFolder; } void KdenliveDoc::setProjectFolder(QUrl url) { if (url == m_projectFolder) return; setModified(true); QDir dir(url.toLocalFile()); if (!dir.exists()) { dir.mkpath(dir.absolutePath()); } dir.mkdir(QStringLiteral("titles")); dir.mkdir(QStringLiteral("thumbs")); dir.mkdir(QStringLiteral("proxy")); dir.mkdir(QStringLiteral(".backup")); if (KMessageBox::questionYesNo(QApplication::activeWindow(), i18n("You have changed the project folder. Do you want to copy the cached data from %1 to the new folder %2?", m_projectFolder.path(), url.path())) == KMessageBox::Yes) moveProjectData(url); m_projectFolder = url; updateProjectFolderPlacesEntry(); } void KdenliveDoc::moveProjectData(const QUrl &url) { QList list = pCore->binController()->getControllerList(); QList cacheUrls; for (int i = 0; i < list.count(); ++i) { ClipController *clip = list.at(i); if (clip->clipType() == Text) { // the image for title clip must be moved QUrl oldUrl = clip->clipUrl(); QUrl newUrl = QUrl::fromLocalFile(url.toLocalFile() + QDir::separator() + "titles/" + oldUrl.fileName()); KIO::Job *job = KIO::copy(oldUrl, newUrl); if (job->exec()) clip->setProperty(QStringLiteral("resource"), newUrl.path()); } QString hash = clip->getClipHash(); QUrl oldVideoThumbUrl = QUrl::fromLocalFile(m_projectFolder.path() + QDir::separator() + "thumbs/" + hash + ".png"); if (QFile::exists(oldVideoThumbUrl.path())) { cacheUrls << oldVideoThumbUrl; } QUrl oldAudioThumbUrl = QUrl::fromLocalFile(m_projectFolder.path() + QDir::separator() + "thumbs/" + hash + ".thumb"); if (QFile::exists(oldAudioThumbUrl.path())) { cacheUrls << oldAudioThumbUrl; } QUrl oldVideoProxyUrl = QUrl::fromLocalFile(m_projectFolder.path() + QDir::separator() + "proxy/" + hash + '.' + KdenliveSettings::proxyextension()); if (QFile::exists(oldVideoProxyUrl.path())) { cacheUrls << oldVideoProxyUrl; } } if (!cacheUrls.isEmpty()) { KIO::Job *job = KIO::copy(cacheUrls, QUrl::fromLocalFile(url.path() + QDir::separator() + "thumbs/")); KJobWidgets::setWindow(job, QApplication::activeWindow()); job->exec(); } } const QString &KdenliveDoc::profilePath() const { return m_profile.path; } MltVideoProfile KdenliveDoc::mltProfile() const { return m_profile; } bool KdenliveDoc::profileChanged(const QString &profile) const { return m_profile.toList() != ProfilesDialog::getVideoProfile(profile).toList(); } ProfileInfo KdenliveDoc::getProfileInfo() const { ProfileInfo info; info.profileSize = getRenderSize(); info.profileFps = fps(); return info; } double KdenliveDoc::dar() const { return (double) m_profile.display_aspect_num / m_profile.display_aspect_den; } QUndoStack *KdenliveDoc::commandStack() { return m_commandStack; } Render *KdenliveDoc::renderer() { return m_render; } int KdenliveDoc::getFramePos(const QString &duration) { return m_timecode.getFrameCount(duration); } QDomDocument KdenliveDoc::toXml() { return m_document; } Timecode KdenliveDoc::timecode() const { return m_timecode; } QDomNodeList KdenliveDoc::producersList() { return m_document.elementsByTagName(QStringLiteral("producer")); } double KdenliveDoc::projectDuration() const { if (m_render) return GenTime(m_render->getLength(), m_render->fps()).ms() / 1000; else return 0; } double KdenliveDoc::fps() const { return m_render->fps(); } int KdenliveDoc::width() const { return m_width; } int KdenliveDoc::height() const { return m_height; } QUrl KdenliveDoc::url() const { return m_url; } void KdenliveDoc::setUrl(const QUrl &url) { m_url = url; } -void KdenliveDoc::slotModified() +void KdenliveDoc::slotModified(int ix) { setModified(m_commandStack->isClean() == false); + if (m_undoPreviewIndex != -1 && ix == m_undoPreviewIndex - 1) { + restoreTimelinePreviews(); + } else if (ix < m_undoPreviewIndex - 1) { + // Restore preview files if any + m_undoPreviewIndex = -1; + } } void KdenliveDoc::setModified(bool mod) { // fix mantis#3160: The document may have an empty URL if not saved yet, but should have a m_autosave in any case if (m_autosave && mod && KdenliveSettings::crashrecovery()) { emit startAutoSave(); } if (mod == m_modified) return; m_modified = mod; emit docModified(m_modified); } bool KdenliveDoc::isModified() const { return m_modified; } const QString KdenliveDoc::description() const { if (!m_url.isValid()) return i18n("Untitled") + "[*] / " + m_profile.description; else return m_url.fileName() + " [*]/ " + m_profile.description; } bool KdenliveDoc::addClip(QDomElement elem, const QString &clipId) { const QString producerId = clipId.section('_', 0, 0); elem.setAttribute(QStringLiteral("id"), producerId); if (KdenliveSettings::checkfirstprojectclip() && pCore->bin()->isEmpty()) { elem.setAttribute("checkProfile", 1); } pCore->bin()->createClip(elem); pCore->producerQueue()->getFileProperties(elem, producerId, 150, true); /*QString str; QTextStream stream(&str); elem.save(stream, 4); qDebug()<<"ADDING CLIP COMMAND\n-----------\n"<getClipById(producerId); if (clip == NULL) { QString clipFolder = KRecentDirs::dir(":KdenliveClipFolder"); elem.setAttribute("id", producerId); QString path = elem.attribute("resource"); QString extension; if (elem.attribute("type").toInt() == SlideShow) { QUrl f = QUrl::fromLocalFile(path); extension = f.fileName(); path = f.adjusted(QUrl::RemoveFilename).path(); } if (elem.hasAttribute("_missingsource")) { // Clip has proxy but missing original source } else if (path.isEmpty() == false && QFile::exists(path) == false && elem.attribute("type").toInt() != Text && !elem.hasAttribute("placeholder")) { //qDebug() << "// FOUND MISSING CLIP: " << path << ", TYPE: " << elem.attribute("type").toInt(); const QString size = elem.attribute("file_size"); const QString hash = elem.attribute("file_hash"); QString newpath; int action = KMessageBox::No; if (!size.isEmpty() && !hash.isEmpty()) { if (!m_searchFolder.isEmpty()) newpath = searchFileRecursively(m_searchFolder, size, hash); else action = (KMessageBox::ButtonCode) KMessageBox::questionYesNoCancel(QApplication::activeWindow(), i18n("Clip %1
is invalid, what do you want to do?", path), i18n("File not found"), KGuiItem(i18n("Search automatically")), KGuiItem(i18n("Keep as placeholder"))); } else { if (elem.attribute("type").toInt() == SlideShow) { int res = KMessageBox::questionYesNoCancel(QApplication::activeWindow(), i18n("Clip %1
is invalid or missing, what do you want to do?", path), i18n("File not found"), KGuiItem(i18n("Search manually")), KGuiItem(i18n("Keep as placeholder"))); if (res == KMessageBox::Yes) newpath = QFileDialog::getExistingDirectory(QApplication::activeWindow(), i18n("Looking for %1", path), clipFolder); else { // Abort project loading action = res; } } else { int res = KMessageBox::questionYesNoCancel(QApplication::activeWindow(), i18n("Clip %1
is invalid or missing, what do you want to do?", path), i18n("File not found"), KGuiItem(i18n("Search manually")), KGuiItem(i18n("Keep as placeholder"))); if (res == KMessageBox::Yes) newpath = QFileDialog::getOpenFileName(QApplication::activeWindow(), i18n("Looking for %1", path), clipFolder); else { // Abort project loading action = res; } } } if (action == KMessageBox::Yes) { //qDebug() << "// ASKED FOR SRCH CLIP: " << clipId; m_searchFolder = QFileDialog::getExistingDirectory(QApplication::activeWindow(), QString(), clipFolder); if (!m_searchFolder.isEmpty()) newpath = searchFileRecursively(QDir(m_searchFolder), size, hash); } else if (action == KMessageBox::Cancel) { return false; } else if (action == KMessageBox::No) { // Keep clip as placeHolder elem.setAttribute("placeholder", '1'); } if (!newpath.isEmpty()) { //qDebug() << "// NEW CLIP PATH FOR CLIP " << clipId << " : " << newpath; if (elem.attribute("type").toInt() == SlideShow) newpath.append('/' + extension); elem.setAttribute("resource", newpath); setNewClipResource(clipId, newpath); setModified(true); } } clip = new DocClipBase(m_clipManager, elem, producerId); m_clipManager->addClip(clip); } return true; */ } QString KdenliveDoc::searchFileRecursively(const QDir &dir, const QString &matchSize, const QString &matchHash) const { QString foundFileName; QByteArray fileData; QByteArray fileHash; QStringList filesAndDirs = dir.entryList(QDir::Files | QDir::Readable); for (int i = 0; i < filesAndDirs.size() && foundFileName.isEmpty(); ++i) { QFile file(dir.absoluteFilePath(filesAndDirs.at(i))); if (file.open(QIODevice::ReadOnly)) { if (QString::number(file.size()) == matchSize) { /* * 1 MB = 1 second per 450 files (or faster) * 10 MB = 9 seconds per 450 files (or faster) */ if (file.size() > 1000000 * 2) { fileData = file.read(1000000); if (file.seek(file.size() - 1000000)) fileData.append(file.readAll()); } else fileData = file.readAll(); file.close(); fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5); if (QString(fileHash.toHex()) == matchHash) return file.fileName(); else qDebug() << filesAndDirs.at(i) << "size match but not hash"; } } ////qDebug() << filesAndDirs.at(i) << file.size() << fileHash.toHex(); } filesAndDirs = dir.entryList(QDir::Dirs | QDir::Readable | QDir::Executable | QDir::NoDotAndDotDot); for (int i = 0; i < filesAndDirs.size() && foundFileName.isEmpty(); ++i) { foundFileName = searchFileRecursively(dir.absoluteFilePath(filesAndDirs.at(i)), matchSize, matchHash); if (!foundFileName.isEmpty()) break; } return foundFileName; } /* bool KdenliveDoc::addClipInfo(QDomElement elem, QDomElement orig, const QString &clipId) { DocClipBase *clip = m_clipManager->getClipById(clipId); if (clip == NULL) { if (!addClip(elem, clipId, false)) return false; } else { QMap properties; QDomNamedNodeMap attributes = elem.attributes(); for (int i = 0; i < attributes.count(); ++i) { QString attrname = attributes.item(i).nodeName(); if (attrname != "resource") properties.insert(attrname, attributes.item(i).nodeValue()); ////qDebug() << attrname << " = " << attributes.item(i).nodeValue(); } clip->setProperties(properties); emit addProjectClip(clip, false); } if (!orig.isNull()) { QMap meta; for (QDomNode m = orig.firstChild(); !m.isNull(); m = m.nextSibling()) { QString name = m.toElement().attribute("name"); if (name.startsWith(QLatin1String("meta.attr"))) { if (name.endsWith(QLatin1String(".markup"))) name = name.section('.', 0, -2); meta.insert(name.section('.', 2, -1), m.firstChild().nodeValue()); } } if (!meta.isEmpty()) { if (clip == NULL) clip = m_clipManager->getClipById(clipId); if (clip) clip->setMetadata(meta); } } return true; }*/ void KdenliveDoc::deleteClip(const QString &clipId) { ClipController *controller = pCore->binController()->getController(clipId); if (!controller) { // Clip doesn't exist, something is wrong qWarning()<<"// Document error deleting clip: "<clipType(); QString url = controller->clipUrl().toLocalFile(); // Delete clip in bin pCore->bin()->deleteClip(clipId); // Delete controller and Mlt::Producer pCore->binController()->removeBinClip(clipId); // Remove from file watch if (type != Color && type != SlideShow && type != QText && !url.isEmpty()) { m_fileWatcher.removeFile(url); } } ProjectClip *KdenliveDoc::getBinClip(const QString &clipId) { return pCore->bin()->getBinClip(clipId); } QStringList KdenliveDoc::getBinFolderClipIds(const QString &folderId) const { return pCore->bin()->getBinFolderClipIds(folderId); } ClipController *KdenliveDoc::getClipController(const QString &clipId) { return pCore->binController()->getController(clipId); } void KdenliveDoc::slotCreateTextTemplateClip(const QString &group, const QString &groupId, QUrl path) { QString titlesFolder = QDir::cleanPath(projectFolder().path() + QDir::separator() + "titles/"); if (path.isEmpty()) { QPointer d = new QFileDialog(QApplication::activeWindow(), i18n("Enter Template Path"), titlesFolder); d->setMimeTypeFilters(QStringList() << QStringLiteral("application/x-kdenlivetitle")); d->setFileMode(QFileDialog::ExistingFile); if (d->exec() == QDialog::Accepted && !d->selectedUrls().isEmpty()) { path = d->selectedUrls().first(); } delete d; } if (path.isEmpty()) return; //TODO: rewrite with new title system (just set resource) m_clipManager->slotAddTextTemplateClip(i18n("Template title clip"), path, group, groupId); emit selectLastAddedClip(QString::number(m_clipManager->lastClipId())); } void KdenliveDoc::cacheImage(const QString &fileId, const QImage &img) const { img.save(QDir::cleanPath(m_projectFolder.path() +QDir::separator() + "thumbs/" + fileId + ".png")); } void KdenliveDoc::setDocumentProperty(const QString &name, const QString &value) { if (value.isEmpty()) { m_documentProperties.remove(name); return; } m_documentProperties[name] = value; } const QString KdenliveDoc::getDocumentProperty(const QString &name, const QString &defaultValue) const { if (m_documentProperties.contains(name)) return m_documentProperties.value(name); return defaultValue; } QMap KdenliveDoc::getRenderProperties() const { QMap renderProperties; QMapIterator i(m_documentProperties); while (i.hasNext()) { i.next(); if (i.key().startsWith(QLatin1String("render"))) renderProperties.insert(i.key(), i.value()); } return renderProperties; } void KdenliveDoc::saveCustomEffects(const QDomNodeList &customeffects) { QDomElement e; QStringList importedEffects; int maxchild = customeffects.count(); for (int i = 0; i < maxchild; ++i) { e = customeffects.at(i).toElement(); const QString id = e.attribute(QStringLiteral("id")); const QString tag = e.attribute(QStringLiteral("tag")); if (!id.isEmpty()) { // Check if effect exists or save it if (MainWindow::customEffects.hasEffect(tag, id) == -1) { QDomDocument doc; doc.appendChild(doc.importNode(e, true)); QString path = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/effects"; path += id + ".xml"; if (!QFile::exists(path)) { importedEffects << id; QFile file(path); if (file.open(QFile::WriteOnly | QFile::Truncate)) { QTextStream out(&file); out << doc.toString(); } } } } } if (!importedEffects.isEmpty()) KMessageBox::informationList(QApplication::activeWindow(), i18n("The following effects were imported from the project:"), importedEffects); if (!importedEffects.isEmpty()) { emit reloadEffects(); } } void KdenliveDoc::updateProjectFolderPlacesEntry() { /* * For similar and more code have a look at kfileplacesmodel.cpp and the included files: * http://websvn.kde.org/trunk/KDE/kdelibs/kfile/kfileplacesmodel.cpp?view=markup */ const QString file = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/user-places.xbel"; KBookmarkManager *bookmarkManager = KBookmarkManager::managerForExternalFile(file); if (!bookmarkManager) return; KBookmarkGroup root = bookmarkManager->root(); KBookmark bookmark = root.first(); QString kdenliveName = QCoreApplication::applicationName(); QUrl documentLocation = m_projectFolder; bool exists = false; while (!bookmark.isNull()) { // UDI not empty indicates a device QString udi = bookmark.metaDataItem(QStringLiteral("UDI")); QString appName = bookmark.metaDataItem(QStringLiteral("OnlyInApp")); if (udi.isEmpty() && appName == kdenliveName && bookmark.text() == i18n("Project Folder")) { if (bookmark.url() != documentLocation) { bookmark.setUrl(documentLocation); bookmarkManager->emitChanged(root); } exists = true; break; } bookmark = root.next(bookmark); } // if entry does not exist yet (was not found), well, create it then if (!exists) { bookmark = root.addBookmark(i18n("Project Folder"), documentLocation, QStringLiteral("folder-favorites")); // Make this user selectable ? bookmark.setMetaDataItem(QStringLiteral("OnlyInApp"), kdenliveName); bookmarkManager->emitChanged(root); } } const QSize KdenliveDoc::getRenderSize() const { QSize size; if (m_render) { size.setWidth(m_render->frameRenderWidth()); size.setHeight(m_render->renderHeight()); } return size; } // static double KdenliveDoc::getDisplayRatio(const QString &path) { QFile file(path); QDomDocument doc; if (!file.open(QIODevice::ReadOnly)) { qWarning() << "ERROR, CANNOT READ: " << path; return 0; } if (!doc.setContent(&file)) { qWarning() << "ERROR, CANNOT READ: " << path; file.close(); return 0; } file.close(); QDomNodeList list = doc.elementsByTagName(QStringLiteral("profile")); if (list.isEmpty()) return 0; QDomElement profile = list.at(0).toElement(); double den = profile.attribute(QStringLiteral("display_aspect_den")).toDouble(); if (den > 0) return profile.attribute(QStringLiteral("display_aspect_num")).toDouble() / den; return 0; } void KdenliveDoc::backupLastSavedVersion(const QString &path) { // Ensure backup folder exists if (path.isEmpty()) return; QFile file(path); QDir backupFolder(m_projectFolder.path() + "/.backup"); QString fileName = QUrl::fromLocalFile(path).fileName().section('.', 0, -2); QFileInfo info(file); fileName.append('-' + m_documentProperties.value(QStringLiteral("documentid"))); fileName.append(info.lastModified().toString(QStringLiteral("-yyyy-MM-dd-hh-mm"))); fileName.append(".kdenlive"); QString backupFile = backupFolder.absoluteFilePath(fileName); if (file.exists()) { // delete previous backup if it was done less than 60 seconds ago QFile::remove(backupFile); if (!QFile::copy(path, backupFile)) { KMessageBox::information(QApplication::activeWindow(), i18n("Cannot create backup copy:\n%1", backupFile)); } } } void KdenliveDoc::cleanupBackupFiles() { QDir backupFolder(m_projectFolder.path() + "/.backup"); QString projectFile = url().fileName().section('.', 0, -2); projectFile.append('-' + m_documentProperties.value(QStringLiteral("documentid"))); projectFile.append("-??"); projectFile.append("??"); projectFile.append("-??"); projectFile.append("-??"); projectFile.append("-??"); projectFile.append("-??.kdenlive"); QStringList filter; filter << projectFile; backupFolder.setNameFilters(filter); QFileInfoList resultList = backupFolder.entryInfoList(QDir::Files, QDir::Time); QDateTime d = QDateTime::currentDateTime(); QStringList hourList; QStringList dayList; QStringList weekList; QStringList oldList; for (int i = 0; i < resultList.count(); ++i) { if (d.secsTo(resultList.at(i).lastModified()) < 3600) { // files created in the last hour hourList.append(resultList.at(i).absoluteFilePath()); } else if (d.secsTo(resultList.at(i).lastModified()) < 43200) { // files created in the day dayList.append(resultList.at(i).absoluteFilePath()); } else if (d.daysTo(resultList.at(i).lastModified()) < 8) { // files created in the week weekList.append(resultList.at(i).absoluteFilePath()); } else { // older files oldList.append(resultList.at(i).absoluteFilePath()); } } if (hourList.count() > 20) { int step = hourList.count() / 10; for (int i = 0; i < hourList.count(); i += step) { //qDebug()<<"REMOVE AT: "< 20) { int step = dayList.count() / 10; for (int i = 0; i < dayList.count(); i += step) { dayList.removeAt(i); --i; } } else dayList.clear(); if (weekList.count() > 20) { int step = weekList.count() / 10; for (int i = 0; i < weekList.count(); i += step) { weekList.removeAt(i); --i; } } else weekList.clear(); if (oldList.count() > 20) { int step = oldList.count() / 10; for (int i = 0; i < oldList.count(); i += step) { oldList.removeAt(i); --i; } } else oldList.clear(); QString f; while (hourList.count() > 0) { f = hourList.takeFirst(); QFile::remove(f); QFile::remove(f + ".png"); } while (dayList.count() > 0) { f = dayList.takeFirst(); QFile::remove(f); QFile::remove(f + ".png"); } while (weekList.count() > 0) { f = weekList.takeFirst(); QFile::remove(f); QFile::remove(f + ".png"); } while (oldList.count() > 0) { f = oldList.takeFirst(); QFile::remove(f); QFile::remove(f + ".png"); } } const QMap KdenliveDoc::metadata() const { return m_documentMetadata; } void KdenliveDoc::setMetadata(const QMap &meta) { setModified(true); m_documentMetadata = meta; } void KdenliveDoc::slotProxyCurrentItem(bool doProxy, QList clipList) { if (clipList.isEmpty()) clipList = pCore->bin()->selectedClips(); QUndoCommand *command = new QUndoCommand(); if (doProxy) command->setText(i18np("Add proxy clip", "Add proxy clips", clipList.count())); else command->setText(i18np("Remove proxy clip", "Remove proxy clips", clipList.count())); // Make sure the proxy folder exists QString proxydir = projectFolder().path() + QDir::separator() + "proxy/"; QDir dir(projectFolder().path()); dir.mkdir(QStringLiteral("proxy")); // Prepare updated properties QMap newProps; QMap oldProps; if (!doProxy) newProps.insert(QStringLiteral("kdenlive:proxy"), QStringLiteral("-")); // Parse clips for (int i = 0; i < clipList.count(); ++i) { ProjectClip *item = clipList.at(i); ClipType t = item->clipType(); // Only allow proxy on some clip types if ((t == Video || t == AV || t == Unknown || t == Image || t == Playlist) && item->isReady()) { if ((doProxy && item->hasProxy()) || (!doProxy && !item->hasProxy() && pCore->binController()->hasClip(item->clipId()))) continue; if (pCore->producerQueue()->isProcessing(item->clipId())) { continue; } if (doProxy) { newProps.clear(); QString path = proxydir + item->hash() + '.' + (t == Image ? QStringLiteral("png") : getDocumentProperty(QStringLiteral("proxyextension"))); // insert required duration for proxy newProps.insert(QStringLiteral("proxy_out"), item->getProducerProperty(QStringLiteral("out"))); newProps.insert(QStringLiteral("kdenlive:proxy"), path); } else if (!pCore->binController()->hasClip(item->clipId())) { // Force clip reload newProps.insert(QStringLiteral("resource"), item->url().toLocalFile()); } // We need to insert empty proxy so that undo will work //TODO: how to handle clip properties //oldProps = clip->currentProperties(newProps); if (doProxy) oldProps.insert(QStringLiteral("kdenlive:proxy"), QStringLiteral("-")); new EditClipCommand(pCore->bin(), item->clipId(), oldProps, newProps, true, command); } } if (command->childCount() > 0) { m_commandStack->push(command); } else delete command; } //TODO put all file watching stuff in own class void KdenliveDoc::watchFile(const QUrl &url) { m_fileWatcher.addFile(url.toLocalFile()); } void KdenliveDoc::slotClipModified(const QString &path) { QStringList ids = pCore->binController()->getBinIdsByResource(QUrl::fromLocalFile(path)); foreach (const QString &id, ids) { if (!m_modifiedClips.contains(id)) { pCore->bin()->setWaitingStatus(id); } m_modifiedClips[id] = QTime::currentTime(); } if (!m_modifiedTimer.isActive()) m_modifiedTimer.start(); } void KdenliveDoc::slotClipMissing(const QString &path) { qDebug() << "// CLIP: " << path << " WAS MISSING"; QStringList ids = pCore->binController()->getBinIdsByResource(QUrl::fromLocalFile(path)); //TODO handle missing clips by replacing producer with an invalid producer /*foreach (const QString &id, ids) { emit missingClip(id); }*/ } void KdenliveDoc::slotProcessModifiedClips() { if (!m_modifiedClips.isEmpty()) { QMapIterator i(m_modifiedClips); while (i.hasNext()) { i.next(); if (QTime::currentTime().msecsTo(i.value()) <= -1500) { pCore->bin()->reloadClip(i.key()); m_modifiedClips.remove(i.key()); break; } } setModified(true); } if (m_modifiedClips.isEmpty()) m_modifiedTimer.stop(); } QMap KdenliveDoc::documentProperties() { m_documentProperties.insert(QStringLiteral("version"), QString::number(DOCUMENTVERSION)); m_documentProperties.insert(QStringLiteral("kdenliveversion"), QStringLiteral(KDENLIVE_VERSION)); m_documentProperties.insert(QStringLiteral("projectfolder"), m_projectFolder.path()); m_documentProperties.insert(QStringLiteral("profile"), profilePath()); m_documentProperties.insert(QStringLiteral("position"), QString::number(m_render->seekPosition().frames(m_render->fps()))); return m_documentProperties; } void KdenliveDoc::loadDocumentProperties() { QDomNodeList list = m_document.elementsByTagName(QStringLiteral("playlist")); if (!list.isEmpty()) { QDomElement pl = list.at(0).toElement(); if (pl.isNull()) return; QDomNodeList props = pl.elementsByTagName(QStringLiteral("property")); QString name; QDomElement e; for (int i = 0; i < props.count(); i++) { e = props.at(i).toElement(); name = e.attribute(QStringLiteral("name")); if (name.startsWith(QLatin1String("kdenlive:docproperties."))) { name = name.section(QStringLiteral("."), 1); m_documentProperties.insert(name, e.firstChild().nodeValue()); } else if (name.startsWith(QLatin1String("kdenlive:docmetadata."))) { name = name.section(QStringLiteral("."), 1); m_documentMetadata.insert(name, e.firstChild().nodeValue()); } } } QString path = m_documentProperties.value(QStringLiteral("projectfolder")); if (!path.startsWith('/')) { QDir dir = QDir::home(); path = dir.absoluteFilePath(path); } m_projectFolder = QUrl::fromLocalFile(path); list = m_document.elementsByTagName(QStringLiteral("profile")); if (!list.isEmpty()) { m_profile = ProfilesDialog::getVideoProfileFromXml(list.at(0).toElement()); } updateProjectProfile(false); } void KdenliveDoc::updateProjectProfile(bool reloadProducers) { KdenliveSettings::setProject_display_ratio((double) m_profile.display_aspect_num / m_profile.display_aspect_den); double fps = (double) m_profile.frame_rate_num / m_profile.frame_rate_den; KdenliveSettings::setProject_fps(fps); m_width = m_profile.width; m_height = m_profile.height; bool fpsChanged = m_timecode.fps() != fps; m_timecode.setFormat(fps); pCore->producerQueue()->abortOperations(); KdenliveSettings::setCurrent_profile(m_profile.path); pCore->monitorManager()->resetProfiles(m_profile, m_timecode); if (!reloadProducers) return; emit updateFps(fpsChanged); if (fpsChanged) { pCore->bin()->reloadAllProducers(); } } void KdenliveDoc::resetProfile() { m_profile = ProfilesDialog::getVideoProfile(KdenliveSettings::current_profile()); updateProjectProfile(true); emit docModified(true); } void KdenliveDoc::slotSwitchProfile() { QAction *action = qobject_cast(sender()); if (!action) return; QVariantList data = action->data().toList(); QString id = data.takeFirst().toString(); if (!data.isEmpty()) { // we want a profile switch m_profile = MltVideoProfile(data); updateProjectProfile(true); emit docModified(true); } } void KdenliveDoc::switchProfile(MltVideoProfile profile, const QString &id, const QDomElement &xml) { // Request profile update QString matchingProfile = ProfilesDialog::existingProfile(profile); if (matchingProfile.isEmpty() && (profile.width % 8 != 0)) { // Make sure profile width is a multiple of 8, required by some parts of mlt profile.adjustWidth(); matchingProfile = ProfilesDialog::existingProfile(profile); } if (!matchingProfile.isEmpty()) { // We found a known matching profile, switch and inform user QMap< QString, QString > profileProperties = ProfilesDialog::getSettingsFromFile(matchingProfile); profile.path = matchingProfile; profile.description = profileProperties.value("description"); // Build actions for the info message (switch / cancel) QList list; QAction *ac = new QAction(KoIconUtils::themedIcon(QStringLiteral("dialog-ok")), i18n("Switch"), this); QVariantList params; connect(ac, SIGNAL(triggered(bool)), this, SLOT(slotSwitchProfile())); params << id << profile.toList(); ac->setData(params); QAction *ac2 = new QAction(KoIconUtils::themedIcon(QStringLiteral("dialog-cancel")), i18n("Cancel"), this); QVariantList params2; params2 << id; ac2->setData(params2); connect(ac2, SIGNAL(triggered(bool)), this, SLOT(slotSwitchProfile())); list << ac << ac2; pCore->bin()->doDisplayMessage(i18n("Switch to clip profile %1?", profile.descriptiveString()), KMessageWidget::Information, list); } else { // No known profile, ask user if he wants to use clip profile anyway if (KMessageBox::warningContinueCancel(QApplication::activeWindow(), i18n("No profile found for your clip.\nCreate and switch to new profile (%1x%2, %3fps)?", profile.width, profile.height, QString::number((double)profile.frame_rate_num / profile.frame_rate_den, 'f', 2))) == KMessageBox::Continue) { m_profile = profile; m_profile.description = QString("%1x%2 %3fps").arg(profile.width).arg(profile.height).arg(QString::number((double)profile.frame_rate_num / profile.frame_rate_den, 'f', 2)); ProfilesDialog::saveProfile(m_profile); updateProjectProfile(true); emit docModified(true); pCore->producerQueue()->getFileProperties(xml, id, 150, true); } } } void KdenliveDoc::forceProcessing(const QString &id) { pCore->producerQueue()->forceProcessing(id); } void KdenliveDoc::getFileProperties(const QDomElement &xml, const QString &clipId, int imageHeight, bool replaceProducer) { pCore->producerQueue()->getFileProperties(xml, clipId, imageHeight, replaceProducer); } void KdenliveDoc::doAddAction(const QString &name, QAction *a) { pCore->window()->actionCollection()->addAction(name, a); } void KdenliveDoc::invalidatePreviews(QList chunks) { QDir dir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); + bool redo = true; + if (m_commandStack->index() > m_undoPreviewIndex) { + m_undoPreviewIndex = m_commandStack->index(); + } else { + redo = false; + } + m_undoChunks = chunks; + // Clear all previous undo files + if (redo && dir.cd("undo")) { + dir.removeRecursively(); + dir.cdUp(); + } + dir.mkdir("undo"); QString documentId = m_documentProperties.value(QStringLiteral("documentid")); + QString ext = m_documentProperties.value(QStringLiteral("previewextension")); foreach(int i, chunks) { - QFile::remove(dir.absoluteFilePath(documentId + QString("-%1.%2").arg(i).arg(KdenliveSettings::tl_extension()))); + QString current = documentId + QString("-%1.%2").arg(i).arg(ext); + if (redo) + dir.rename(current, "undo/" + current); + else + dir.remove(current); } setModified(true); } +void KdenliveDoc::restoreTimelinePreviews() +{ + QDir dir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); + QString documentId = m_documentProperties.value(QStringLiteral("documentid")); + QString ext = m_documentProperties.value(QStringLiteral("previewextension")); + foreach(int i, m_undoChunks) { + QString current = documentId + QString("-%1.%2").arg(i).arg(ext); + dir.remove(current); + dir.rename("undo/" + current, current); + } + dir.rmdir("undo"); + emit reloadChunks(m_undoChunks); + m_undoPreviewIndex = -1; + m_undoChunks.clear(); +} + void KdenliveDoc::previewProgress(int p) { pCore->window()->setPreviewProgress(p); } void KdenliveDoc::selectPreviewProfile() { // Read preview profiles and find the best match KConfig conf(QStringLiteral("encodingprofiles.rc"), KConfig::CascadeConfig, QStandardPaths::DataLocation); KConfigGroup group(&conf, "timelinepreview"); QMap< QString, QString > values = group.entryMap(); QMapIterator i(values); QStringList matchingProfiles; QStringList fallBackProfiles; while (i.hasNext()) { i.next(); // Check for frame rate QStringList data = i.value().split(" "); bool rateFound = false; foreach(const QString arg, data) { if (arg.startsWith(QStringLiteral("r="))) { rateFound = true; double fps = arg.section(QStringLiteral("="), 1).toDouble(); if (fps > 0) { if (qAbs((int) (m_render->fps() * 100) - (fps * 100)) <= 1) { matchingProfiles << i.value(); break; } } } } if (!rateFound) { // Profile without fps, can be used as fallBack fallBackProfiles << i.value(); } } QString bestMatch; if (matchingProfiles.count() > 1) { // several profiles with matching fps, try to decide based on resolution QString docSize = QString("s=%1x%2").arg(m_profile.width).arg(m_profile.height); foreach (const QString ¶m, matchingProfiles) { if (param.contains(docSize)) { bestMatch = param; break; } } if (bestMatch.isEmpty()) bestMatch = matchingProfiles.first(); } else if (matchingProfiles.count() == 1) { bestMatch = matchingProfiles.first(); } else if (!fallBackProfiles.isEmpty()) { bestMatch = fallBackProfiles.first(); } if (!bestMatch.isEmpty()) { setDocumentProperty(QStringLiteral("previewparameters"), bestMatch.section(";", 0, 0)); setDocumentProperty(QStringLiteral("previewextension"), bestMatch.section(";", 1, 1)); } } diff --git a/src/doc/kdenlivedoc.h b/src/doc/kdenlivedoc.h index 41b8c1650..0607ffea2 100644 --- a/src/doc/kdenlivedoc.h +++ b/src/doc/kdenlivedoc.h @@ -1,250 +1,257 @@ /*************************************************************************** * 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 * ***************************************************************************/ /*! \class KdenliveDoc \brief Represents a kdenlive project file Instances of KdeliveDoc classes are created by void MainWindow::newFile(bool showProjectSettings, bool force) */ #ifndef KDENLIVEDOC_H #define KDENLIVEDOC_H #include #include #include #include #include #include #include #include #include #include "gentime.h" #include "timecode.h" #include "definitions.h" #include "timeline/guide.h" #include "mltcontroller/effectscontroller.h" class Render; class ClipManager; class MainWindow; class TrackInfo; class NotesPlugin; class ProjectClip; class ClipController; class QTextEdit; class QUndoGroup; class QTimer; class QUndoStack; namespace Mlt { class Profile; } class KdenliveDoc: public QObject { Q_OBJECT public: KdenliveDoc(const QUrl &url, const QUrl &projectFolder, QUndoGroup *undoGroup, const QString &profileName, const QMap & properties, const QMap & metadata, const QPoint &tracks, Render *render, NotesPlugin *notes, bool *openBackup, MainWindow *parent = 0); ~KdenliveDoc(); QDomNodeList producersList(); double fps() const; int width() const; int height() const; QUrl url() const; KAutoSaveFile *m_autosave; Timecode timecode() const; QDomDocument toXml(); QUndoStack *commandStack(); Render *renderer(); ClipManager *clipManager(); QString groupsXml() const; /** @brief Adds a clip to the project tree. * @return false if the user aborted the operation, true otherwise */ bool addClip(QDomElement elem, const QString &clipId); /** @brief Updates information about a clip. * @param elem the * @param orig the potential * @param clipId the producer id * @return false if the user aborted the operation (in case the clip wasn't * there yet), true otherwise * * If the clip wasn't added before, it tries to add it to the project. */ //bool addClipInfo(QDomElement elem, QDomElement orig, const QString &clipId); void deleteClip(const QString &clipId); int getFramePos(const QString &duration); /** @brief Get a bin's clip from its id. */ ProjectClip *getBinClip(const QString &clipId); /** @brief Get a list of all clip ids that are inside a folder. */ QStringList getBinFolderClipIds(const QString &folderId) const; ClipController *getClipController(const QString &clipId); const QString &profilePath() const; /** @brief Returns current project profile. */ MltVideoProfile mltProfile() const; ProfileInfo getProfileInfo() const; const QString description() const; void setUrl(const QUrl &url); /** @brief Defines whether the document needs to be saved. */ bool isModified() const; /** @brief Returns the project folder, used to store project files. */ QUrl projectFolder() const; void setZoom(int horizontal, int vertical); QPoint zoom() const; double dar() const; double projectDuration() const; /** @brief Returns the project file xml. */ QDomDocument xmlSceneList(const QString &scene); /** @brief Saves the project file xml to a file. */ bool saveSceneList(const QString &path, const QString &scene); void cacheImage(const QString &fileId, const QImage &img) const; void setProjectFolder(QUrl url); void setZone(int start, int end); QPoint zone() const; int setSceneList(); void setDocumentProperty(const QString &name, const QString &value); const QString getDocumentProperty(const QString &name, const QString &defaultValue = QString()) const; /** @brief Gets the list of renderer properties saved into the document. */ QMap getRenderProperties() const; /** @brief Read the display ratio from an xml project file. */ static double getDisplayRatio(const QString &path); /** @brief Backup the project file */ void backupLastSavedVersion(const QString &path); /** @brief Returns the document metadata (author, copyright, ...) */ const QMap metadata() const; /** @brief Set the document metadata (author, copyright, ...) */ void setMetadata(const QMap & meta); /** @brief Get frame size of the renderer (profile)*/ const QSize getRenderSize() const; /** @brief Add url to the file watcher so that we monitor changes */ void watchFile(const QUrl &url); /** @brief Get all document properties that need to be saved */ QMap documentProperties(); bool useProxy() const; bool autoGenerateProxy(int width) const; bool autoGenerateImageProxy(int width) const; QString documentNotes() const; /** @brief Saves effects embedded in project file. */ void saveCustomEffects(const QDomNodeList &customeffects); void resetProfile(); /** @brief Force processing of clip id in producer queue. */ void forceProcessing(const QString &id); void getFileProperties(const QDomElement &xml, const QString &clipId, int imageHeight, bool replaceProducer = true); /** @brief Returns true if the profile file has changed. */ bool profileChanged(const QString &profile) const; void doAddAction(const QString &name, QAction *a); void invalidatePreviews(QList chunks); void previewProgress(int p); /** @brief Select most appropriate rendering profile for timeline preview based on fps / size. */ void selectPreviewProfile(); private: QUrl m_url; QDomDocument m_document; KDirWatch m_fileWatcher; /** Timer used to reload clips when they have been externally modified */ QTimer m_modifiedTimer; /** List of the clip IDs that need to be reloaded after being externally modified */ QMap m_modifiedClips; int m_width; int m_height; Timecode m_timecode; Render *m_render; QTextEdit *m_notesWidget; QUndoStack *m_commandStack; ClipManager *m_clipManager; MltVideoProfile m_profile; QString m_searchFolder; /** @brief Tells whether the current document has been changed after being saved. */ bool m_modified; /** @brief The project folder, used to store project files (titles, effects...). */ QUrl m_projectFolder; + int m_undoPreviewIndex; + QList m_undoChunks; QMap m_documentProperties; QMap m_documentMetadata; QString searchFileRecursively(const QDir &dir, const QString &matchSize, const QString &matchHash) const; void moveProjectData(const QUrl &url); /** @brief Creates a new project. */ QDomDocument createEmptyDocument(int videotracks, int audiotracks); QDomDocument createEmptyDocument(const QList &tracks); /** @brief Updates the project folder location entry in the kdenlive file dialogs to point to the current project folder. */ void updateProjectFolderPlacesEntry(); /** @brief Only keep some backup files, delete some */ void cleanupBackupFiles(); /** @brief Load document properties from the xml file */ void loadDocumentProperties(); /** @brief update document properties to reflect a change in the current profile */ void updateProjectProfile(bool reloadProducers = false); + /** @brief Undo stack changed, restore timeline preview files if any*/ + void restoreTimelinePreviews(); public slots: void slotCreateTextTemplateClip(const QString &group, const QString &groupId, QUrl path); /** @brief Sets the document as modified or up to date. * @description If crash recovery is turned on, a timer calls KdenliveDoc::slotAutoSave() \n * Emits docModified conected to MainWindow::slotUpdateDocumentState \n * @param mod (optional) true if the document has to be saved */ void setModified(bool mod = true); void slotProxyCurrentItem(bool doProxy, QList clipList = QList()); /** @brief Saves the current project at the autosave location. * @description The autosave files are in ~/.kde/data/stalefiles/kdenlive/ */ void slotAutoSave(); private slots: void slotClipModified(const QString &path); void slotClipMissing(const QString &path); void slotProcessModifiedClips(); - void slotModified(); + void slotModified(int ix); void slotSetDocumentNotes(const QString ¬es); void switchProfile(MltVideoProfile profile, const QString &id, const QDomElement &xml); void slotSwitchProfile(); signals: void resetProjectList(); void progressInfo(const QString &, int); /** @brief Informs that the document status has been changed. * * If the document has been modified, it's called with true as an argument. */ void docModified(bool); void selectLastAddedClip(const QString &); void guidesUpdated(); /** @brief When creating a backup file, also save a thumbnail of current timeline */ void saveTimelinePreview(const QString &path); /** @brief Trigger the autosave timer start */ void startAutoSave(); /** @brief Current doc created effects, reload list */ void reloadEffects(); /** @brief Fps was changed, update timeline */ void updateFps(bool changed); + /** @brief Some timeline preview chunks restored, reload them */ + void reloadChunks(QList chunks); + }; #endif diff --git a/src/kdenlivesettings.kcfg b/src/kdenlivesettings.kcfg index 3f19453ac..1c4d1b480 100644 --- a/src/kdenlivesettings.kcfg +++ b/src/kdenlivesettings.kcfg @@ -1,924 +1,910 @@ 0 4 false false true true false 00:00:05:00 00:00:05:00 00:00:00:01 false false false false false false 00:00:05:00 true 3 2 false false false 1000 2000 0 1 25 false true true true false false true false 0 false false - - - 0 - - - - - - - - - - - false 0 sdl_audio 0 #999999 100 true 0 false 0 1 2 1 /tmp/ $HOME/kdenlive true $HOME true 0 0 false false 0 /dev/video0 2 default 0 true false 0 0 0 false 0 0 1280 720 15.0 true false capture true 0 0 capture false 3 true true 0 true 0 1.7777778 25 atsc_1080p_25 atsc_1080p_25 true false false false true true false false false 0x15 0x05 false 0x07 true true false true #000000 true 320 240 true false false false true 5 3 false false false 10 0 false false true false false true true true 0 onefield nearest 0 diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index e650f5796..136e12a9c 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1,3658 +1,3647 @@ /*************************************************************************** * 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 "mainwindow.h" #include "mainwindowadaptor.h" #include "core.h" #include "bin/projectclip.h" #include "bin/generators/generators.h" #include "library/librarywidget.h" #include "monitor/scopes/audiographspectrum.h" #include "mltcontroller/clipcontroller.h" #include "kdenlivesettings.h" #include "dialogs/kdenlivesettingsdialog.h" #include "dialogs/clipcreationdialog.h" #include "effectslist/initeffects.h" #include "project/dialogs/projectsettings.h" #include "project/clipmanager.h" #include "monitor/monitor.h" #include "monitor/recmonitor.h" #include "monitor/monitormanager.h" #include "doc/kdenlivedoc.h" #include "timeline/timeline.h" #include "timeline/track.h" #include "timeline/customtrackview.h" #include "effectslist/effectslistview.h" #include "effectslist/effectbasket.h" #include "effectstack/effectstackview2.h" #include "project/transitionsettings.h" #include "mltcontroller/bincontroller.h" #include "mltcontroller/producerqueue.h" #include "dialogs/renderwidget.h" #include "renderer.h" #include "dialogs/wizard.h" #include "project/projectcommands.h" #include "titler/titlewidget.h" #include "timeline/markerdialog.h" #include "timeline/clipitem.h" #include "interfaces.h" #include "project/cliptranscode.h" #include "scopes/scopemanager.h" #include "project/dialogs/archivewidget.h" #include "utils/resourcewidget.h" #include "layoutmanagement.h" #include "hidetitlebars.h" #include "mltconnection.h" #include "project/projectmanager.h" #include "timeline/timelinesearch.h" #include #include "utils/thememanager.h" #include "utils/KoIconUtils.h" #ifdef USE_JOGSHUTTLE #include "jogshuttle/jogmanager.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include MyToolButton::MyToolButton(QWidget *parent) : QToolButton(parent) , m_defaultAction(NULL) { setPopupMode(MenuButtonPopup); m_progress = width() - 6; m_pix = new QPixmap(1,1); m_pix->fill(Qt::transparent); m_dummyAction = new QAction(QIcon(*m_pix), i18n("Rendering preview"), this); } MyToolButton::~MyToolButton() { delete m_pix; delete m_dummyAction; } void MyToolButton::setProgress(int progress) { int prog = (width() - 6) * (double) progress / 1000; if (m_timer.isValid()) { // calculate remaining time qint64 ms = m_timer.elapsed() * (1000.0 / progress - 1); if (ms < 60000) m_remainingTime = i18nc("s as seconds", "%1s", ms / 1000); else if (ms < 3600000) m_remainingTime = i18nc("m as minutes", "%1m", ms / 60000); else { m_remainingTime = i18nc("h as hours", "%1h", qMax(99, (int) (ms / 3600000))); } } if (prog == m_progress) return; if (progress < 0) { if (m_defaultAction) setDefaultAction(m_defaultAction); m_remainingTime.clear(); m_timer.invalidate(); m_progress = -1; } else { if (!m_timer.isValid() || progress == 0) { if (!m_defaultAction) { m_defaultAction = defaultAction(); } setDefaultAction(m_dummyAction); m_timer.start(); } if (progress == 1000) { if (m_defaultAction) setDefaultAction(m_defaultAction); m_remainingTime.clear(); m_timer.invalidate(); } m_progress = prog; } update(); } int MyToolButton::progress() const { return m_progress; } void MyToolButton::paintEvent(QPaintEvent *event) { QToolButton::paintEvent(event); if (m_progress < width() - 6) { QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing, true); if (m_progress < 0) painter.fillRect(3, height() - 5, (width() - 6), 3, Qt::red); else { if (m_progress > 0) { // draw remaining time painter.drawText(rect(), Qt::AlignLeft, m_remainingTime); } QColor w(Qt::white); w.setAlpha(40); painter.fillRect(3, height() - 5, m_progress, 3, palette().highlight().color()); painter.fillRect(3 + m_progress, height() - 5, width() - 6 - m_progress, 3, w); } painter.setPen(palette().shadow().color()); painter.drawRoundedRect(2, height() - 6, width() - 4, 5, 2, 2); } } static const char version[] = KDENLIVE_VERSION; namespace Mlt { class Producer; }; EffectsList MainWindow::videoEffects; EffectsList MainWindow::audioEffects; EffectsList MainWindow::customEffects; EffectsList MainWindow::transitions; QMap MainWindow::m_lumacache; QMap MainWindow::m_lumaFiles; /*static bool sortByNames(const QPair &a, const QPair &b) { return a.first < b.first; }*/ // determine the the default KDE style as defined BY THE USER // (as opposed to whatever style KDE considers default) static QString defaultStyle(const char *fallback=Q_NULLPTR) { KSharedConfigPtr kdeGlobals = KSharedConfig::openConfig(QStringLiteral("kdeglobals"), KConfig::NoGlobals); KConfigGroup cg(kdeGlobals, "KDE"); return cg.readEntry("widgetStyle", fallback); } MainWindow::MainWindow(const QString &MltPath, const QUrl &Url, const QString & clipsToLoad, QWidget *parent) : KXmlGuiWindow(parent), m_timelineArea(NULL), m_stopmotion(NULL), m_effectStack(NULL), m_exitCode(EXIT_SUCCESS), m_effectList(NULL), m_transitionList(NULL), m_clipMonitor(NULL), m_projectMonitor(NULL), m_recMonitor(NULL), m_renderWidget(NULL), m_themeInitialized(false), m_isDarkTheme(false) { qRegisterMetaType ("audioShortVector"); qRegisterMetaType< QVector > ("QVector"); qRegisterMetaType ("MessageType"); qRegisterMetaType ("stringMap"); qRegisterMetaType ("audioByteArray"); qRegisterMetaType< QVector > (); qRegisterMetaType ("QDomElement"); qRegisterMetaType ("requestClipInfo"); qRegisterMetaType ("MltVideoProfile"); Core::build(this); // Widget themes for non KDE users KActionMenu *stylesAction= new KActionMenu(i18n("Style"), this); QActionGroup *stylesGroup = new QActionGroup(stylesAction); // GTK theme does not work well with Kdenlive, and does not support color theming, so avoid it QStringList availableStyles = QStyleFactory::keys(); QString desktopStyle = QApplication::style()->objectName(); if (QString::compare(desktopStyle, QLatin1String("GTK+"), Qt::CaseInsensitive) == 0 && KdenliveSettings::widgetstyle().isEmpty()) { if (availableStyles.contains(QLatin1String("breeze"), Qt::CaseInsensitive)) { // Auto switch to Breeze theme KdenliveSettings::setWidgetstyle(QStringLiteral("Breeze")); } else if (availableStyles.contains(QLatin1String("fusion"), Qt::CaseInsensitive)) { KdenliveSettings::setWidgetstyle(QStringLiteral("Fusion")); } } // Add default style action QAction *defaultStyle = new QAction(i18n("Default"), stylesGroup); defaultStyle->setCheckable(true); stylesAction->addAction(defaultStyle); if (KdenliveSettings::widgetstyle().isEmpty()) { defaultStyle->setChecked(true); } foreach(const QString &style, availableStyles) { QAction *a = new QAction(style, stylesGroup); a->setCheckable(true); a->setData(style); if (KdenliveSettings::widgetstyle() == style) { a->setChecked(true); } stylesAction->addAction(a); } connect(stylesGroup, &QActionGroup::triggered, this, &MainWindow::slotChangeStyle); // Color schemes KActionMenu *themeAction= new KActionMenu(i18n("Theme"), this); ThemeManager::instance()->setThemeMenuAction(themeAction); ThemeManager::instance()->setCurrentTheme(KdenliveSettings::colortheme()); connect(ThemeManager::instance(), SIGNAL(signalThemeChanged(const QString &)), this, SLOT(slotThemeChanged(const QString &)), Qt::DirectConnection); if (!KdenliveSettings::widgetstyle().isEmpty() && QString::compare(desktopStyle, KdenliveSettings::widgetstyle(), Qt::CaseInsensitive) != 0) { // User wants a custom widget style, init doChangeStyle(); } else ThemeManager::instance()->slotChangePalette(); new RenderingAdaptor(this); pCore->initialize(); MltConnection::locateMeltAndProfilesPath(MltPath); KdenliveSettings::setCurrent_profile(KdenliveSettings::default_profile()); // If using a custom profile, make sure the file exists or fallback to default if (KdenliveSettings::current_profile().startsWith(QStringLiteral("/")) && !QFile::exists(KdenliveSettings::current_profile())) { KMessageBox::sorry(this, i18n("Cannot find your default profile, switching to ATSC 1080p 25")); KdenliveSettings::setCurrent_profile("atsc_1080p_25"); KdenliveSettings::setDefault_profile("atsc_1080p_25"); } m_commandStack = new QUndoGroup; m_timelineArea = new QTabWidget(this); //m_timelineArea->setTabReorderingEnabled(true); m_timelineArea->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); m_timelineArea->setMinimumHeight(200); // Hide tabbar QTabBar *bar = m_timelineArea->findChild(); bar->setHidden(true); m_gpuAllowed = initEffects::parseEffectFiles(pCore->binController()->mltRepository()); //initEffects::parseCustomEffectsFile(); m_shortcutRemoveFocus = new QShortcut(QKeySequence(QStringLiteral("Esc")), this); connect(m_shortcutRemoveFocus, SIGNAL(activated()), this, SLOT(slotRemoveFocus())); /// Add Widgets setDockOptions(QMainWindow::AllowNestedDocks | QMainWindow::AllowTabbedDocks); #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) setDockOptions(dockOptions() | QMainWindow::GroupedDragging); #endif setTabPosition(Qt::AllDockWidgetAreas, KdenliveSettings::verticaltabs() ? QTabWidget::East : QTabWidget::North); QToolBar *timelineTb = new QToolBar(this);//pCore->window()->toolBar("timelineToolBar"); QWidget *ctn = new QWidget(this); QVBoxLayout *ctnLay = new QVBoxLayout; ctnLay->setSpacing(0); ctnLay->setContentsMargins(0, 0, 0, 0); ctn->setLayout(ctnLay); ctnLay->addWidget(timelineTb); QFrame *fr = new QFrame(this); fr->setFrameShape(QFrame::HLine); fr->setMaximumHeight(1); fr->setLineWidth(1); ctnLay->addWidget(fr); ctnLay->addWidget(m_timelineArea); ctn->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); setCentralWidget(ctn); m_projectBinDock = addDock(i18n("Project Bin"), QStringLiteral("project_bin"), pCore->bin()); QDockWidget * libraryDock = addDock(i18n("Library"), QStringLiteral("library"), pCore->library()); m_clipMonitor = new Monitor(Kdenlive::ClipMonitor, pCore->monitorManager(), this); pCore->bin()->setMonitor(m_clipMonitor); connect(m_clipMonitor, &Monitor::showConfigDialog, this, &MainWindow::slotPreferences); connect(m_clipMonitor, &Monitor::addMarker, this, &MainWindow::slotAddMarkerGuideQuickly); connect(m_clipMonitor, &Monitor::deleteMarker, this, &MainWindow::slotDeleteClipMarker); connect(m_clipMonitor, &Monitor::seekToPreviousSnap, this, &MainWindow::slotSnapRewind); connect(m_clipMonitor, &Monitor::seekToNextSnap, this, &MainWindow::slotSnapForward); connect(pCore->bin(), SIGNAL(clipNeedsReload(QString,bool)),this, SLOT(slotUpdateClip(QString,bool))); connect(pCore->bin(), SIGNAL(findInTimeline(QString)), this, SLOT(slotClipInTimeline(QString))); //TODO deprecated, replace with Bin methods if necessary /*connect(m_projectList, SIGNAL(loadingIsOver()), this, SLOT(slotElapsedTime())); connect(m_projectList, SIGNAL(displayMessage(QString,int,MessageType)), this, SLOT(slotGotProgressInfo(QString,int,MessageType))); connect(m_projectList, SIGNAL(updateRenderStatus()), this, SLOT(slotCheckRenderStatus())); connect(m_projectList, SIGNAL(updateProfile(QString)), this, SLOT(slotUpdateProjectProfile(QString))); connect(m_projectList, SIGNAL(refreshClip(QString,bool)), pCore->monitorManager(), SLOT(slotRefreshCurrentMonitor(QString))); connect(m_clipMonitor, SIGNAL(zoneUpdated(QPoint)), m_projectList, SLOT(slotUpdateClipCut(QPoint)));*/ connect(m_clipMonitor, SIGNAL(extractZone(QString)), pCore->bin(), SLOT(slotStartCutJob(QString))); connect(m_clipMonitor, SIGNAL(passKeyPress(QKeyEvent*)), this, SLOT(triggerKey(QKeyEvent*))); m_projectMonitor = new Monitor(Kdenlive::ProjectMonitor, pCore->monitorManager(), this); connect(m_projectMonitor, SIGNAL(passKeyPress(QKeyEvent*)), this, SLOT(triggerKey(QKeyEvent*))); connect(m_projectMonitor, &Monitor::addMarker, this, &MainWindow::slotAddMarkerGuideQuickly); connect(m_projectMonitor, &Monitor::deleteMarker, this, &MainWindow::slotDeleteClipMarker); connect(m_projectMonitor, &Monitor::seekToPreviousSnap, this, &MainWindow::slotSnapRewind); connect(m_projectMonitor, &Monitor::seekToNextSnap, this, &MainWindow::slotSnapForward); connect(m_projectMonitor, SIGNAL(updateGuide(int, QString)), this, SLOT(slotEditGuide(int, QString))); /* //TODO disabled until ported to qml m_recMonitor = new RecMonitor(Kdenlive::RecordMonitor, pCore->monitorManager(), this); connect(m_recMonitor, SIGNAL(addProjectClip(QUrl)), this, SLOT(slotAddProjectClip(QUrl))); connect(m_recMonitor, SIGNAL(addProjectClipList(QList)), this, SLOT(slotAddProjectClipList(QList))); */ pCore->monitorManager()->initMonitors(m_clipMonitor, m_projectMonitor, m_recMonitor); connect(m_clipMonitor, SIGNAL(addMasterEffect(QString,QDomElement)), pCore->bin(), SLOT(slotEffectDropped(QString,QDomElement))); // Audio spectrum scope m_audioSpectrum = new AudioGraphSpectrum(pCore->monitorManager()); QDockWidget * spectrumDock = addDock(i18n("Audio Spectrum"), QStringLiteral("audiospectrum"), m_audioSpectrum); connect(this, &MainWindow::reloadTheme, m_audioSpectrum, &AudioGraphSpectrum::refreshPixmap); // Close library and audiospectrum on first run libraryDock->close(); spectrumDock->close(); m_effectStack = new EffectStackView2(m_projectMonitor, this); connect(m_effectStack, SIGNAL(startFilterJob(const ItemInfo&,const QString&,QMap&,QMap&,QMap&)), pCore->bin(), SLOT(slotStartFilterJob(const ItemInfo &,const QString&,QMap&,QMap&,QMap&))); connect(pCore->bin(), SIGNAL(masterClipSelected(ClipController *, Monitor *)), m_effectStack, SLOT(slotMasterClipItemSelected(ClipController *, Monitor *))); connect(pCore->bin(), SIGNAL(masterClipUpdated(ClipController *, Monitor *)), m_effectStack, SLOT(slotRefreshMasterClipEffects(ClipController *, Monitor *))); connect(m_effectStack, SIGNAL(addMasterEffect(QString,QDomElement)), pCore->bin(), SLOT(slotEffectDropped(QString,QDomElement))); connect(m_effectStack, SIGNAL(removeMasterEffect(QString,QDomElement)), pCore->bin(), SLOT(slotDeleteEffect(QString,QDomElement))); connect(m_effectStack, SIGNAL(changeEffectPosition(QString,const QList ,int)), pCore->bin(), SLOT(slotMoveEffect(QString,const QList ,int))); connect(m_effectStack, SIGNAL(reloadEffects()), this, SLOT(slotReloadEffects())); connect(m_effectStack, SIGNAL(displayMessage(QString,int)), this, SLOT(slotGotProgressInfo(QString,int))); m_effectStackDock = addDock(i18n("Properties"), QStringLiteral("effect_stack"), m_effectStack); m_effectList = new EffectsListView(); m_effectListDock = addDock(i18n("Effects"), QStringLiteral("effect_list"), m_effectList); m_transitionList = new EffectsListView(EffectsListView::TransitionMode); m_transitionListDock = addDock(i18n("Transitions"), QStringLiteral("transition_list"), m_transitionList); setupActions(); // Add monitors here to keep them at the right of the window m_clipMonitorDock = addDock(i18n("Clip Monitor"), QStringLiteral("clip_monitor"), m_clipMonitor); m_projectMonitorDock = addDock(i18n("Project Monitor"), QStringLiteral("project_monitor"), m_projectMonitor); if (m_recMonitor) { m_recMonitorDock = addDock(i18n("Record Monitor"), QStringLiteral("record_monitor"), m_recMonitor); } m_undoView = new QUndoView(); m_undoView->setCleanIcon(KoIconUtils::themedIcon(QStringLiteral("edit-clear"))); m_undoView->setEmptyLabel(i18n("Clean")); m_undoView->setGroup(m_commandStack); m_undoViewDock = addDock(i18n("Undo History"), QStringLiteral("undo_history"), m_undoView); // Color and icon theme stuff addAction(QStringLiteral("themes_menu"), themeAction); connect(m_commandStack, SIGNAL(cleanChanged(bool)), m_saveAction, SLOT(setDisabled(bool))); addAction(QStringLiteral("styles_menu"), stylesAction); // Close non-general docks for the initial layout // only show important ones m_undoViewDock->close(); /// Tabify Widgets /// tabifyDockWidget(m_transitionListDock, m_effectListDock); tabifyDockWidget(pCore->bin()->clipPropertiesDock(), m_effectStackDock); //tabifyDockWidget(m_effectListDock, m_effectStackDock); tabifyDockWidget(m_clipMonitorDock, m_projectMonitorDock); if (m_recMonitor) { tabifyDockWidget(m_clipMonitorDock, m_recMonitorDock); } readOptions(); QAction *action; // Stop motion actions. Beware of the order, we MUST use the same order in stopmotion/stopmotion.cpp m_stopmotion_actions = new KActionCategory(i18n("Stop Motion"), actionCollection()); action = new QAction(KoIconUtils::themedIcon(QStringLiteral("media-record")), i18n("Capture frame"), this); //action->setShortcutContext(Qt::WidgetWithChildrenShortcut); m_stopmotion_actions->addAction(QStringLiteral("stopmotion_capture"), action); action = new QAction(i18n("Switch live / captured frame"), this); //action->setShortcutContext(Qt::WidgetWithChildrenShortcut); m_stopmotion_actions->addAction(QStringLiteral("stopmotion_switch"), action); action = new QAction(KoIconUtils::themedIcon(QStringLiteral("edit-paste")), i18n("Show last frame over video"), this); action->setCheckable(true); action->setChecked(false); m_stopmotion_actions->addAction(QStringLiteral("stopmotion_overlay"), action); // Monitor Record action addAction(QStringLiteral("switch_monitor_rec"), m_clipMonitor->recAction()); // Build effects menu m_effectsMenu = new QMenu(i18n("Add Effect"), this); m_effectActions = new KActionCategory(i18n("Effects"), actionCollection()); m_effectList->reloadEffectList(m_effectsMenu, m_effectActions); m_transitionsMenu = new QMenu(i18n("Add Transition"), this); m_transitionActions = new KActionCategory(i18n("Transitions"), actionCollection()); m_transitionList->reloadEffectList(m_transitionsMenu, m_transitionActions); ScopeManager *scmanager = new ScopeManager(this); new LayoutManagement(this); new HideTitleBars(this); new TimelineSearch(this); m_extraFactory = new KXMLGUIClient(this); buildDynamicActions(); // Add shortcut to action tooltips QList< KActionCollection * > collections = KActionCollection::allCollections(); for (int i = 0; i < collections.count(); ++i) { KActionCollection *coll = collections.at(i); foreach( QAction* tempAction, coll->actions()) { // find the shortcut pattern and delete (note the preceding space in the RegEx) QString strippedTooltip = tempAction->toolTip().remove(QRegExp("\\s\\(.*\\)")); // append shortcut if it exists for action if (tempAction->shortcut() == QKeySequence(0)) tempAction->setToolTip( strippedTooltip); else tempAction->setToolTip( strippedTooltip + " (" + tempAction->shortcut().toString() + ")"); } } // Create Effect Basket (dropdown list of favorites) m_effectBasket = new EffectBasket(m_effectList); connect(m_effectBasket, SIGNAL(addEffect(QDomElement)), this, SLOT(slotAddEffect(QDomElement))); QWidgetAction *widgetlist = new QWidgetAction(this); widgetlist->setDefaultWidget(m_effectBasket); //widgetlist->setText(i18n("Favorite Effects")); widgetlist->setToolTip(i18n("Favorite Effects")); widgetlist->setIcon(KoIconUtils::themedIcon("favorite")); QMenu *menu = new QMenu(this); menu->addAction(widgetlist); QToolButton *basketButton = new QToolButton(this); basketButton->setMenu(menu); basketButton->setToolButtonStyle(toolBar()->toolButtonStyle()); basketButton->setDefaultAction(widgetlist); basketButton->setPopupMode(QToolButton::InstantPopup); //basketButton->setText(i18n("Favorite Effects")); basketButton->setToolTip(i18n("Favorite Effects")); basketButton->setIcon(KoIconUtils::themedIcon(QStringLiteral("favorite"))); QWidgetAction* toolButtonAction = new QWidgetAction(this); //toolButtonAction->setText(i18n("Favorite Effects")); toolButtonAction->setIcon(KoIconUtils::themedIcon("favorite")); toolButtonAction->setDefaultWidget(basketButton); //addAction(QStringLiteral("favorite_effects"), toolButtonAction); connect(toolButtonAction, SIGNAL(triggered(bool)), basketButton, SLOT(showMenu())); setupGUI(); /*ScriptingPart* sp = new ScriptingPart(this, QStringList()); guiFactory()->addClient(sp);*/ loadGenerators(); loadDockActions(); loadClipActions(); // Connect monitor overlay info menu. QMenu *monitorOverlay = static_cast(factory()->container(QStringLiteral("monitor_config_overlay"), this)); connect(monitorOverlay, SIGNAL(triggered(QAction*)), this, SLOT(slotSwitchMonitorOverlay(QAction*))); m_projectMonitor->setupMenu(static_cast(factory()->container(QStringLiteral("monitor_go"), this)), monitorOverlay, m_playZone, m_loopZone, NULL, m_loopClip); m_clipMonitor->setupMenu(static_cast(factory()->container(QStringLiteral("monitor_go"), this)), monitorOverlay, m_playZone, m_loopZone, static_cast(factory()->container(QStringLiteral("marker_menu"), this))); QMenu *clipInTimeline = static_cast(factory()->container(QStringLiteral("clip_in_timeline"), this)); clipInTimeline->setIcon(KoIconUtils::themedIcon(QStringLiteral("go-jump"))); pCore->bin()->setupGeneratorMenu(); connect(pCore->monitorManager(), SIGNAL(updateOverlayInfos(int,int)), this, SLOT(slotUpdateMonitorOverlays(int,int))); // Setup and fill effects and transitions menus. QMenu *m = static_cast(factory()->container(QStringLiteral("video_effects_menu"), this)); connect(m, SIGNAL(triggered(QAction*)), this, SLOT(slotAddVideoEffect(QAction*))); connect(m_effectsMenu, SIGNAL(triggered(QAction*)), this, SLOT(slotAddVideoEffect(QAction*))); connect(m_transitionsMenu, SIGNAL(triggered(QAction*)), this, SLOT(slotAddTransition(QAction*))); m_timelineContextMenu = new QMenu(this); m_timelineContextClipMenu = new QMenu(this); m_timelineContextTransitionMenu = new QMenu(this); m_timelineContextMenu->addAction(actionCollection()->action(QStringLiteral("insert_space"))); m_timelineContextMenu->addAction(actionCollection()->action(QStringLiteral("delete_space"))); m_timelineContextMenu->addAction(actionCollection()->action(KStandardAction::name(KStandardAction::Paste))); m_timelineContextClipMenu->addAction(actionCollection()->action(QStringLiteral("clip_in_project_tree"))); //m_timelineContextClipMenu->addAction(actionCollection()->action("clip_to_project_tree")); m_timelineContextClipMenu->addAction(actionCollection()->action(QStringLiteral("delete_timeline_clip"))); m_timelineContextClipMenu->addSeparator(); m_timelineContextClipMenu->addAction(actionCollection()->action(QStringLiteral("group_clip"))); m_timelineContextClipMenu->addAction(actionCollection()->action(QStringLiteral("ungroup_clip"))); m_timelineContextClipMenu->addAction(actionCollection()->action(QStringLiteral("split_audio"))); m_timelineContextClipMenu->addAction(actionCollection()->action(QStringLiteral("set_audio_align_ref"))); m_timelineContextClipMenu->addAction(actionCollection()->action(QStringLiteral("align_audio"))); m_timelineContextClipMenu->addSeparator(); m_timelineContextClipMenu->addAction(actionCollection()->action(QStringLiteral("cut_timeline_clip"))); m_timelineContextClipMenu->addAction(actionCollection()->action(KStandardAction::name(KStandardAction::Copy))); m_timelineContextClipMenu->addAction(actionCollection()->action(QStringLiteral("paste_effects"))); m_timelineContextClipMenu->addSeparator(); QMenu *markersMenu = static_cast(factory()->container(QStringLiteral("marker_menu"), this)); m_timelineContextClipMenu->addMenu(markersMenu); m_timelineContextClipMenu->addSeparator(); m_timelineContextClipMenu->addMenu(m_transitionsMenu); m_timelineContextClipMenu->addMenu(m_effectsMenu); m_timelineContextTransitionMenu->addAction(actionCollection()->action(QStringLiteral("delete_timeline_clip"))); m_timelineContextTransitionMenu->addAction(actionCollection()->action(KStandardAction::name(KStandardAction::Copy))); m_timelineContextTransitionMenu->addAction(actionCollection()->action(QStringLiteral("auto_transition"))); connect(m_effectList, SIGNAL(addEffect(QDomElement)), this, SLOT(slotAddEffect(QDomElement))); connect(m_effectList, SIGNAL(reloadEffects()), this, SLOT(slotReloadEffects())); slotConnectMonitors(); timelineTb->setToolButtonStyle(Qt::ToolButtonIconOnly); KSelectAction *sceneMode = new KSelectAction(this); sceneMode->addAction(m_normalEditTool); sceneMode->addAction(m_overwriteEditTool); sceneMode->addAction(m_insertEditTool); sceneMode->setCurrentItem(0); timelineTb->addAction(sceneMode); timelineTb->addSeparator(); timelineTb->addAction(m_buttonSelectTool); timelineTb->addAction(m_buttonRazorTool); timelineTb->addAction(m_buttonSpacerTool); timelineTb->addSeparator(); m_timeFormatButton = new KSelectAction(QStringLiteral("00:00:00:00 / 00:00:00:00"), this); QFont fixedFont = QFontDatabase::systemFont(QFontDatabase::FixedFont); m_timeFormatButton->setFont(fixedFont); m_timeFormatButton->addAction(i18n("hh:mm:ss:ff")); m_timeFormatButton->addAction(i18n("Frames")); if (KdenliveSettings::frametimecode()) m_timeFormatButton->setCurrentItem(1); else m_timeFormatButton->setCurrentItem(0); connect(m_timeFormatButton, SIGNAL(triggered(int)), this, SLOT(slotUpdateTimecodeFormat(int))); m_timeFormatButton->setToolBarMode(KSelectAction::MenuMode); m_timeFormatButton->setToolButtonPopupMode(QToolButton::InstantPopup); const QFontMetrics metric(fixedFont); int requiredWidth = metric.boundingRect(QStringLiteral("00:00:00:00 / 00:00:00:00")).width() + 20; timelineTb->addAction(m_timeFormatButton); QWidget *actionWidget = timelineTb->widgetForAction(m_timeFormatButton); actionWidget->setObjectName(QStringLiteral("timecode")); actionWidget->setMinimumWidth(requiredWidth); timelineTb->addSeparator(); timelineTb->addAction(actionCollection()->action(QStringLiteral("insert_to_in_point"))); timelineTb->addAction(actionCollection()->action(QStringLiteral("overwrite_to_in_point"))); timelineTb->addAction(actionCollection()->action(QStringLiteral("remove_extract"))); timelineTb->addAction(actionCollection()->action(QStringLiteral("remove_lift"))); MyToolButton *timelinePreview = new MyToolButton(this); QMenu *tlMenu = new QMenu(this); timelinePreview->setMenu(tlMenu); connect(this, &MainWindow::setPreviewProgress, timelinePreview, &MyToolButton::setProgress); QAction *prevRender = actionCollection()->action(QStringLiteral("prerender_timeline_zone")); tlMenu->addAction(actionCollection()->action(QStringLiteral("stop_prerender_timeline"))); tlMenu->addAction(actionCollection()->action(QStringLiteral("set_render_timeline_zone"))); tlMenu->addAction(actionCollection()->action(QStringLiteral("unset_render_timeline_zone"))); tlMenu->addAction(actionCollection()->action(QStringLiteral("unset_render_timeline_zone"))); // Automatic timeline preview action QAction *autoRender = new QAction(KoIconUtils::themedIcon(QStringLiteral("view-refresh")), i18n("Automatic Preview"), this); autoRender->setCheckable(true); autoRender->setChecked(KdenliveSettings::autopreview()); connect(autoRender, &QAction::triggered, this, &MainWindow::slotToggleAutoPreview); tlMenu->addAction(autoRender); timelinePreview->setDefaultAction(prevRender); timelinePreview->setAutoRaise(true); timelineTb->addWidget(timelinePreview); timelineTb->addAction(toolButtonAction); QWidget *sep = new QWidget(this); sep->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); timelineTb->addWidget(sep); timelineTb->addAction(m_buttonAutomaticSplitAudio); timelineTb->addAction(m_buttonVideoThumbs); timelineTb->addAction(m_buttonAudioThumbs); timelineTb->addAction(m_buttonShowMarkers); timelineTb->addAction(m_buttonSnap); timelineTb->addSeparator(); timelineTb->addAction(m_buttonFitZoom); timelineTb->addAction(m_zoomOut); timelineTb->addWidget(m_zoomSlider); timelineTb->addAction(m_zoomIn); // Populate encoding profiles KConfig conf(QStringLiteral("encodingprofiles.rc"), KConfig::CascadeConfig, QStandardPaths::DataLocation); if (KdenliveSettings::proxyparams().isEmpty() || KdenliveSettings::proxyextension().isEmpty()) { KConfigGroup group(&conf, "proxy"); QMap< QString, QString > values = group.entryMap(); QMapIterator i(values); if (i.hasNext()) { i.next(); QString data = i.value(); KdenliveSettings::setProxyparams(data.section(';', 0, 0)); KdenliveSettings::setProxyextension(data.section(';', 1, 1)); } } if (KdenliveSettings::v4l_parameters().isEmpty() || KdenliveSettings::v4l_extension().isEmpty()) { KConfigGroup group(&conf, "video4linux"); QMap< QString, QString > values = group.entryMap(); QMapIterator i(values); if (i.hasNext()) { i.next(); QString data = i.value(); KdenliveSettings::setV4l_parameters(data.section(';', 0, 0)); KdenliveSettings::setV4l_extension(data.section(';', 1, 1)); } } - if (KdenliveSettings::tl_parameters().isEmpty() || KdenliveSettings::tl_extension().isEmpty()) { - KConfigGroup group(&conf, "timelinepreview"); - QMap< QString, QString > values = group.entryMap(); - QMapIterator i(values); - if (i.hasNext()) { - i.next(); - QString data = i.value(); - KdenliveSettings::setTl_parameters(data.section(';', 0, 0)); - KdenliveSettings::setTl_extension(data.section(';', 1, 1)); - } - } if (KdenliveSettings::grab_parameters().isEmpty() || KdenliveSettings::grab_extension().isEmpty()) { KConfigGroup group(&conf, "screengrab"); QMap< QString, QString > values = group.entryMap(); QMapIterator i(values); if (i.hasNext()) { i.next(); QString data = i.value(); KdenliveSettings::setGrab_parameters(data.section(';', 0, 0)); KdenliveSettings::setGrab_extension(data.section(';', 1, 1)); } } if (KdenliveSettings::decklink_parameters().isEmpty() || KdenliveSettings::decklink_extension().isEmpty()) { KConfigGroup group(&conf, "decklink"); QMap< QString, QString > values = group.entryMap(); QMapIterator i(values); if (i.hasNext()) { i.next(); QString data = i.value(); KdenliveSettings::setDecklink_parameters(data.section(';', 0, 0)); KdenliveSettings::setDecklink_extension(data.section(';', 1, 1)); } } statusBar()->show(); emit GUISetupDone(); pCore->projectManager()->init(Url, clipsToLoad); QTimer::singleShot(0, pCore->projectManager(), SLOT(slotLoadOnOpen())); connect(this, SIGNAL(reloadTheme()), this, SLOT(slotReloadTheme()), Qt::UniqueConnection); #ifdef USE_JOGSHUTTLE new JogManager(this); #endif scmanager->slotCheckActiveScopes(); //KMessageBox::information(this, "Warning, development version for testing only. we are currently working on core functionnalities,\ndo not save any project or your project files might be corrupted."); } void MainWindow::slotThemeChanged(const QString &theme) { disconnect(this, SIGNAL(reloadTheme()), this, SLOT(slotReloadTheme())); KSharedConfigPtr config = KSharedConfig::openConfig(theme); setPalette(KColorScheme::createApplicationPalette(config)); qApp->setPalette(palette()); QPalette plt = palette(); KdenliveSettings::setColortheme(theme); if (m_effectStack) { m_effectStack->updatePalette(); m_effectStack->transitionConfig()->updatePalette(); } if (m_effectList) m_effectList->updatePalette(); if (m_transitionList) m_transitionList->updatePalette(); if (m_clipMonitor) m_clipMonitor->setPalette(plt); if (m_projectMonitor) m_projectMonitor->setPalette(plt); setStatusBarStyleSheet(plt); if (pCore->projectManager() && pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->updatePalette(); } if (m_timelineArea) { m_timelineArea->setPalette(plt); } /*if (statusBar()) { const QObjectList children = statusBar()->children(); foreach(QObject * child, children) { if (child->isWidgetType()) ((QWidget*)child)->setPalette(plt); const QObjectList subchildren = child->children(); foreach(QObject * subchild, subchildren) { if (subchild->isWidgetType()) ((QWidget*)subchild)->setPalette(plt); } } }*/ #if KXMLGUI_VERSION_MINOR < 23 && KXMLGUI_VERSION_MAJOR == 5 // Not required anymore with auto colored icons since KF5 5.23 QColor background = plt.window().color(); bool useDarkIcons = background.value() < 100; if (m_themeInitialized && useDarkIcons != m_isDarkTheme) { if (pCore->bin()) { pCore->bin()->refreshIcons(); } if (m_clipMonitor) m_clipMonitor->refreshIcons(); if (m_projectMonitor) m_projectMonitor->refreshIcons(); if (pCore->monitorManager()) pCore->monitorManager()->refreshIcons(); if (m_effectStack && m_effectStack->transitionConfig()) m_effectStack->transitionConfig()->refreshIcons(); if (m_effectList) m_effectList->refreshIcons(); if (m_effectStack) m_effectStack->refreshIcons(); if (pCore->projectManager() && pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->refreshIcons(); } foreach(QAction* action, actionCollection()->actions()) { QIcon icon = action->icon(); if (icon.isNull()) continue; QString iconName = icon.name(); QIcon newIcon = KoIconUtils::themedIcon(iconName); if (newIcon.isNull()) continue; action->setIcon(newIcon); } } m_themeInitialized = true; m_isDarkTheme = useDarkIcons; #endif connect(this, SIGNAL(reloadTheme()), this, SLOT(slotReloadTheme()), Qt::UniqueConnection); } bool MainWindow::event(QEvent *e) { switch (e->type()) { case QEvent::ApplicationPaletteChange: emit reloadTheme(); e->accept(); break; default: break; } return KXmlGuiWindow::event(e); } void MainWindow::slotReloadTheme() { ThemeManager::instance()->slotSettingsChanged(); } MainWindow::~MainWindow() { delete m_stopmotion; delete m_audioSpectrum; m_effectStack->slotClipItemSelected(NULL, m_projectMonitor); m_effectStack->slotTransitionItemSelected(NULL, 0, QPoint(), false); if (m_projectMonitor) m_projectMonitor->stop(); if (m_clipMonitor) m_clipMonitor->stop(); delete pCore; delete m_effectStack; delete m_projectMonitor; delete m_clipMonitor; delete m_shortcutRemoveFocus; qDeleteAll(m_transitions); Mlt::Factory::close(); } //virtual bool MainWindow::queryClose() { if (m_renderWidget) { int waitingJobs = m_renderWidget->waitingJobsCount(); if (waitingJobs > 0) { switch (KMessageBox::warningYesNoCancel(this, i18np("You have 1 rendering job waiting in the queue.\nWhat do you want to do with this job?", "You have %1 rendering jobs waiting in the queue.\nWhat do you want to do with these jobs?", waitingJobs), QString(), KGuiItem(i18n("Start them now")), KGuiItem(i18n("Delete them")))) { case KMessageBox::Yes : // create script with waiting jobs and start it if (m_renderWidget->startWaitingRenderJobs() == false) return false; break; case KMessageBox::No : // Don't do anything, jobs will be deleted break; default: return false; } } } saveOptions(); // WARNING: According to KMainWindow::queryClose documentation we are not supposed to close the document here? return pCore->projectManager()->closeCurrentDocument(true, true); } void MainWindow::loadGenerators() { QMenu *addMenu = static_cast(factory()->container(QStringLiteral("generators"), this)); Generators::getGenerators(KdenliveSettings::producerslist(), addMenu); connect(addMenu, SIGNAL(triggered(QAction *)), this, SLOT(buildGenerator(QAction *))); } void MainWindow::buildGenerator(QAction *action) { Generators gen(m_clipMonitor, action->data().toString(), this); if (gen.exec() == QDialog::Accepted) { pCore->bin()->slotAddClipToProject(gen.getSavedClip()); } } void MainWindow::saveProperties(KConfigGroup &config) { // save properties here KXmlGuiWindow::saveProperties(config); //TODO: fix session management if (qApp->isSavingSession()) { if (pCore->projectManager()->current() && !pCore->projectManager()->current()->url().isEmpty()) { config.writeEntry("kdenlive_lastUrl", pCore->projectManager()->current()->url().path()); } } } void MainWindow::readProperties(const KConfigGroup &config) { // read properties here KXmlGuiWindow::readProperties(config); //TODO: fix session management /*if (qApp->isSessionRestored()) { pCore->projectManager()->openFile(QUrl::fromLocalFile(config.readEntry("kdenlive_lastUrl", QString()))); }*/ } void MainWindow::saveNewToolbarConfig() { KXmlGuiWindow::saveNewToolbarConfig(); //TODO for some reason all dynamically inserted actions are removed by the save toolbar // So we currently re-add them manually.... loadDockActions(); loadClipActions(); pCore->bin()->rebuildMenu(); } void MainWindow::slotReloadEffects() { initEffects::parseCustomEffectsFile(); m_effectList->reloadEffectList(m_effectsMenu, m_effectActions); } void MainWindow::configureNotifications() { KNotifyConfigWidget::configure(this); } void MainWindow::slotFullScreen() { KToggleFullScreenAction::setFullScreen(this, actionCollection()->action(QStringLiteral("fullscreen"))->isChecked()); } void MainWindow::slotAddEffect(const QDomElement &effect) { if (effect.isNull()) { //qDebug() << "--- ERROR, TRYING TO APPEND NULL EFFECT"; return; } QDomElement effectToAdd = effect.cloneNode().toElement(); EFFECTMODE status = m_effectStack->effectStatus(); if (status == TIMELINE_TRACK) { pCore->projectManager()->currentTimeline()->projectView()->slotAddTrackEffect(effectToAdd, pCore->projectManager()->currentTimeline()->tracksCount() - m_effectStack->trackIndex()); } else if (status == TIMELINE_CLIP) { pCore->projectManager()->currentTimeline()->projectView()->slotAddEffectToCurrentItem(effectToAdd); } else if (status == MASTER_CLIP) { pCore->bin()->slotEffectDropped(QString(), effectToAdd); } } void MainWindow::slotUpdateClip(const QString &id, bool reload) { ProjectClip *clip = pCore->bin()->getBinClip(id); if (!clip) { return; } pCore->projectManager()->currentTimeline()->projectView()->slotUpdateClip(id, reload); } void MainWindow::slotConnectMonitors() { //connect(m_projectList, SIGNAL(deleteProjectClips(QStringList,QMap)), this, SLOT(slotDeleteProjectClips(QStringList,QMap))); connect(m_projectMonitor->render, SIGNAL(gotFileProperties(requestClipInfo,ClipController *)), pCore->bin(), SLOT(slotProducerReady(requestClipInfo,ClipController *)), Qt::DirectConnection); connect(m_clipMonitor, SIGNAL(refreshClipThumbnail(QString)), pCore->bin(), SLOT(slotRefreshClipThumbnail(QString))); connect(m_projectMonitor, SIGNAL(requestFrameForAnalysis(bool)), this, SLOT(slotMonitorRequestRenderFrame(bool))); connect(m_projectMonitor, &Monitor::createSplitOverlay, this, &MainWindow::createSplitOverlay); connect(m_projectMonitor, &Monitor::removeSplitOverlay, this, &MainWindow::removeSplitOverlay); } void MainWindow::createSplitOverlay(Mlt::Filter *filter) { if (pCore->projectManager()->currentTimeline()) { if (pCore->projectManager()->currentTimeline()->projectView()->createSplitOverlay(filter)) { m_projectMonitor->activateSplit(); } } } void MainWindow::removeSplitOverlay() { if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->projectView()->removeSplitOverlay(); } } void MainWindow::addAction(const QString &name, QAction *action) { m_actionNames.append(name); actionCollection()->addAction(name, action); actionCollection()->setDefaultShortcut(action, action->shortcut()); //Fix warning about setDefaultShortcut } QAction *MainWindow::addAction(const QString &name, const QString &text, const QObject *receiver, const char *member, const QIcon &icon, const QKeySequence &shortcut) { QAction *action = new QAction(text, this); if (!icon.isNull()) { action->setIcon(icon); } if (!shortcut.isEmpty()) { action->setShortcut(shortcut); } addAction(name, action); connect(action, SIGNAL(triggered(bool)), receiver, member); return action; } void MainWindow::setupActions() { m_statusProgressBar = new QProgressBar(this); m_statusProgressBar->setMinimum(0); m_statusProgressBar->setMaximum(100); m_statusProgressBar->setMaximumWidth(150); m_statusProgressBar->setVisible(false); /*KToolBar *toolbar = new KToolBar(QStringLiteral("statusToolBar"), this, Qt::BottomToolBarArea); toolbar->setMovable(false); setStatusBarStyleSheet(palette()); QString styleBorderless = QStringLiteral("QToolButton { border-width: 0px;margin: 1px 3px 0px;padding: 0px;}");*/ //create edit mode buttons m_normalEditTool = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-normal-edit")), i18n("Normal mode"), this); m_normalEditTool->setShortcut(i18nc("Normal editing", "n")); //toolbar->addAction(m_normalEditTool); m_normalEditTool->setCheckable(true); m_normalEditTool->setChecked(true); m_overwriteEditTool = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-overwrite-edit")), i18n("Overwrite mode"), this); //m_overwriteEditTool->setShortcut(i18nc("Overwrite mode shortcut", "o")); //toolbar->addAction(m_overwriteEditTool); m_overwriteEditTool->setCheckable(true); m_overwriteEditTool->setChecked(false); m_insertEditTool = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-insert-edit")), i18n("Insert mode"), this); //m_insertEditTool->setShortcut(i18nc("Insert mode shortcut", "i")); //toolbar->addAction(m_insertEditTool); m_insertEditTool->setCheckable(true); m_insertEditTool->setChecked(false); QActionGroup *editGroup = new QActionGroup(this); editGroup->addAction(m_normalEditTool); editGroup->addAction(m_overwriteEditTool); editGroup->addAction(m_insertEditTool); editGroup->setExclusive(true); connect(editGroup, SIGNAL(triggered(QAction*)), this, SLOT(slotChangeEdit(QAction*))); //toolbar->addSeparator(); // create tools buttons m_buttonSelectTool = new QAction(KoIconUtils::themedIcon(QStringLiteral("cursor-arrow")), i18n("Selection tool"), this); m_buttonSelectTool->setShortcut(i18nc("Selection tool shortcut", "s")); //toolbar->addAction(m_buttonSelectTool); m_buttonSelectTool->setCheckable(true); m_buttonSelectTool->setChecked(true); m_buttonRazorTool = new QAction(KoIconUtils::themedIcon(QStringLiteral("edit-cut")), i18n("Razor tool"), this); m_buttonRazorTool->setShortcut(i18nc("Razor tool shortcut", "x")); //toolbar->addAction(m_buttonRazorTool); m_buttonRazorTool->setCheckable(true); m_buttonRazorTool->setChecked(false); m_buttonSpacerTool = new QAction(KoIconUtils::themedIcon(QStringLiteral("distribute-horizontal-x")), i18n("Spacer tool"), this); m_buttonSpacerTool->setShortcut(i18nc("Spacer tool shortcut", "m")); //toolbar->addAction(m_buttonSpacerTool); m_buttonSpacerTool->setCheckable(true); m_buttonSpacerTool->setChecked(false); QActionGroup *toolGroup = new QActionGroup(this); toolGroup->addAction(m_buttonSelectTool); toolGroup->addAction(m_buttonRazorTool); toolGroup->addAction(m_buttonSpacerTool); toolGroup->setExclusive(true); //toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly); /*QWidget * actionWidget; int max = toolbar->iconSizeDefault() + 2; actionWidget = toolbar->widgetForAction(m_normalEditTool); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4); actionWidget = toolbar->widgetForAction(m_insertEditTool); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4); actionWidget = toolbar->widgetForAction(m_overwriteEditTool); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4); actionWidget = toolbar->widgetForAction(m_buttonSelectTool); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4); actionWidget = toolbar->widgetForAction(m_buttonRazorTool); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4); actionWidget = toolbar->widgetForAction(m_buttonSpacerTool); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4);*/ connect(toolGroup, SIGNAL(triggered(QAction*)), this, SLOT(slotChangeTool(QAction*))); //toolbar->addSeparator(); m_buttonFitZoom = new QAction(KoIconUtils::themedIcon(QStringLiteral("zoom-fit-best")), i18n("Fit zoom to project"), this); //toolbar->addAction(m_buttonFitZoom); m_buttonFitZoom->setCheckable(false); m_zoomOut = new QAction(KoIconUtils::themedIcon(QStringLiteral("zoom-out")), i18n("Zoom Out"), this); //toolbar->addAction(m_zoomOut); m_zoomOut->setShortcut(Qt::CTRL + Qt::Key_Minus); m_zoomSlider = new QSlider(Qt::Horizontal, this); m_zoomSlider->setMaximum(13); m_zoomSlider->setPageStep(1); m_zoomSlider->setInvertedAppearance(true); m_zoomSlider->setInvertedControls(true); m_zoomSlider->setMaximumWidth(150); m_zoomSlider->setMinimumWidth(100); //toolbar->addWidget(m_zoomSlider); m_zoomIn = new QAction(KoIconUtils::themedIcon(QStringLiteral("zoom-in")), i18n("Zoom In"), this); //toolbar->addAction(m_zoomIn); m_zoomIn->setShortcut(Qt::CTRL + Qt::Key_Plus); /*actionWidget = toolbar->widgetForAction(m_buttonFitZoom); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4); actionWidget->setStyleSheet(styleBorderless); actionWidget = toolbar->widgetForAction(m_zoomIn); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4); actionWidget->setStyleSheet(styleBorderless); actionWidget = toolbar->widgetForAction(m_zoomOut); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4); actionWidget->setStyleSheet(styleBorderless);*/ connect(m_zoomSlider, SIGNAL(valueChanged(int)), this, SLOT(slotSetZoom(int))); connect(m_zoomSlider, SIGNAL(sliderMoved(int)), this, SLOT(slotShowZoomSliderToolTip(int))); connect(m_buttonFitZoom, SIGNAL(triggered()), this, SLOT(slotFitZoom())); connect(m_zoomIn, SIGNAL(triggered(bool)), this, SLOT(slotZoomIn())); connect(m_zoomOut, SIGNAL(triggered(bool)), this, SLOT(slotZoomOut())); //toolbar->addSeparator(); //create automatic audio split button m_buttonAutomaticSplitAudio = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-split-audio")), i18n("Split audio and video automatically"), this); //toolbar->addAction(m_buttonAutomaticSplitAudio); m_buttonAutomaticSplitAudio->setCheckable(true); m_buttonAutomaticSplitAudio->setChecked(KdenliveSettings::splitaudio()); connect(m_buttonAutomaticSplitAudio, &QAction::toggled, this, &MainWindow::slotSwitchSplitAudio); m_buttonVideoThumbs = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-show-videothumb")), i18n("Show video thumbnails"), this); //toolbar->addAction(m_buttonVideoThumbs); m_buttonVideoThumbs->setCheckable(true); m_buttonVideoThumbs->setChecked(KdenliveSettings::videothumbnails()); connect(m_buttonVideoThumbs, SIGNAL(triggered()), this, SLOT(slotSwitchVideoThumbs())); m_buttonAudioThumbs = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-show-audiothumb")), i18n("Show audio thumbnails"), this); //toolbar->addAction(m_buttonAudioThumbs); m_buttonAudioThumbs->setCheckable(true); m_buttonAudioThumbs->setChecked(KdenliveSettings::audiothumbnails()); connect(m_buttonAudioThumbs, SIGNAL(triggered()), this, SLOT(slotSwitchAudioThumbs())); m_buttonShowMarkers = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-show-markers")), i18n("Show markers comments"), this); //toolbar->addAction(m_buttonShowMarkers); m_buttonShowMarkers->setCheckable(true); m_buttonShowMarkers->setChecked(KdenliveSettings::showmarkers()); connect(m_buttonShowMarkers, SIGNAL(triggered()), this, SLOT(slotSwitchMarkersComments())); m_buttonSnap = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-snap")), i18n("Snap"), this); //toolbar->addAction(m_buttonSnap); m_buttonSnap->setCheckable(true); m_buttonSnap->setChecked(KdenliveSettings::snaptopoints()); connect(m_buttonSnap, SIGNAL(triggered()), this, SLOT(slotSwitchSnap())); /*actionWidget = toolbar->widgetForAction(m_buttonAutomaticSplitAudio); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4); actionWidget = toolbar->widgetForAction(m_buttonVideoThumbs); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4); actionWidget = toolbar->widgetForAction(m_buttonAudioThumbs); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4); actionWidget = toolbar->widgetForAction(m_buttonShowMarkers); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4); actionWidget = toolbar->widgetForAction(m_buttonSnap); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4);*/ m_messageLabel = new StatusBarMessageLabel(this); m_messageLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::MinimumExpanding); statusBar()->addWidget(m_messageLabel, 10); statusBar()->addWidget(m_statusProgressBar, 0); //statusBar()->addPermanentWidget(toolbar); addAction(QStringLiteral("normal_mode"), m_normalEditTool); addAction(QStringLiteral("overwrite_mode"), m_overwriteEditTool); addAction(QStringLiteral("insert_mode"), m_insertEditTool); addAction(QStringLiteral("select_tool"), m_buttonSelectTool); addAction(QStringLiteral("razor_tool"), m_buttonRazorTool); addAction(QStringLiteral("spacer_tool"), m_buttonSpacerTool); addAction(QStringLiteral("automatic_split_audio"), m_buttonAutomaticSplitAudio); addAction(QStringLiteral("show_video_thumbs"), m_buttonVideoThumbs); addAction(QStringLiteral("show_audio_thumbs"), m_buttonAudioThumbs); addAction(QStringLiteral("show_markers"), m_buttonShowMarkers); addAction(QStringLiteral("snap"), m_buttonSnap); addAction(QStringLiteral("zoom_fit"), m_buttonFitZoom); addAction(QStringLiteral("zoom_in"), m_zoomIn); addAction(QStringLiteral("zoom_out"), m_zoomOut); KNS3::standardAction(i18n("Download New Wipes..."), this, SLOT(slotGetNewLumaStuff()), actionCollection(), "get_new_lumas"); KNS3::standardAction(i18n("Download New Render Profiles..."), this, SLOT(slotGetNewRenderStuff()), actionCollection(), "get_new_profiles"); KNS3::standardAction(i18n("Download New Project Profiles..."), this, SLOT(slotGetNewMltProfileStuff()), actionCollection(), "get_new_mlt_profiles"); KNS3::standardAction(i18n("Download New Title Templates..."), this, SLOT(slotGetNewTitleStuff()), actionCollection(), "get_new_titles"); addAction(QStringLiteral("run_wizard"), i18n("Run Config Wizard"), this, SLOT(slotRunWizard()), KoIconUtils::themedIcon(QStringLiteral("tools-wizard"))); addAction(QStringLiteral("project_settings"), i18n("Project Settings"), this, SLOT(slotEditProjectSettings()), KoIconUtils::themedIcon(QStringLiteral("configure"))); addAction(QStringLiteral("project_render"), i18n("Render"), this, SLOT(slotRenderProject()), KoIconUtils::themedIcon(QStringLiteral("media-record")), Qt::CTRL + Qt::Key_Return); addAction(QStringLiteral("project_clean"), i18n("Clean Project"), this, SLOT(slotCleanProject()), KoIconUtils::themedIcon(QStringLiteral("edit-clear"))); //TODO //addAction("project_adjust_profile", i18n("Adjust Profile to Current Clip"), pCore->bin(), SLOT(adjustProjectProfileToItem())); m_playZone = addAction(QStringLiteral("monitor_play_zone"), i18n("Play Zone"), pCore->monitorManager(), SLOT(slotPlayZone()), KoIconUtils::themedIcon(QStringLiteral("media-playback-start")), Qt::CTRL + Qt::Key_Space); m_loopZone = addAction(QStringLiteral("monitor_loop_zone"), i18n("Loop Zone"), pCore->monitorManager(), SLOT(slotLoopZone()), KoIconUtils::themedIcon(QStringLiteral("media-playback-start")), Qt::ALT + Qt::Key_Space); m_loopClip = addAction(QStringLiteral("monitor_loop_clip"), i18n("Loop selected clip"), m_projectMonitor, SLOT(slotLoopClip()), KoIconUtils::themedIcon(QStringLiteral("media-playback-start"))); m_loopClip->setEnabled(false); addAction(QStringLiteral("dvd_wizard"), i18n("DVD Wizard"), this, SLOT(slotDvdWizard()), KoIconUtils::themedIcon(QStringLiteral("media-optical"))); addAction(QStringLiteral("transcode_clip"), i18n("Transcode Clips"), this, SLOT(slotTranscodeClip()), KoIconUtils::themedIcon(QStringLiteral("edit-copy"))); addAction(QStringLiteral("archive_project"), i18n("Archive Project"), this, SLOT(slotArchiveProject()), KoIconUtils::themedIcon(QStringLiteral("document-save-all"))); addAction(QStringLiteral("switch_monitor"), i18n("Switch monitor"), this, SLOT(slotSwitchMonitors()), QIcon(), Qt::Key_T); addAction(QStringLiteral("expand_timeline_clip"), i18n("Expand Clip"), pCore->projectManager(), SLOT(slotExpandClip()), KoIconUtils::themedIcon(QStringLiteral("document-open"))); QAction *overlayInfo = new QAction(KoIconUtils::themedIcon(QStringLiteral("help-hint")), i18n("Monitor Info Overlay"), this); addAction(QStringLiteral("monitor_overlay"), overlayInfo); overlayInfo->setCheckable(true); overlayInfo->setData(0x01); QAction *overlayTCInfo = new QAction(KoIconUtils::themedIcon(QStringLiteral("help-hint")), i18n("Monitor Overlay Timecode"), this); addAction(QStringLiteral("monitor_overlay_tc"), overlayTCInfo); overlayTCInfo->setCheckable(true); overlayTCInfo->setData(0x02); QAction *overlayMarkerInfo = new QAction(KoIconUtils::themedIcon(QStringLiteral("help-hint")), i18n("Monitor Overlay Markers"), this); addAction(QStringLiteral("monitor_overlay_markers"), overlayMarkerInfo); overlayMarkerInfo->setCheckable(true); overlayMarkerInfo->setData(0x04); QAction *overlaySafeInfo = new QAction(KoIconUtils::themedIcon(QStringLiteral("help-hint")), i18n("Monitor Overlay Safe Zones"), this); addAction(QStringLiteral("monitor_overlay_safezone"), overlaySafeInfo); overlaySafeInfo->setCheckable(true); overlaySafeInfo->setData(0x08); QAction *overlayAudioInfo = new QAction(KoIconUtils::themedIcon(QStringLiteral("help-hint")), i18n("Monitor Overlay Audio Waveform"), this); addAction(QStringLiteral("monitor_overlay_audiothumb"), overlayAudioInfo); overlayAudioInfo->setCheckable(true); overlayAudioInfo->setData(0x10); QAction *dropFrames = new QAction(QIcon(), i18n("Real Time (drop frames)"), this); dropFrames->setCheckable(true); dropFrames->setChecked(KdenliveSettings::monitor_dropframes()); connect(dropFrames, SIGNAL(toggled(bool)), this, SLOT(slotSwitchDropFrames(bool))); KSelectAction *monitorGamma = new KSelectAction(i18n("Monitor Gamma"), this); monitorGamma->addAction(i18n("sRGB (computer)")); monitorGamma->addAction(i18n("Rec. 709 (TV)")); addAction(QStringLiteral("mlt_gamma"), monitorGamma); monitorGamma->setCurrentItem(KdenliveSettings::monitor_gamma()); connect(monitorGamma, SIGNAL(triggered(int)), this, SLOT(slotSetMonitorGamma(int))); addAction(QStringLiteral("insert_project_tree"), i18n("Insert Zone in Project Bin"), this, SLOT(slotInsertZoneToTree()), QIcon(), Qt::CTRL + Qt::Key_I); addAction(QStringLiteral("insert_timeline"), i18n("Insert Zone in Timeline"), this, SLOT(slotInsertZoneToTimeline()), QIcon(), Qt::SHIFT + Qt::CTRL + Qt::Key_I); QAction *resizeStart = new QAction(QIcon(), i18n("Resize Item Start"), this); addAction(QStringLiteral("resize_timeline_clip_start"), resizeStart); resizeStart->setShortcut(Qt::Key_1); connect(resizeStart, SIGNAL(triggered(bool)), this, SLOT(slotResizeItemStart())); QAction *resizeEnd = new QAction(QIcon(), i18n("Resize Item End"), this); addAction(QStringLiteral("resize_timeline_clip_end"), resizeEnd); resizeEnd->setShortcut(Qt::Key_2); connect(resizeEnd, SIGNAL(triggered(bool)), this, SLOT(slotResizeItemEnd())); addAction(QStringLiteral("monitor_seek_snap_backward"), i18n("Go to Previous Snap Point"), this, SLOT(slotSnapRewind()), KoIconUtils::themedIcon(QStringLiteral("media-seek-backward")), Qt::ALT + Qt::Key_Left); addAction(QStringLiteral("seek_clip_start"), i18n("Go to Clip Start"), this, SLOT(slotClipStart()), KoIconUtils::themedIcon(QStringLiteral("media-seek-backward")), Qt::Key_Home); addAction(QStringLiteral("seek_clip_end"), i18n("Go to Clip End"), this, SLOT(slotClipEnd()), KoIconUtils::themedIcon(QStringLiteral("media-seek-forward")), Qt::Key_End); addAction(QStringLiteral("monitor_seek_snap_forward"), i18n("Go to Next Snap Point"), this, SLOT(slotSnapForward()), KoIconUtils::themedIcon(QStringLiteral("media-seek-forward")), Qt::ALT + Qt::Key_Right); addAction(QStringLiteral("delete_timeline_clip"), i18n("Delete Selected Item"), this, SLOT(slotDeleteItem()), KoIconUtils::themedIcon(QStringLiteral("edit-delete")), Qt::Key_Delete); addAction(QStringLiteral("align_playhead"), i18n("Align Playhead to Mouse Position"), this, SLOT(slotAlignPlayheadToMousePos()), QIcon(), Qt::Key_P); QAction *stickTransition = new QAction(i18n("Automatic Transition"), this); stickTransition->setData(QStringLiteral("auto")); stickTransition->setCheckable(true); stickTransition->setEnabled(false); addAction(QStringLiteral("auto_transition"), stickTransition); connect(stickTransition, SIGNAL(triggered(bool)), this, SLOT(slotAutoTransition())); addAction(QStringLiteral("group_clip"), i18n("Group Clips"), this, SLOT(slotGroupClips()), KoIconUtils::themedIcon(QStringLiteral("object-group")), Qt::CTRL + Qt::Key_G); QAction * ungroupClip = addAction(QStringLiteral("ungroup_clip"), i18n("Ungroup Clips"), this, SLOT(slotUnGroupClips()), KoIconUtils::themedIcon(QStringLiteral("object-ungroup")), Qt::CTRL + Qt::SHIFT + Qt::Key_G); ungroupClip->setData("ungroup_clip"); addAction(QStringLiteral("edit_item_duration"), i18n("Edit Duration"), this, SLOT(slotEditItemDuration()), KoIconUtils::themedIcon(QStringLiteral("measure"))); addAction(QStringLiteral("clip_in_project_tree"), i18n("Clip in Project Bin"), this, SLOT(slotClipInProjectTree()), KoIconUtils::themedIcon(QStringLiteral("go-jump-definition"))); addAction(QStringLiteral("overwrite_to_in_point"), i18n("Overwrite Clip Zone in Timeline"), this, SLOT(slotInsertClipOverwrite()), KoIconUtils::themedIcon(QStringLiteral("timeline-overwrite")), Qt::Key_B); addAction(QStringLiteral("insert_to_in_point"), i18n("Insert Clip Zone in Timeline"), this, SLOT(slotInsertClipInsert()), KoIconUtils::themedIcon(QStringLiteral("timeline-insert")), Qt::Key_V); addAction(QStringLiteral("remove_extract"), i18n("Extract Timeline Zone"), this, SLOT(slotExtractZone()), KoIconUtils::themedIcon(QStringLiteral("timeline-extract")), Qt::SHIFT + Qt::Key_X); addAction(QStringLiteral("remove_lift"), i18n("Lift Timeline Zone"), this, SLOT(slotLiftZone()), KoIconUtils::themedIcon(QStringLiteral("timeline-lift")), Qt::Key_Z); addAction(QStringLiteral("set_render_timeline_zone"), i18n("Add Preview Zone"), this, SLOT(slotDefinePreviewRender()), KoIconUtils::themedIcon(QStringLiteral("insert-horizontal-rule"))); addAction(QStringLiteral("unset_render_timeline_zone"), i18n("Unset Preview Zone"), this, SLOT(slotRemovePreviewRender()), KoIconUtils::themedIcon(QStringLiteral("insert-horizontal-rule"))); addAction(QStringLiteral("prerender_timeline_zone"), i18n("Start Preview Render"), this, SLOT(slotPreviewRender()), KoIconUtils::themedIcon(QStringLiteral("player-time")), QKeySequence(Qt::SHIFT + Qt::Key_Return)); addAction(QStringLiteral("stop_prerender_timeline"), i18n("Stop Preview Render"), this, SLOT(slotStopPreviewRender()), KoIconUtils::themedIcon(QStringLiteral("process-stop"))); addAction(QStringLiteral("select_timeline_clip"), i18n("Select Clip"), this, SLOT(slotSelectTimelineClip()), KoIconUtils::themedIcon(QStringLiteral("edit-select")), Qt::Key_Plus); addAction(QStringLiteral("deselect_timeline_clip"), i18n("Deselect Clip"), this, SLOT(slotDeselectTimelineClip()), KoIconUtils::themedIcon(QStringLiteral("edit-select")), Qt::Key_Minus); addAction(QStringLiteral("select_add_timeline_clip"), i18n("Add Clip To Selection"), this, SLOT(slotSelectAddTimelineClip()), KoIconUtils::themedIcon(QStringLiteral("edit-select")), Qt::ALT + Qt::Key_Plus); addAction(QStringLiteral("select_timeline_transition"), i18n("Select Transition"), this, SLOT(slotSelectTimelineTransition()), KoIconUtils::themedIcon(QStringLiteral("edit-select")), Qt::SHIFT + Qt::Key_Plus); addAction(QStringLiteral("deselect_timeline_transition"), i18n("Deselect Transition"), this, SLOT(slotDeselectTimelineTransition()), KoIconUtils::themedIcon(QStringLiteral("edit-select")), Qt::SHIFT + Qt::Key_Minus); addAction(QStringLiteral("select_add_timeline_transition"), i18n("Add Transition To Selection"), this, SLOT(slotSelectAddTimelineTransition()), KoIconUtils::themedIcon(QStringLiteral("edit-select")), Qt::ALT + Qt::SHIFT + Qt::Key_Plus); addAction(QStringLiteral("cut_timeline_clip"), i18n("Cut Clip"), this, SLOT(slotCutTimelineClip()), KoIconUtils::themedIcon(QStringLiteral("edit-cut")), Qt::SHIFT + Qt::Key_R); addAction(QStringLiteral("add_clip_marker"), i18n("Add Marker"), this, SLOT(slotAddClipMarker()), KoIconUtils::themedIcon(QStringLiteral("bookmark-new"))); addAction(QStringLiteral("delete_clip_marker"), i18n("Delete Marker"), this, SLOT(slotDeleteClipMarker()), KoIconUtils::themedIcon(QStringLiteral("edit-delete"))); addAction(QStringLiteral("delete_all_clip_markers"), i18n("Delete All Markers"), this, SLOT(slotDeleteAllClipMarkers()), KoIconUtils::themedIcon(QStringLiteral("edit-delete"))); QAction * editClipMarker = addAction(QStringLiteral("edit_clip_marker"), i18n("Edit Marker"), this, SLOT(slotEditClipMarker()), KoIconUtils::themedIcon(QStringLiteral("document-properties"))); editClipMarker->setData(QStringLiteral("edit_marker")); addAction(QStringLiteral("add_marker_guide_quickly"), i18n("Add Marker/Guide quickly"), this, SLOT(slotAddMarkerGuideQuickly()), KoIconUtils::themedIcon(QStringLiteral("bookmark-new")), Qt::Key_Asterisk); QAction * splitAudio = addAction(QStringLiteral("split_audio"), i18n("Split Audio"), this, SLOT(slotSplitAudio()), KoIconUtils::themedIcon(QStringLiteral("document-new"))); // "A+V" as data means this action should only be available for clips with audio AND video splitAudio->setData("A+V"); QAction * setAudioAlignReference = addAction(QStringLiteral("set_audio_align_ref"), i18n("Set Audio Reference"), this, SLOT(slotSetAudioAlignReference())); // "A" as data means this action should only be available for clips with audio setAudioAlignReference->setData("A"); QAction * alignAudio = addAction(QStringLiteral("align_audio"), i18n("Align Audio to Reference"), this, SLOT(slotAlignAudio()), QIcon()); // "A" as data means this action should only be available for clips with audio alignAudio->setData("A"); QAction * audioOnly = new QAction(KoIconUtils::themedIcon(QStringLiteral("document-new")), i18n("Audio Only"), this); addAction(QStringLiteral("clip_audio_only"), audioOnly); audioOnly->setData(PlaylistState::AudioOnly); audioOnly->setCheckable(true); QAction * videoOnly = new QAction(KoIconUtils::themedIcon(QStringLiteral("document-new")), i18n("Video Only"), this); addAction(QStringLiteral("clip_video_only"), videoOnly); videoOnly->setData(PlaylistState::VideoOnly); videoOnly->setCheckable(true); QAction * audioAndVideo = new QAction(KoIconUtils::themedIcon(QStringLiteral("document-new")), i18n("Audio and Video"), this); addAction(QStringLiteral("clip_audio_and_video"), audioAndVideo); audioAndVideo->setData(PlaylistState::Original); audioAndVideo->setCheckable(true); m_clipTypeGroup = new QActionGroup(this); m_clipTypeGroup->addAction(audioOnly); m_clipTypeGroup->addAction(videoOnly); m_clipTypeGroup->addAction(audioAndVideo); connect(m_clipTypeGroup, SIGNAL(triggered(QAction*)), this, SLOT(slotUpdateClipType(QAction*))); m_clipTypeGroup->setEnabled(false); addAction(QStringLiteral("insert_space"), i18n("Insert Space"), this, SLOT(slotInsertSpace())); addAction(QStringLiteral("delete_space"), i18n("Remove Space"), this, SLOT(slotRemoveSpace())); KActionCategory *timelineActions = new KActionCategory(i18n("Tracks"), actionCollection()); QAction *insertTrack = new QAction(QIcon(), i18n("Insert Track"), this); connect(insertTrack, &QAction::triggered, this, &MainWindow::slotInsertTrack); timelineActions->addAction(QStringLiteral("insert_track"), insertTrack); QAction *deleteTrack = new QAction(QIcon(), i18n("Delete Track"), this); connect(deleteTrack, &QAction::triggered, this, &MainWindow::slotDeleteTrack); timelineActions->addAction(QStringLiteral("delete_track"), deleteTrack); deleteTrack->setData("delete_track"); QAction *configTracks = new QAction(KoIconUtils::themedIcon(QStringLiteral("configure")), i18n("Configure Tracks"), this); connect(configTracks, &QAction::triggered, this, &MainWindow::slotConfigTrack); timelineActions->addAction(QStringLiteral("config_tracks"), configTracks); QAction *selectTrack = new QAction(QIcon(), i18n("Select All in Current Track"), this); connect(selectTrack, &QAction::triggered, this, &MainWindow::slotSelectTrack); timelineActions->addAction(QStringLiteral("select_track"), selectTrack); QAction *selectAll = KStandardAction::selectAll(this, SLOT(slotSelectAllTracks()), this); selectAll->setIcon(KoIconUtils::themedIcon(QStringLiteral("kdenlive-select-all"))); selectAll->setShortcutContext(Qt::WidgetWithChildrenShortcut); timelineActions->addAction(QStringLiteral("select_all_tracks"), selectAll); kdenliveCategoryMap.insert(QStringLiteral("timeline"), timelineActions); addAction(QStringLiteral("add_guide"), i18n("Add Guide"), this, SLOT(slotAddGuide()), KoIconUtils::themedIcon(QStringLiteral("document-new"))); addAction(QStringLiteral("delete_guide"), i18n("Delete Guide"), this, SLOT(slotDeleteGuide()), KoIconUtils::themedIcon(QStringLiteral("edit-delete"))); addAction(QStringLiteral("edit_guide"), i18n("Edit Guide"), this, SLOT(slotEditGuide()), KoIconUtils::themedIcon(QStringLiteral("document-properties"))); addAction(QStringLiteral("delete_all_guides"), i18n("Delete All Guides"), this, SLOT(slotDeleteAllGuides()), KoIconUtils::themedIcon(QStringLiteral("edit-delete"))); QAction *pasteEffects = addAction(QStringLiteral("paste_effects"), i18n("Paste Effects"), this, SLOT(slotPasteEffects()), KoIconUtils::themedIcon(QStringLiteral("edit-paste"))); pasteEffects->setData("paste_effects"); m_saveAction = KStandardAction::save(pCore->projectManager(), SLOT(saveFile()), actionCollection()); m_saveAction->setIcon(KoIconUtils::themedIcon(QStringLiteral("document-save"))); addAction(QStringLiteral("save_selection"), i18n("Save Selection"), pCore->projectManager(), SLOT(slotSaveSelection()), KoIconUtils::themedIcon(QStringLiteral("document-save"))); QAction *sentToLibrary = addAction(QStringLiteral("send_library"), i18n("Add Selection to Library"), pCore->library(), SLOT(slotAddToLibrary()), KoIconUtils::themedIcon(QStringLiteral("bookmark-new"))); pCore->library()->setupActions(QList () << sentToLibrary); QAction *a = KStandardAction::quit(this, SLOT(close()), actionCollection()); a->setIcon(KoIconUtils::themedIcon(QStringLiteral("application-exit"))); // TODO: make the following connection to slotEditKeys work //KStandardAction::keyBindings(this, SLOT(slotEditKeys()), actionCollection()); a = KStandardAction::preferences(this, SLOT(slotPreferences()), actionCollection()); a->setIcon(KoIconUtils::themedIcon(QStringLiteral("configure"))); a = KStandardAction::configureNotifications(this, SLOT(configureNotifications()), actionCollection()); a->setIcon(KoIconUtils::themedIcon(QStringLiteral("configure"))); a = KStandardAction::copy(this, SLOT(slotCopy()), actionCollection()); a->setIcon(KoIconUtils::themedIcon(QStringLiteral("edit-copy"))); a = KStandardAction::paste(this, SLOT(slotPaste()), actionCollection()); a->setIcon(KoIconUtils::themedIcon(QStringLiteral("edit-paste"))); a = KStandardAction::fullScreen(this, SLOT(slotFullScreen()), this, actionCollection()); a->setIcon(KoIconUtils::themedIcon(QStringLiteral("view-fullscreen"))); QAction *undo = KStandardAction::undo(m_commandStack, SLOT(undo()), actionCollection()); undo->setIcon(KoIconUtils::themedIcon(QStringLiteral("edit-undo"))); undo->setEnabled(false); connect(m_commandStack, SIGNAL(canUndoChanged(bool)), undo, SLOT(setEnabled(bool))); QAction *redo = KStandardAction::redo(m_commandStack, SLOT(redo()), actionCollection()); redo->setIcon(KoIconUtils::themedIcon(QStringLiteral("edit-redo"))); redo->setEnabled(false); connect(m_commandStack, SIGNAL(canRedoChanged(bool)), redo, SLOT(setEnabled(bool))); QMenu *addClips = new QMenu(); QAction *addClip = addAction(QStringLiteral("add_clip"), i18n("Add Clip"), pCore->bin(), SLOT(slotAddClip()), KoIconUtils::themedIcon(QStringLiteral("kdenlive-add-clip"))); addClips->addAction(addClip); QAction *action = addAction(QStringLiteral("add_color_clip"), i18n("Add Color Clip"), pCore->bin(), SLOT(slotCreateProjectClip()), KoIconUtils::themedIcon(QStringLiteral("kdenlive-add-color-clip"))); action->setData((int) Color); addClips->addAction(action); action = addAction(QStringLiteral("add_slide_clip"), i18n("Add Slideshow Clip"), pCore->bin(), SLOT(slotCreateProjectClip()), KoIconUtils::themedIcon(QStringLiteral("kdenlive-add-slide-clip"))); action->setData((int) SlideShow); addClips->addAction(action); action = addAction(QStringLiteral("add_text_clip"), i18n("Add Title Clip"), pCore->bin(), SLOT(slotCreateProjectClip()), KoIconUtils::themedIcon(QStringLiteral("kdenlive-add-text-clip"))); action->setData((int) Text); addClips->addAction(action); action = addAction(QStringLiteral("add_text_template_clip"), i18n("Add Template Title"), pCore->bin(), SLOT(slotCreateProjectClip()), KoIconUtils::themedIcon(QStringLiteral("kdenlive-add-text-clip"))); action->setData((int) TextTemplate); addClips->addAction(action); action = addAction(QStringLiteral("add_qtext_clip"), i18n("Add Simple Text Clip"), pCore->bin(), SLOT(slotCreateProjectClip()), KoIconUtils::themedIcon(QStringLiteral("kdenlive-add-text-clip"))); action->setData((int) QText); addClips->addAction(action); QAction *addFolder = addAction(QStringLiteral("add_folder"), i18n("Create Folder"), pCore->bin(), SLOT(slotAddFolder()), KoIconUtils::themedIcon(QStringLiteral("folder-new"))); addClips->addAction(addAction(QStringLiteral("download_resource"), i18n("Online Resources"), this, SLOT(slotDownloadResources()), KoIconUtils::themedIcon(QStringLiteral("edit-download")))); QAction *clipProperties = addAction(QStringLiteral("clip_properties"), i18n("Clip Properties"), pCore->bin(), SLOT(slotSwitchClipProperties(bool)), KoIconUtils::themedIcon(QStringLiteral("document-edit"))); clipProperties->setCheckable(true); clipProperties->setData("clip_properties"); QAction *openClip = addAction(QStringLiteral("edit_clip"), i18n("Edit Clip"), pCore->bin(), SLOT(slotOpenClip()), KoIconUtils::themedIcon(QStringLiteral("document-open"))); openClip->setData("edit_clip"); openClip->setEnabled(false); QAction *deleteClip = addAction(QStringLiteral("delete_clip"), i18n("Delete Clip"), pCore->bin(), SLOT(slotDeleteClip()), KoIconUtils::themedIcon(QStringLiteral("edit-delete"))); deleteClip->setData("delete_clip"); deleteClip->setEnabled(false); QAction *reloadClip = addAction(QStringLiteral("reload_clip"), i18n("Reload Clip"), pCore->bin(), SLOT(slotReloadClip()), KoIconUtils::themedIcon(QStringLiteral("view-refresh"))); reloadClip->setData("reload_clip"); reloadClip->setEnabled(false); QAction *disableEffects = addAction(QStringLiteral("disable_timeline_effects"), i18n("Disable Timeline Effects"), pCore->projectManager(), SLOT(slotDisableTimelineEffects(bool)), KoIconUtils::themedIcon(QStringLiteral("favorite"))); disableEffects->setData("disable_timeline_effects"); disableEffects->setCheckable(true); disableEffects->setChecked(false); QAction *locateClip = addAction(QStringLiteral("locate_clip"), i18n("Locate Clip..."), pCore->bin(), SLOT(slotLocateClip()), KoIconUtils::themedIcon(QStringLiteral("edit-file"))); locateClip->setData("locate_clip"); locateClip->setEnabled(false); QAction *duplicateClip = addAction(QStringLiteral("duplicate_clip"), i18n("Duplicate Clip"), pCore->bin(), SLOT(slotDuplicateClip()), KoIconUtils::themedIcon(QStringLiteral("edit-copy"))); duplicateClip->setData("duplicate_clip"); duplicateClip->setEnabled(false); QAction *proxyClip = new QAction(i18n("Proxy Clip"), this); addAction(QStringLiteral("proxy_clip"), proxyClip); proxyClip->setData(QStringList() << QString::number((int) AbstractClipJob::PROXYJOB)); proxyClip->setCheckable(true); proxyClip->setChecked(false); //TODO: port stopmotion to new Monitor code //addAction("stopmotion", i18n("Stop Motion Capture"), this, SLOT(slotOpenStopmotion()), KoIconUtils::themedIcon("image-x-generic")); addAction(QStringLiteral("switch_track_lock"), i18n("Toggle Track Lock"), pCore->projectManager(), SLOT(slotSwitchTrackLock()), QIcon(), Qt::SHIFT + Qt::Key_L); addAction(QStringLiteral("switch_all_track_lock"), i18n("Toggle All Track Lock"), pCore->projectManager(), SLOT(slotSwitchAllTrackLock()), QIcon(), Qt::CTRL + Qt::SHIFT + Qt::Key_L); addAction(QStringLiteral("switch_track_target"), i18n("Toggle Track Target"), pCore->projectManager(), SLOT(slotSwitchTrackTarget()), QIcon(), Qt::SHIFT + Qt::Key_T); QHash actions; actions.insert(QStringLiteral("locate"), locateClip); actions.insert(QStringLiteral("reload"), reloadClip); actions.insert(QStringLiteral("duplicate"), duplicateClip); actions.insert(QStringLiteral("proxy"), proxyClip); actions.insert(QStringLiteral("properties"), clipProperties); actions.insert(QStringLiteral("open"), openClip); actions.insert(QStringLiteral("delete"), deleteClip); actions.insert(QStringLiteral("folder"), addFolder); pCore->bin()->setupMenu(addClips, addClip, actions); // Setup effects and transitions actions. KActionCategory *transitionActions = new KActionCategory(i18n("Transitions"), actionCollection()); //m_transitions = new QAction*[transitions.count()]; for (int i = 0; i < transitions.count(); ++i) { QStringList effectInfo = transitions.effectIdInfo(i); if (effectInfo.isEmpty()) continue; QAction *a = new QAction(effectInfo.at(0), this); a->setData(effectInfo); a->setIconVisibleInMenu(false); m_transitions << a; QString id = effectInfo.at(2); if (id.isEmpty()) id = effectInfo.at(1); transitionActions->addAction("transition_" + id, a); } } void MainWindow::setStatusBarStyleSheet(const QPalette &p) { return; KColorScheme scheme(p.currentColorGroup(), KColorScheme::Window, KSharedConfig::openConfig(KdenliveSettings::colortheme())); QColor buttonBg = scheme.background(KColorScheme::LinkBackground).color(); QColor buttonBord = scheme.foreground(KColorScheme::LinkText).color(); QColor buttonBord2 = scheme.shade(KColorScheme::LightShade); statusBar()->setStyleSheet(QStringLiteral("QStatusBar QLabel {font-size:%1pt;} QStatusBar::item { border: 0px; font-size:%1pt;padding:0px; }").arg(statusBar()->font().pointSize())); QString style1 = QStringLiteral("QToolBar { border: 0px } QToolButton { border-style: inset; border:1px solid transparent;border-radius: 3px;margin: 0px 3px;padding: 0px;} QToolButton#timecode {padding-right:10px;} QToolButton:hover { background: %3;border-style: inset; border:1px solid %3;border-radius: 3px;} QToolButton:checked { background-color: %1; border-style: inset; border:1px solid %2;border-radius: 3px;}").arg(buttonBg.name(), buttonBord.name(), buttonBord2.name()); statusBar()->setStyleSheet(style1); } void MainWindow::saveOptions() { KdenliveSettings::self()->save(); } void MainWindow::readOptions() { KSharedConfigPtr config = KSharedConfig::openConfig(); pCore->projectManager()->recentFilesAction()->loadEntries(KConfigGroup(config, "Recent Files")); if (KdenliveSettings::defaultprojectfolder().isEmpty()) { QDir dir(QDir::homePath()); if (!dir.mkdir(QStringLiteral("kdenlive"))) { qDebug() << "/// ERROR CREATING PROJECT FOLDER: "; } else { dir.cd("kdenlive"); KdenliveSettings::setDefaultprojectfolder(dir.absolutePath()); } } if (KdenliveSettings::trackheight() == 0) { QFontMetrics metrics(font()); int trackHeight = 2 * metrics.height(); QStyle *style = qApp->style(); trackHeight += style->pixelMetric(QStyle::PM_ToolBarIconSize) + 2 * style->pixelMetric(QStyle::PM_ToolBarItemMargin) + style->pixelMetric(QStyle::PM_ToolBarItemSpacing) + 2; KdenliveSettings::setTrackheight(trackHeight); } if (KdenliveSettings::trackheight() == 0) { KdenliveSettings::setTrackheight(50); } KConfigGroup initialGroup(config, "version"); if (!initialGroup.exists() || KdenliveSettings::ffmpegpath().isEmpty() || KdenliveSettings::ffplaypath().isEmpty()) { // this is our first run, show Wizard QPointer w = new Wizard(false, this); if (w->exec() == QDialog::Accepted && w->isOk()) { w->adjustSettings(); delete w; } else { delete w; ::exit(1); } } initialGroup.writeEntry("version", version); } void MainWindow::slotRunWizard() { QPointer w = new Wizard(false, this); if (w->exec() == QDialog::Accepted && w->isOk()) { w->adjustSettings(); } delete w; } void MainWindow::slotRefreshProfiles() { KdenliveSettingsDialog* d = static_cast (KConfigDialog::exists("settings")); if (d) { d->checkProfile(); } } void MainWindow::slotEditProjectSettings() { KdenliveDoc *project = pCore->projectManager()->current(); QPoint p = pCore->projectManager()->currentTimeline()->getTracksCount(); QPointer w = new ProjectSettings(project, project->metadata(), pCore->projectManager()->currentTimeline()->projectView()->extractTransitionsLumas(), p.x(), p.y(), project->projectFolder().path(), true, !project->isModified(), this); connect(w, SIGNAL(disableProxies()), this, SLOT(slotDisableProxies())); connect(w, SIGNAL(refreshProfiles()), this, SLOT(slotRefreshProfiles())); if (w->exec() == QDialog::Accepted) { QString profile = w->selectedProfile(); project->setProjectFolder(w->selectedFolder()); pCore->projectManager()->currentTimeline()->updatePreviewSettings(w->selectedPreview()); bool modified = false; if (m_recMonitor) { m_recMonitor->slotUpdateCaptureFolder(project->projectFolder().path() + QDir::separator()); } if (m_renderWidget) { m_renderWidget->setDocumentPath(project->projectFolder().path() + QDir::separator()); } if (KdenliveSettings::videothumbnails() != w->enableVideoThumbs()) { slotSwitchVideoThumbs(); } if (KdenliveSettings::audiothumbnails() != w->enableAudioThumbs()) { slotSwitchAudioThumbs(); } if (project->profilePath() != profile || project->profileChanged(profile)) { KdenliveSettings::setCurrent_profile(profile); pCore->projectManager()->slotResetProfiles(); slotUpdateDocumentState(true); } if (project->getDocumentProperty(QStringLiteral("proxyparams")) != w->proxyParams()) { modified = true; project->setDocumentProperty(QStringLiteral("proxyparams"), w->proxyParams()); if (pCore->binController()->clipCount() > 0 && KMessageBox::questionYesNo(this, i18n("You have changed the proxy parameters. Do you want to recreate all proxy clips for this project?")) == KMessageBox::Yes) { //TODO: rebuild all proxies //m_projectList->rebuildProxies(); } } if (project->getDocumentProperty(QStringLiteral("proxyextension")) != w->proxyExtension()) { modified = true; project->setDocumentProperty(QStringLiteral("proxyextension"), w->proxyExtension()); } if (project->getDocumentProperty(QStringLiteral("generateproxy")) != QString::number((int) w->generateProxy())) { modified = true; project->setDocumentProperty(QStringLiteral("generateproxy"), QString::number((int) w->generateProxy())); } if (project->getDocumentProperty(QStringLiteral("proxyminsize")) != QString::number(w->proxyMinSize())) { modified = true; project->setDocumentProperty(QStringLiteral("proxyminsize"), QString::number(w->proxyMinSize())); } if (project->getDocumentProperty(QStringLiteral("generateimageproxy")) != QString::number((int) w->generateImageProxy())) { modified = true; project->setDocumentProperty(QStringLiteral("generateimageproxy"), QString::number((int) w->generateImageProxy())); } if (project->getDocumentProperty(QStringLiteral("proxyimageminsize")) != QString::number(w->proxyImageMinSize())) { modified = true; project->setDocumentProperty(QStringLiteral("proxyimageminsize"), QString::number(w->proxyImageMinSize())); } if (QString::number((int) w->useProxy()) != project->getDocumentProperty(QStringLiteral("enableproxy"))) { project->setDocumentProperty(QStringLiteral("enableproxy"), QString::number((int) w->useProxy())); modified = true; slotUpdateProxySettings(); } if (w->metadata() != project->metadata()) { project->setMetadata(w->metadata()); } if (modified) project->setModified(); } delete w; } void MainWindow::slotDisableProxies() { pCore->projectManager()->current()->setDocumentProperty(QStringLiteral("enableproxy"), QString::number((int) false)); pCore->projectManager()->current()->setModified(); slotUpdateProxySettings(); } void MainWindow::slotRenderProject() { KdenliveDoc *project = pCore->projectManager()->current(); if (!m_renderWidget) { QString projectfolder = project ? project->projectFolder().path() + QDir::separator() : KdenliveSettings::defaultprojectfolder(); MltVideoProfile profile; if (project) { profile = project->mltProfile(); m_renderWidget = new RenderWidget(projectfolder, project->useProxy(), profile, this); connect(m_renderWidget, SIGNAL(shutdown()), this, SLOT(slotShutdown())); connect(m_renderWidget, SIGNAL(selectedRenderProfile(QMap)), this, SLOT(slotSetDocumentRenderProfile(QMap))); connect(m_renderWidget, SIGNAL(prepareRenderingData(bool,bool,QString)), this, SLOT(slotPrepareRendering(bool,bool,QString))); connect(m_renderWidget, SIGNAL(abortProcess(QString)), this, SIGNAL(abortRenderJob(QString))); connect(m_renderWidget, SIGNAL(openDvdWizard(QString)), this, SLOT(slotDvdWizard(QString))); m_renderWidget->setProfile(project->mltProfile()); m_renderWidget->setGuides(pCore->projectManager()->currentTimeline()->projectView()->guidesData(), project->projectDuration()); m_renderWidget->setDocumentPath(project->projectFolder().path() + QDir::separator()); m_renderWidget->setRenderProfile(project->getRenderProperties()); } } slotCheckRenderStatus(); m_renderWidget->show(); //m_renderWidget->showNormal(); // What are the following lines supposed to do? //pCore->projectManager()->currentTimeline()->tracksNumber(); //m_renderWidget->enableAudio(false); //m_renderWidget->export_audio; } void MainWindow::slotCheckRenderStatus() { // Make sure there are no missing clips //TODO /*if (m_renderWidget) m_renderWidget->missingClips(pCore->bin()->hasMissingClips());*/ } void MainWindow::setRenderingProgress(const QString &url, int progress) { if (m_renderWidget) m_renderWidget->setRenderJob(url, progress); } void MainWindow::setRenderingFinished(const QString &url, int status, const QString &error) { if (m_renderWidget) m_renderWidget->setRenderStatus(url, status, error); } void MainWindow::slotCleanProject() { if (KMessageBox::warningContinueCancel(this, i18n("This will remove all unused clips from your project."), i18n("Clean up project")) == KMessageBox::Cancel) return; pCore->bin()->cleanup(); } void MainWindow::slotUpdateMousePosition(int pos) { if (pCore->projectManager()->current()) { switch (m_timeFormatButton->currentItem()) { case 0: m_timeFormatButton->setText(pCore->projectManager()->current()->timecode().getTimecodeFromFrames(pos) + " / " + pCore->projectManager()->current()->timecode().getTimecodeFromFrames(pCore->projectManager()->currentTimeline()->duration())); break; default: m_timeFormatButton->setText(QString::number(pos) + " / " + QString::number(pCore->projectManager()->currentTimeline()->duration())); } } } void MainWindow::slotUpdateProjectDuration(int pos) { if (pCore->projectManager()->current()) { pCore->projectManager()->currentTimeline()->setDuration(pos); slotUpdateMousePosition(pCore->projectManager()->currentTimeline()->projectView()->getMousePos()); } } void MainWindow::slotUpdateDocumentState(bool modified) { setWindowTitle(pCore->projectManager()->current()->description()); setWindowModified(modified); m_saveAction->setEnabled(modified); } void MainWindow::connectDocument() { KdenliveDoc *project = pCore->projectManager()->current(); Timeline *trackView = pCore->projectManager()->currentTimeline(); connect(project, SIGNAL(startAutoSave()), pCore->projectManager(), SLOT(slotStartAutoSave())); connect(project, SIGNAL(reloadEffects()), this, SLOT(slotReloadEffects())); KdenliveSettings::setProject_fps(project->fps()); m_clipMonitorDock->raise(); m_effectStack->transitionConfig()->updateProjectFormat(); connect(trackView, SIGNAL(configTrack()), this, SLOT(slotConfigTrack())); connect(trackView, SIGNAL(updateTracksInfo()), this, SLOT(slotUpdateTrackInfo())); connect(trackView, SIGNAL(mousePosition(int)), this, SLOT(slotUpdateMousePosition(int))); connect(pCore->producerQueue(), SIGNAL(infoProcessingFinished()), trackView->projectView(), SLOT(slotInfoProcessingFinished()), Qt::DirectConnection); connect(trackView->projectView(), SIGNAL(importKeyframes(GraphicsRectItem,QString,QString)), this, SLOT(slotProcessImportKeyframes(GraphicsRectItem,QString,QString))); connect(m_projectMonitor, &Monitor::multitrackView, trackView, &Timeline::slotMultitrackView); connect(m_projectMonitor, SIGNAL(renderPosition(int)), trackView, SLOT(moveCursorPos(int))); connect(m_projectMonitor, SIGNAL(zoneUpdated(QPoint)), trackView, SLOT(slotSetZone(QPoint))); connect(m_projectMonitor, SIGNAL(zoneUpdated(QPoint)), project, SLOT(setModified())); connect(m_clipMonitor, SIGNAL(zoneUpdated(QPoint)), project, SLOT(setModified())); connect(project, SIGNAL(docModified(bool)), this, SLOT(slotUpdateDocumentState(bool))); connect(trackView->projectView(), SIGNAL(guidesUpdated()), this, SLOT(slotGuidesUpdated())); connect(project, SIGNAL(saveTimelinePreview(QString)), trackView, SLOT(slotSaveTimelinePreview(QString))); connect(trackView, SIGNAL(showTrackEffects(int,TrackInfo)), this, SLOT(slotTrackSelected(int,TrackInfo))); connect(trackView->projectView(), SIGNAL(clipItemSelected(ClipItem*,bool,bool)), this, SLOT(slotTimelineClipSelected(ClipItem*,bool,bool)), Qt::DirectConnection); connect(trackView->projectView(), &CustomTrackView::setActiveKeyframe, m_effectStack, &EffectStackView2::setActiveKeyframe); connect(trackView->projectView(), SIGNAL(transitionItemSelected(Transition*,int,QPoint,bool)), m_effectStack, SLOT(slotTransitionItemSelected(Transition*,int,QPoint,bool)), Qt::DirectConnection); connect(trackView->projectView(), SIGNAL(transitionItemSelected(Transition*,int,QPoint,bool)), this, SLOT(slotActivateTransitionView(Transition*))); connect(trackView->projectView(), SIGNAL(zoomIn()), this, SLOT(slotZoomIn())); connect(trackView->projectView(), SIGNAL(zoomOut()), this, SLOT(slotZoomOut())); connect(trackView, SIGNAL(setZoom(int)), this, SLOT(slotSetZoom(int))); connect(trackView->projectView(), SIGNAL(displayMessage(QString,MessageType)), m_messageLabel, SLOT(setMessage(QString,MessageType))); connect(pCore->bin(), SIGNAL(clipNameChanged(QString)), trackView->projectView(), SLOT(clipNameChanged(QString))); connect(pCore->bin(), SIGNAL(displayMessage(QString,MessageType)), m_messageLabel, SLOT(setMessage(QString,MessageType))); connect(trackView->projectView(), SIGNAL(showClipFrame(const QString&,int)), pCore->bin(), SLOT(selectClipById(const QString&,int))); connect(trackView->projectView(), SIGNAL(playMonitor()), m_projectMonitor, SLOT(slotPlay())); connect(m_projectMonitor, &Monitor::addEffect, trackView->projectView(), &CustomTrackView::slotAddEffectToCurrentItem); connect(trackView->projectView(), SIGNAL(transitionItemSelected(Transition*,int,QPoint,bool)), m_projectMonitor, SLOT(slotSetSelectedClip(Transition*))); connect(pCore->bin(), SIGNAL(gotFilterJobResults(QString,int,int,stringMap,stringMap)), trackView->projectView(), SLOT(slotGotFilterJobResults(QString,int,int,stringMap,stringMap))); //TODO //connect(m_projectList, SIGNAL(addMarkers(QString,QList)), trackView->projectView(), SLOT(slotAddClipMarker(QString,QList))); // Effect stack signals connect(m_effectStack, SIGNAL(updateEffect(ClipItem*,int,QDomElement,QDomElement,int,bool)), trackView->projectView(), SLOT(slotUpdateClipEffect(ClipItem*,int,QDomElement,QDomElement,int,bool))); connect(m_effectStack, SIGNAL(updateClipRegion(ClipItem*,int,QString)), trackView->projectView(), SLOT(slotUpdateClipRegion(ClipItem*,int,QString))); connect(m_effectStack, SIGNAL(removeEffect(ClipItem*,int,QDomElement)), trackView->projectView(), SLOT(slotDeleteEffect(ClipItem*,int,QDomElement))); connect(m_effectStack, SIGNAL(removeEffectGroup(ClipItem*,int,QDomDocument)), trackView->projectView(), SLOT(slotDeleteEffectGroup(ClipItem*,int,QDomDocument))); connect(m_effectStack, SIGNAL(addEffect(ClipItem*,QDomElement,int)), trackView->projectView(), SLOT(slotAddEffect(ClipItem*,QDomElement,int))); connect(m_effectStack, SIGNAL(changeEffectState(ClipItem*,int,QList,bool)), trackView->projectView(), SLOT(slotChangeEffectState(ClipItem*,int,QList,bool))); connect(m_effectStack, SIGNAL(changeEffectPosition(ClipItem*,int,QList,int)), trackView->projectView(), SLOT(slotChangeEffectPosition(ClipItem*,int,QList,int))); connect(m_effectStack, SIGNAL(refreshEffectStack(ClipItem*)), trackView->projectView(), SLOT(slotRefreshEffects(ClipItem*))); connect(m_effectStack, SIGNAL(seekTimeline(int)), trackView->projectView(), SLOT(seekCursorPos(int))); connect(m_effectStack, SIGNAL(importClipKeyframes(GraphicsRectItem, ItemInfo, QDomElement, QMap)), trackView->projectView(), SLOT(slotImportClipKeyframes(GraphicsRectItem, ItemInfo, QDomElement, QMap))); // Transition config signals connect(m_effectStack->transitionConfig(), SIGNAL(transitionUpdated(Transition*,QDomElement)), trackView->projectView() , SLOT(slotTransitionUpdated(Transition*,QDomElement))); connect(m_effectStack->transitionConfig(), SIGNAL(seekTimeline(int)), trackView->projectView() , SLOT(seekCursorPos(int))); connect(trackView->projectView(), SIGNAL(activateDocumentMonitor()), m_projectMonitor, SLOT(slotActivateMonitor())); connect(project, &KdenliveDoc::updateFps, trackView, &Timeline::updateProfile, Qt::DirectConnection); connect(trackView, SIGNAL(zoneMoved(int,int)), this, SLOT(slotZoneMoved(int,int))); trackView->projectView()->setContextMenu(m_timelineContextMenu, m_timelineContextClipMenu, m_timelineContextTransitionMenu, m_clipTypeGroup, static_cast(factory()->container(QStringLiteral("marker_menu"), this))); if (m_renderWidget) { slotCheckRenderStatus(); m_renderWidget->setProfile(project->mltProfile()); m_renderWidget->setGuides(pCore->projectManager()->currentTimeline()->projectView()->guidesData(), project->projectDuration()); m_renderWidget->setDocumentPath(project->projectFolder().path() + QDir::separator()); m_renderWidget->setRenderProfile(project->getRenderProperties()); } m_zoomSlider->setValue(project->zoom().x()); m_commandStack->setActiveStack(project->commandStack()); KdenliveSettings::setProject_display_ratio(project->dar()); setWindowTitle(project->description()); setWindowModified(project->isModified()); m_saveAction->setEnabled(project->isModified()); m_normalEditTool->setChecked(true); connect(m_projectMonitor, SIGNAL(durationChanged(int)), this, SLOT(slotUpdateProjectDuration(int))); pCore->monitorManager()->setDocument(project); trackView->updateProfile(false); if (m_recMonitor) { m_recMonitor->slotUpdateCaptureFolder(project->projectFolder().path() + QDir::separator()); } //Update the mouse position display so it will display in DF/NDF format by default based on the project setting. slotUpdateMousePosition(0); // Update guides info in render widget slotGuidesUpdated(); // Make sure monitor is visible so that it is painted black on startup //show(); //pCore->monitorManager()->activateMonitor(Kdenlive::ClipMonitor, true); // set tool to select tool m_buttonSelectTool->setChecked(true); connect(m_projectMonitorDock, SIGNAL(visibilityChanged(bool)), m_projectMonitor, SLOT(slotRefreshMonitor(bool)), Qt::UniqueConnection); connect(m_clipMonitorDock, SIGNAL(visibilityChanged(bool)), m_clipMonitor, SLOT(slotRefreshMonitor(bool)), Qt::UniqueConnection); } void MainWindow::slotZoneMoved(int start, int end) { pCore->projectManager()->current()->setZone(start, end); m_projectMonitor->slotZoneMoved(start, end); } void MainWindow::slotGuidesUpdated() { QMap guidesData = pCore->projectManager()->currentTimeline()->projectView()->guidesData(); if (m_renderWidget) m_renderWidget->setGuides(guidesData, pCore->projectManager()->current()->projectDuration()); m_projectMonitor->setGuides(guidesData); } void MainWindow::slotEditKeys() { KShortcutsDialog dialog(KShortcutsEditor::AllActions, KShortcutsEditor::LetterShortcutsAllowed, this); dialog.addCollection(actionCollection(), i18nc("general keyboard shortcuts", "General")); dialog.configure(); } void MainWindow::slotPreferences(int page, int option) { /* * An instance of your dialog could be already created and could be * cached, in which case you want to display the cached dialog * instead of creating another one */ if (m_stopmotion) { m_stopmotion->slotLive(false); } if (KConfigDialog::showDialog(QStringLiteral("settings"))) { KdenliveSettingsDialog* d = static_cast (KConfigDialog::exists(QStringLiteral("settings"))); if (page != -1) d->showPage(page, option); return; } // KConfigDialog didn't find an instance of this dialog, so lets // create it : // Get the mappable actions in localized form QMap actions; KActionCollection* collection = actionCollection(); QRegExp ampEx("&{1,1}"); foreach (const QString& action_name, m_actionNames) { QString action_text = collection->action(action_name)->text(); action_text.remove(ampEx); actions[action_text] = action_name; } KdenliveSettingsDialog* dialog = new KdenliveSettingsDialog(actions, m_gpuAllowed, this); connect(dialog, SIGNAL(settingsChanged(QString)), this, SLOT(updateConfiguration())); connect(dialog, SIGNAL(settingsChanged(QString)), SIGNAL(configurationChanged())); connect(dialog, SIGNAL(doResetProfile()), pCore->projectManager(), SLOT(slotResetProfiles())); connect(dialog, SIGNAL(checkTabPosition()), this, SLOT(slotCheckTabPosition())); connect(dialog, SIGNAL(restartKdenlive()), this, SLOT(slotRestart())); connect(dialog, SIGNAL(updateLibraryFolder()), pCore, SIGNAL(updateLibraryPath())); if (m_recMonitor) { connect(dialog, SIGNAL(updateCaptureFolder()), this, SLOT(slotUpdateCaptureFolder())); connect(dialog, SIGNAL(updateFullScreenGrab()), m_recMonitor, SLOT(slotUpdateFullScreenGrab())); } dialog->show(); if (page != -1) { dialog->showPage(page, option); } } void MainWindow::slotCheckTabPosition() { QTabWidget::TabPosition pos = tabPosition(Qt::LeftDockWidgetArea); bool reload = false; if (KdenliveSettings::verticaltabs() && pos != QTabWidget::East) { reload = true; } else if (!KdenliveSettings::verticaltabs() && pos != QTabWidget::North) { reload = true; } if (reload) setTabPosition(Qt::AllDockWidgetAreas, KdenliveSettings::verticaltabs() ? QTabWidget::East : QTabWidget::North); } void MainWindow::slotRestart() { m_exitCode = EXIT_RESTART; QApplication::closeAllWindows(); } void MainWindow::closeEvent(QCloseEvent* event) { KXmlGuiWindow::closeEvent(event); if (event->isAccepted()) { QApplication::exit(m_exitCode); return; } } void MainWindow::slotUpdateCaptureFolder() { if (m_recMonitor) { if (pCore->projectManager()->current()) m_recMonitor->slotUpdateCaptureFolder(pCore->projectManager()->current()->projectFolder().path() + QDir::separator()); else m_recMonitor->slotUpdateCaptureFolder(KdenliveSettings::defaultprojectfolder()); } } void MainWindow::updateConfiguration() { //TODO: we should apply settings to all projects, not only the current one if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->refresh(); pCore->projectManager()->currentTimeline()->projectView()->checkAutoScroll(); pCore->projectManager()->currentTimeline()->checkTrackHeight(); } m_buttonAudioThumbs->setChecked(KdenliveSettings::audiothumbnails()); m_buttonVideoThumbs->setChecked(KdenliveSettings::videothumbnails()); m_buttonShowMarkers->setChecked(KdenliveSettings::showmarkers()); slotSwitchSplitAudio(KdenliveSettings::splitaudio()); // Update list of transcoding profiles buildDynamicActions(); loadClipActions(); } void MainWindow::slotSwitchSplitAudio(bool enable) { KdenliveSettings::setSplitaudio(enable); m_buttonAutomaticSplitAudio->setChecked(KdenliveSettings::splitaudio()); if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->updateHeaders(); } } void MainWindow::slotSwitchVideoThumbs() { KdenliveSettings::setVideothumbnails(!KdenliveSettings::videothumbnails()); if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->projectView()->slotUpdateAllThumbs(); } m_buttonVideoThumbs->setChecked(KdenliveSettings::videothumbnails()); } void MainWindow::slotSwitchAudioThumbs() { KdenliveSettings::setAudiothumbnails(!KdenliveSettings::audiothumbnails()); pCore->binController()->checkAudioThumbs(); if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->refresh(); pCore->projectManager()->currentTimeline()->projectView()->checkAutoScroll(); } m_buttonAudioThumbs->setChecked(KdenliveSettings::audiothumbnails()); } void MainWindow::slotSwitchMarkersComments() { KdenliveSettings::setShowmarkers(!KdenliveSettings::showmarkers()); if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->refresh(); } m_buttonShowMarkers->setChecked(KdenliveSettings::showmarkers()); } void MainWindow::slotSwitchSnap() { KdenliveSettings::setSnaptopoints(!KdenliveSettings::snaptopoints()); m_buttonSnap->setChecked(KdenliveSettings::snaptopoints()); } void MainWindow::slotDeleteItem() { if (QApplication::focusWidget() && QApplication::focusWidget()->parentWidget() && QApplication::focusWidget()->parentWidget() == pCore->bin()) { pCore->bin()->slotDeleteClip(); } else { QWidget *widget = QApplication::focusWidget(); while (widget && widget != this) { if (widget == m_effectStackDock) { m_effectStack->deleteCurrentEffect(); return; } widget = widget->parentWidget(); } // effect stack has no focus if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->projectView()->deleteSelectedClips(); } } } void MainWindow::slotAddClipMarker() { KdenliveDoc *project = pCore->projectManager()->current(); ClipController *clip = NULL; GenTime pos; if (m_projectMonitor->isActive()) { if (pCore->projectManager()->currentTimeline()) { ClipItem *item = pCore->projectManager()->currentTimeline()->projectView()->getActiveClipUnderCursor(); if (item) { pos = GenTime((int)((m_projectMonitor->position() - item->startPos() + item->cropStart()).frames(project->fps()) * item->speed() + 0.5), project->fps()); clip = pCore->binController()->getController(item->getBinId()); } } } else { clip = m_clipMonitor->currentController(); pos = m_clipMonitor->position(); } if (!clip) { m_messageLabel->setMessage(i18n("Cannot find clip to add marker"), ErrorMessage); return; } QString id = clip->clipId(); CommentedTime marker(pos, i18n("Marker"), KdenliveSettings::default_marker_type()); QPointer d = new MarkerDialog(clip, marker, project->timecode(), i18n("Add Marker"), this); if (d->exec() == QDialog::Accepted) { pCore->bin()->slotAddClipMarker(id, QList () << d->newMarker()); QString hash = clip->getClipHash(); if (!hash.isEmpty()) project->cacheImage(hash + '#' + QString::number(d->newMarker().time().frames(project->fps())), d->markerImage()); } delete d; } void MainWindow::slotDeleteClipMarker(bool allowGuideDeletion) { ClipController *clip = NULL; GenTime pos; if (m_projectMonitor->isActive()) { if (pCore->projectManager()->currentTimeline()) { ClipItem *item = pCore->projectManager()->currentTimeline()->projectView()->getActiveClipUnderCursor(); if (item) { pos = (m_projectMonitor->position() - item->startPos() + item->cropStart()) / item->speed(); clip = pCore->binController()->getController(item->getBinId()); } } } else { clip = m_clipMonitor->currentController(); pos = m_clipMonitor->position(); } if (!clip) { m_messageLabel->setMessage(i18n("Cannot find clip to remove marker"), ErrorMessage); return; } QString id = clip->clipId(); QString comment = clip->markerComment(pos); if (comment.isEmpty()) { if (allowGuideDeletion && m_projectMonitor->isActive()) { slotDeleteGuide(); } else m_messageLabel->setMessage(i18n("No marker found at cursor time"), ErrorMessage); return; } pCore->bin()->deleteClipMarker(comment, id, pos); } void MainWindow::slotDeleteAllClipMarkers() { ClipController *clip = NULL; if (m_projectMonitor->isActive()) { if (pCore->projectManager()->currentTimeline()) { ClipItem *item = pCore->projectManager()->currentTimeline()->projectView()->getActiveClipUnderCursor(); if (item) { clip = pCore->binController()->getController(item->getBinId()); } } } else { clip = m_clipMonitor->currentController(); } if (!clip) { m_messageLabel->setMessage(i18n("Cannot find clip to remove marker"), ErrorMessage); return; } pCore->bin()->deleteAllClipMarkers(clip->clipId()); } void MainWindow::slotEditClipMarker() { ClipController *clip = NULL; GenTime pos; if (m_projectMonitor->isActive()) { if (pCore->projectManager()->currentTimeline()) { ClipItem *item = pCore->projectManager()->currentTimeline()->projectView()->getActiveClipUnderCursor(); if (item) { pos = (m_projectMonitor->position() - item->startPos() + item->cropStart()) / item->speed(); clip = pCore->binController()->getController(item->getBinId()); } } } else { clip = m_clipMonitor->currentController(); pos = m_clipMonitor->position(); } if (!clip) { m_messageLabel->setMessage(i18n("Cannot find clip to remove marker"), ErrorMessage); return; } QString id = clip->clipId(); CommentedTime oldMarker = clip->markerAt(pos); if (oldMarker == CommentedTime()) { m_messageLabel->setMessage(i18n("No marker found at cursor time"), ErrorMessage); return; } QPointer d = new MarkerDialog(clip, oldMarker, pCore->projectManager()->current()->timecode(), i18n("Edit Marker"), this); if (d->exec() == QDialog::Accepted) { pCore->bin()->slotAddClipMarker(id, QList () <newMarker()); QString hash = clip->getClipHash(); if (!hash.isEmpty()) pCore->projectManager()->current()->cacheImage(hash + '#' + QString::number(d->newMarker().time().frames(pCore->projectManager()->current()->fps())), d->markerImage()); if (d->newMarker().time() != pos) { // remove old marker oldMarker.setMarkerType(-1); pCore->bin()->slotAddClipMarker(id, QList () <projectManager()->currentTimeline() || !pCore->projectManager()->current()) return; if (m_clipMonitor->isActive()) { ClipController *clip = m_clipMonitor->currentController(); GenTime pos = m_clipMonitor->position(); if (!clip) { m_messageLabel->setMessage(i18n("Cannot find clip to add marker"), ErrorMessage); return; } //TODO: allow user to set default marker category CommentedTime marker(pos, pCore->projectManager()->current()->timecode().getDisplayTimecode(pos, false), KdenliveSettings::default_marker_type()); pCore->bin()->slotAddClipMarker(clip->clipId(), QList () <projectManager()->currentTimeline()->projectView()->slotAddGuide(false); } } void MainWindow::slotAddGuide() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->slotAddGuide(); } void MainWindow::slotInsertSpace() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->slotInsertSpace(); } void MainWindow::slotRemoveSpace() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->slotRemoveSpace(); } void MainWindow::slotInsertTrack() { pCore->monitorManager()->activateMonitor(Kdenlive::ProjectMonitor); if (pCore->projectManager()->currentTimeline()) { int ix = pCore->projectManager()->currentTimeline()->projectView()->selectedTrack(); pCore->projectManager()->currentTimeline()->projectView()->slotInsertTrack(ix ); } if (pCore->projectManager()->current()) { m_effectStack->transitionConfig()->updateProjectFormat(); } } void MainWindow::slotDeleteTrack() { pCore->monitorManager()->activateMonitor(Kdenlive::ProjectMonitor); if (pCore->projectManager()->currentTimeline()) { int ix = pCore->projectManager()->currentTimeline()->projectView()->selectedTrack(); pCore->projectManager()->currentTimeline()->projectView()->slotDeleteTrack(ix); } if (pCore->projectManager()->current()) { m_effectStack->transitionConfig()->updateProjectFormat(); } } void MainWindow::slotConfigTrack() { pCore->monitorManager()->activateMonitor(Kdenlive::ProjectMonitor); if (pCore->projectManager()->currentTimeline()) { int ix = pCore->projectManager()->currentTimeline()->projectView()->selectedTrack(); pCore->projectManager()->currentTimeline()->projectView()->slotConfigTracks(ix); } if (pCore->projectManager()->current()) m_effectStack->transitionConfig()->updateProjectFormat(); } void MainWindow::slotSelectTrack() { pCore->monitorManager()->activateMonitor(Kdenlive::ProjectMonitor); if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->projectView()->slotSelectClipsInTrack(); } } void MainWindow::slotSelectAllTracks() { pCore->monitorManager()->activateMonitor(Kdenlive::ProjectMonitor); if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->slotSelectAllClips(); } void MainWindow::slotEditGuide(int pos, QString text) { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->slotEditGuide(pos, text); } void MainWindow::slotDeleteGuide() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->slotDeleteGuide(); } void MainWindow::slotDeleteAllGuides() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->slotDeleteAllGuides(); } void MainWindow::slotCutTimelineClip() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->cutSelectedClips(); } void MainWindow::slotInsertClipOverwrite() { if (pCore->projectManager()->currentTimeline()) { QPoint binZone = m_clipMonitor->getZoneInfo(); pCore->projectManager()->currentTimeline()->projectView()->insertZone(TimelineMode::OverwriteEdit, m_clipMonitor->activeClipId(), binZone); } } void MainWindow::slotInsertClipInsert() { if (pCore->projectManager()->currentTimeline()) { QPoint binZone = m_clipMonitor->getZoneInfo(); pCore->projectManager()->currentTimeline()->projectView()->insertZone(TimelineMode::InsertEdit, m_clipMonitor->activeClipId(), binZone); } } void MainWindow::slotExtractZone() { if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->projectView()->extractZone(QPoint(), true); } } void MainWindow::slotLiftZone() { if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->projectView()->extractZone(QPoint(),false); } } void MainWindow::slotPreviewRender() { if (pCore->projectManager()->current()) { pCore->projectManager()->currentTimeline()->startPreviewRender(); } } void MainWindow::slotStopPreviewRender() { if (pCore->projectManager()->current()) { pCore->projectManager()->currentTimeline()->stopPreviewRender(); } } void MainWindow::slotDefinePreviewRender() { if (pCore->projectManager()->current()) { pCore->projectManager()->currentTimeline()->addPreviewRange(true); } } void MainWindow::slotRemovePreviewRender() { if (pCore->projectManager()->current()) { pCore->projectManager()->currentTimeline()->addPreviewRange(false); } } void MainWindow::slotSelectTimelineClip() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->selectClip(true); } void MainWindow::slotSelectTimelineTransition() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->selectTransition(true); } void MainWindow::slotDeselectTimelineClip() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->selectClip(false, true); } void MainWindow::slotDeselectTimelineTransition() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->selectTransition(false, true); } void MainWindow::slotSelectAddTimelineClip() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->selectClip(true, true); } void MainWindow::slotSelectAddTimelineTransition() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->selectTransition(true, true); } void MainWindow::slotGroupClips() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->groupClips(); } void MainWindow::slotUnGroupClips() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->groupClips(false); } void MainWindow::slotEditItemDuration() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->editItemDuration(); } void MainWindow::slotAddProjectClip(const QUrl &url) { pCore->bin()->droppedUrls(QList() << url); } void MainWindow::slotAddProjectClipList(const QList &urls) { pCore->bin()->droppedUrls(urls); } void MainWindow::slotAddTransition(QAction *result) { if (!result) return; QStringList info = result->data().toStringList(); if (info.isEmpty() || info.count() < 2) return; QDomElement transition = transitions.getEffectByTag(info.at(0), info.at(1)); if (pCore->projectManager()->currentTimeline() && !transition.isNull()) { pCore->projectManager()->currentTimeline()->projectView()->slotAddTransitionToSelectedClips(transition.cloneNode().toElement()); } } void MainWindow::slotAddVideoEffect(QAction *result) { if (!result) { return; } const int VideoEffect = 1; const int AudioEffect = 2; QStringList info = result->data().toStringList(); if (info.isEmpty() || info.size() < 3) { return; } QDomElement effect ; if (info.last() == QString::number((int) VideoEffect)) { effect = videoEffects.getEffectByTag(info.at(0), info.at(1)); } else if (info.last() == QString::number((int) AudioEffect)) { effect = audioEffects.getEffectByTag(info.at(0), info.at(1)); } else { effect = customEffects.getEffectByTag(info.at(0), info.at(1)); } if (!effect.isNull()) { slotAddEffect(effect); } else { m_messageLabel->setMessage(i18n("Cannot find effect %1 / %2", info.at(0), info.at(1)), ErrorMessage); } } void MainWindow::slotZoomIn() { m_zoomSlider->setValue(m_zoomSlider->value() - 1); slotShowZoomSliderToolTip(); } void MainWindow::slotZoomOut() { m_zoomSlider->setValue(m_zoomSlider->value() + 1); slotShowZoomSliderToolTip(); } void MainWindow::slotFitZoom() { if (pCore->projectManager()->currentTimeline()) { m_zoomSlider->setValue(pCore->projectManager()->currentTimeline()->fitZoom()); // Make sure to reset scroll bar to start pCore->projectManager()->currentTimeline()->projectView()->scrollToStart(); } } void MainWindow::slotSetZoom(int value) { value = qMax(m_zoomSlider->minimum(), value); value = qMin(m_zoomSlider->maximum(), value); if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->slotChangeZoom(value); } m_zoomOut->setEnabled(value < m_zoomSlider->maximum()); m_zoomIn->setEnabled(value > m_zoomSlider->minimum()); slotUpdateZoomSliderToolTip(value); m_zoomSlider->blockSignals(true); m_zoomSlider->setValue(value); m_zoomSlider->blockSignals(false); } void MainWindow::slotShowZoomSliderToolTip(int zoomlevel) { if (zoomlevel != -1) { slotUpdateZoomSliderToolTip(zoomlevel); } QPoint global = m_zoomSlider->rect().topLeft(); global.ry() += m_zoomSlider->height() / 2; QHelpEvent toolTipEvent(QEvent::ToolTip, QPoint(0, 0), m_zoomSlider->mapToGlobal(global)); QApplication::sendEvent(m_zoomSlider, &toolTipEvent); } void MainWindow::slotUpdateZoomSliderToolTip(int zoomlevel) { m_zoomSlider->setToolTip(i18n("Zoom Level: %1/13", (13 - zoomlevel))); } void MainWindow::slotGotProgressInfo(const QString &message, int progress, MessageType type) { if (type == DefaultMessage) { m_statusProgressBar->setValue(progress); } m_messageLabel->setMessage(progress < 100 ? message : QString(), type); if (progress >= 0) { if (type == DefaultMessage) { m_statusProgressBar->setVisible(progress < 100); } } else { m_statusProgressBar->setVisible(false); } } void MainWindow::customEvent(QEvent* e) { if (e->type() == QEvent::User) m_messageLabel->setMessage(static_cast (e)->message(), MltError); } void MainWindow::slotTimelineClipSelected(ClipItem* item, bool reloadStack, bool raise) { m_effectStack->slotClipItemSelected(item, m_projectMonitor, reloadStack); m_projectMonitor->slotSetSelectedClip(item); if (raise) { m_effectStack->raiseWindow(m_effectStackDock); } } void MainWindow::slotTrackSelected(int index, const TrackInfo &info, bool raise) { m_effectStack->slotTrackItemSelected(index, info, m_projectMonitor); if (raise) { m_effectStack->raiseWindow(m_effectStackDock); } } void MainWindow::slotActivateTransitionView(Transition *transition) { if (transition) m_effectStack->raiseWindow(m_effectStackDock); } void MainWindow::slotSnapRewind() { if (m_projectMonitor->isActive()) { if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->projectView()->slotSeekToPreviousSnap(); } } else { m_clipMonitor->slotSeekToPreviousSnap(); } } void MainWindow::slotSnapForward() { if (m_projectMonitor->isActive()) { if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->projectView()->slotSeekToNextSnap(); } } else { m_clipMonitor->slotSeekToNextSnap(); } } void MainWindow::slotClipStart() { if (m_projectMonitor->isActive()) { if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->projectView()->clipStart(); } } } void MainWindow::slotClipEnd() { if (m_projectMonitor->isActive()) { if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->projectView()->clipEnd(); } } } void MainWindow::slotChangeTool(QAction * action) { if (action == m_buttonSelectTool) slotSetTool(SelectTool); else if (action == m_buttonRazorTool) slotSetTool(RazorTool); else if (action == m_buttonSpacerTool) slotSetTool(SpacerTool); } void MainWindow::slotChangeEdit(QAction * action) { if (!pCore->projectManager()->currentTimeline()) return; if (action == m_overwriteEditTool) pCore->projectManager()->currentTimeline()->projectView()->setEditMode(TimelineMode::OverwriteEdit); else if (action == m_insertEditTool) pCore->projectManager()->currentTimeline()->projectView()->setEditMode(TimelineMode::InsertEdit); else pCore->projectManager()->currentTimeline()->projectView()->setEditMode(TimelineMode::NormalEdit); } void MainWindow::slotSetTool(ProjectTool tool) { if (pCore->projectManager()->current() && pCore->projectManager()->currentTimeline()) { //pCore->projectManager()->current()->setTool(tool); QString message; switch (tool) { case SpacerTool: message = i18n("Ctrl + click to use spacer on current track only"); break; case RazorTool: message = i18n("Click on a clip to cut it, Shift + move to preview cut frame"); break; default: message = i18n("Shift + click to create a selection rectangle, Ctrl + click to add an item to selection"); break; } m_messageLabel->setMessage(message, InformationMessage); pCore->projectManager()->currentTimeline()->projectView()->setTool(tool); } } void MainWindow::slotCopy() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->copyClip(); } void MainWindow::slotPaste() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->pasteClip(); } void MainWindow::slotPasteEffects() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->pasteClipEffects(); } void MainWindow::slotClipInTimeline(const QString &clipId) { if (pCore->projectManager()->currentTimeline()) { QList matching = pCore->projectManager()->currentTimeline()->projectView()->findId(clipId); QMenu *inTimelineMenu = static_cast(factory()->container(QStringLiteral("clip_in_timeline"), this)); QList actionList; for (int i = 0; i < matching.count(); ++i) { QString track = pCore->projectManager()->currentTimeline()->getTrackInfo(matching.at(i).track).trackName; QString start = pCore->projectManager()->current()->timecode().getTimecode(matching.at(i).startPos); int j = 0; QAction *a = new QAction(track + ": " + start, inTimelineMenu); a->setData(QStringList() << track << start); connect(a, SIGNAL(triggered()), this, SLOT(slotSelectClipInTimeline())); while (j < actionList.count()) { if (actionList.at(j)->text() > a->text()) break; j++; } actionList.insert(j, a); } QList list = inTimelineMenu->actions(); unplugActionList("timeline_occurences"); qDeleteAll(list); plugActionList("timeline_occurences", actionList); if (actionList.isEmpty()) { inTimelineMenu->setEnabled(false); } else { inTimelineMenu->setEnabled(true); } } } void MainWindow::slotClipInProjectTree() { if (pCore->projectManager()->currentTimeline()) { int pos = -1; QPoint zone; const QString selectedId = pCore->projectManager()->currentTimeline()->projectView()->getClipUnderCursor(&pos, &zone); if (selectedId.isEmpty()) { return; } m_projectBinDock->raise(); pCore->bin()->selectClipById(selectedId, pos, zone); if (m_projectMonitor->isActive()) { slotSwitchMonitors(); } } } void MainWindow::slotSelectClipInTimeline() { if (pCore->projectManager()->currentTimeline()) { QAction *action = qobject_cast(sender()); QStringList data = action->data().toStringList(); pCore->projectManager()->currentTimeline()->projectView()->selectFound(data.at(0), data.at(1)); } } /** Gets called when the window gets hidden */ void MainWindow::hideEvent(QHideEvent */*event*/) { if (isMinimized() && pCore->monitorManager()) pCore->monitorManager()->pauseActiveMonitor(); } /*void MainWindow::slotSaveZone(Render *render, const QPoint &zone, DocClipBase *baseClip, QUrl path) { QPointer dialog = new QDialog(this); dialog->setWindowTitle("Save clip zone"); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); QVBoxLayout *mainLayout = new QVBoxLayout; dialog->setLayout(mainLayout); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); dialog->connect(buttonBox, SIGNAL(accepted()), dialog, SLOT(accept())); dialog->connect(buttonBox, SIGNAL(rejected()), dialog, SLOT(reject())); QLabel *label1 = new QLabel(i18n("Save clip zone as:"), this); if (path.isEmpty()) { QString tmppath = pCore->projectManager()->current()->projectFolder().path() + QDir::separator(); if (baseClip == NULL) { tmppath.append("untitled.mlt"); } else { tmppath.append((baseClip->name().isEmpty() ? baseClip->fileURL().fileName() : baseClip->name()) + '-' + QString::number(zone.x()).rightJustified(4, '0') + ".mlt"); } path = QUrl(tmppath); } KUrlRequester *url = new KUrlRequester(path, this); url->setFilter("video/mlt-playlist"); QLabel *label2 = new QLabel(i18n("Description:"), this); QLineEdit *edit = new QLineEdit(this); mainLayout->addWidget(label1); mainLayout->addWidget(url); mainLayout->addWidget(label2); mainLayout->addWidget(edit); mainLayout->addWidget(buttonBox); if (dialog->exec() == QDialog::Accepted) { if (QFile::exists(url->url().path())) { if (KMessageBox::questionYesNo(this, i18n("File %1 already exists.\nDo you want to overwrite it?", url->url().path())) == KMessageBox::No) { slotSaveZone(render, zone, baseClip, url->url()); delete dialog; return; } } if (baseClip && !baseClip->fileURL().isEmpty()) { // create zone from clip url, so that we don't have problems with proxy clips QProcess p; QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); env.remove("MLT_PROFILE"); p.setProcessEnvironment(env); p.start(KdenliveSettings::rendererpath(), QStringList() << baseClip->fileURL().toLocalFile() << "in=" + QString::number(zone.x()) << "out=" + QString::number(zone.y()) << "-consumer" << "xml:" + url->url().path()); if (!p.waitForStarted(3000)) { KMessageBox::sorry(this, i18n("Cannot start MLT's renderer:\n%1", KdenliveSettings::rendererpath())); } else if (!p.waitForFinished(5000)) { KMessageBox::sorry(this, i18n("Timeout while creating xml output")); } } else render->saveZone(url->url(), edit->text(), zone); } delete dialog; }*/ void MainWindow::slotResizeItemStart() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->setInPoint(); } void MainWindow::slotResizeItemEnd() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->setOutPoint(); } int MainWindow::getNewStuff(const QString &configFile) { KNS3::Entry::List entries; QPointer dialog = new KNS3::DownloadDialog(configFile); if (dialog->exec()) entries = dialog->changedEntries(); foreach(const KNS3::Entry & entry, entries) { if (entry.status() == KNS3::Entry::Installed) qDebug() << "// Installed files: " << entry.installedFiles(); } delete dialog; return entries.size(); } void MainWindow::slotGetNewTitleStuff() { if (getNewStuff(QStringLiteral("kdenlive_titles.knsrc")) > 0) { // get project title path QString titlePath = pCore->projectManager()->current()->projectFolder().path(); titlePath.append(QStringLiteral("/titles/")); TitleWidget::refreshTitleTemplates(titlePath); } } void MainWindow::slotGetNewLumaStuff() { if (getNewStuff(QStringLiteral("kdenlive_wipes.knsrc")) > 0) { initEffects::refreshLumas(); pCore->projectManager()->currentTimeline()->projectView()->reloadTransitionLumas(); } } void MainWindow::slotGetNewRenderStuff() { if (getNewStuff(QStringLiteral("kdenlive_renderprofiles.knsrc")) > 0) if (m_renderWidget) m_renderWidget->reloadProfiles(); } void MainWindow::slotGetNewMltProfileStuff() { if (getNewStuff(QStringLiteral("kdenlive_projectprofiles.knsrc")) > 0) { // update the list of profiles in settings dialog KdenliveSettingsDialog* d = static_cast (KConfigDialog::exists(QStringLiteral("settings"))); if (d) d->checkProfile(); } } void MainWindow::slotAutoTransition() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->autoTransition(); } void MainWindow::slotSplitAudio() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->splitAudio(); } void MainWindow::slotSetAudioAlignReference() { if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->projectView()->setAudioAlignReference(); } } void MainWindow::slotAlignAudio() { if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->projectView()->alignAudio(); } } void MainWindow::slotUpdateClipType(QAction *action) { if (pCore->projectManager()->currentTimeline()) { PlaylistState::ClipState state = (PlaylistState::ClipState) action->data().toInt(); pCore->projectManager()->currentTimeline()->projectView()->setClipType(state); } } void MainWindow::slotDvdWizard(const QString &url) { // We must stop the monitors since we create a new on in the dvd wizard QPointer w = new DvdWizard(pCore->monitorManager(), url, this); w->exec(); delete w; pCore->monitorManager()->activateMonitor(Kdenlive::ClipMonitor); } void MainWindow::slotShowTimeline(bool show) { if (show == false) { m_timelineState = saveState(); centralWidget()->setHidden(true); } else { centralWidget()->setHidden(false); restoreState(m_timelineState); } } void MainWindow::loadClipActions() { unplugActionList(QStringLiteral("add_effect")); plugActionList(QStringLiteral("add_effect"), m_effectsMenu->actions()); QList clipJobActions = getExtraActions("clipjobs"); unplugActionList("clip_jobs"); plugActionList("clip_jobs", clipJobActions); QList atcActions = getExtraActions(QStringLiteral("audiotranscoderslist")); unplugActionList(QStringLiteral("audio_transcoders_list")); plugActionList(QStringLiteral("audio_transcoders_list"), atcActions); QList tcActions = getExtraActions(QStringLiteral("transcoderslist")); unplugActionList(QStringLiteral("transcoders_list")); plugActionList(QStringLiteral("transcoders_list"), tcActions); } void MainWindow::loadDockActions() { QList list = kdenliveCategoryMap.value(QStringLiteral("interface"))->actions(); // Sort actions QMap sorted; QStringList sortedList; foreach(QAction *a, list) { sorted.insert(a->text(), a); sortedList << a->text(); } QList orderedList; sortedList.sort(Qt::CaseInsensitive); foreach(const QString &text, sortedList) { orderedList << sorted.value(text); } unplugActionList( "dock_actions" ); plugActionList( "dock_actions", orderedList); } void MainWindow::buildDynamicActions() { KActionCategory *ts = NULL; if (kdenliveCategoryMap.contains(QStringLiteral("clipjobs"))) { ts = kdenliveCategoryMap.take(QStringLiteral("clipjobs")); delete ts; } ts = new KActionCategory(i18n("Clip Jobs"), m_extraFactory->actionCollection()); Mlt::Profile profile; Mlt::Filter *filter; foreach(const QString &stab, QStringList() << "vidstab" << "videostab2" << "videostab") { filter = Mlt::Factory::filter(profile, (char*)stab.toUtf8().constData()); if (filter && filter->is_valid()) { QAction *action = new QAction(i18n("Stabilize") + " (" + stab + ")", m_extraFactory->actionCollection()); action->setData(QStringList() << QString::number((int) AbstractClipJob::FILTERCLIPJOB) << stab); ts->addAction(action->text(), action); connect(action, SIGNAL(triggered(bool)), pCore->bin(), SLOT(slotStartClipJob(bool))); delete filter; break; } delete filter; } filter = Mlt::Factory::filter(profile,(char*)"motion_est"); if (filter) { if (filter->is_valid()) { QAction *action = new QAction(i18n("Automatic scene split"), m_extraFactory->actionCollection()); QStringList stabJob; stabJob << QString::number((int) AbstractClipJob::FILTERCLIPJOB) << QStringLiteral("motion_est"); action->setData(stabJob); ts->addAction(action->text(), action); connect(action, SIGNAL(triggered(bool)), pCore->bin(), SLOT(slotStartClipJob(bool))); } delete filter; } if (KdenliveSettings::producerslist().contains(QStringLiteral("timewarp"))) { QAction *action = new QAction(i18n("Reverse clip"), m_extraFactory->actionCollection()); QStringList stabJob; stabJob << QString::number((int) AbstractClipJob::FILTERCLIPJOB) << QStringLiteral("timewarp"); action->setData(stabJob); ts->addAction(action->text(), action); connect(action, SIGNAL(triggered(bool)), pCore->bin(), SLOT(slotStartClipJob(bool))); } QAction *action = new QAction(i18n("Analyse keyframes"), m_extraFactory->actionCollection()); QStringList stabJob(QString::number((int) AbstractClipJob::ANALYSECLIPJOB)); action->setData(stabJob); ts->addAction(action->text(), action); connect(action, SIGNAL(triggered(bool)), pCore->bin(), SLOT(slotStartClipJob(bool))); kdenliveCategoryMap.insert(QStringLiteral("clipjobs"), ts); if (kdenliveCategoryMap.contains(QStringLiteral("transcoderslist"))) { ts = kdenliveCategoryMap.take(QStringLiteral("transcoderslist")); delete ts; } if (kdenliveCategoryMap.contains(QStringLiteral("audiotranscoderslist"))) { ts = kdenliveCategoryMap.take(QStringLiteral("audiotranscoderslist")); delete ts; } ts = new KActionCategory(i18n("Transcoders"), m_extraFactory->actionCollection()); KActionCategory *ats = new KActionCategory(i18n("Extract Audio"), m_extraFactory->actionCollection()); KSharedConfigPtr config = KSharedConfig::openConfig(QStandardPaths::locate(QStandardPaths::DataLocation, QStringLiteral("kdenlivetranscodingrc")), KConfig::CascadeConfig); KConfigGroup transConfig(config, "Transcoding"); // read the entries QMap< QString, QString > profiles = transConfig.entryMap(); QMapIterator i(profiles); while (i.hasNext()) { i.next(); QStringList data; data << QString::number((int) AbstractClipJob::TRANSCODEJOB); data << i.value().split(';'); QAction *a = new QAction(i.key(), m_extraFactory->actionCollection()); a->setData(data); if (data.count() > 1) a->setToolTip(data.at(1)); // slottranscode connect(a, SIGNAL(triggered(bool)), pCore->bin(), SLOT(slotStartClipJob(bool))); if (data.count() > 3 && data.at(3) == "audio") { // This is an audio transcoding action ats->addAction(i.key(), a); } else { ts->addAction(i.key(), a); } } kdenliveCategoryMap.insert(QStringLiteral("transcoderslist"), ts); kdenliveCategoryMap.insert(QStringLiteral("audiotranscoderslist"), ats); // Populate View menu with show / hide actions for dock widgets KActionCategory *guiActions = NULL; if (kdenliveCategoryMap.contains(QStringLiteral("interface"))) { guiActions = kdenliveCategoryMap.take(QStringLiteral("interface")); delete guiActions; } guiActions = new KActionCategory(i18n("Interface"), actionCollection()); QAction *showTimeline = new QAction(i18n("Timeline"), this); showTimeline->setCheckable(true); showTimeline->setChecked(true); connect(showTimeline, SIGNAL(triggered(bool)), this, SLOT(slotShowTimeline(bool))); guiActions->addAction(showTimeline->text(), showTimeline); actionCollection()->addAction(showTimeline->text(), showTimeline); QList docks = findChildren(); for (int i = 0; i < docks.count(); ++i) { QDockWidget* dock = docks.at(i); QAction * dockInformations = dock->toggleViewAction(); if (!dockInformations) continue; dockInformations->setChecked(!dock->isHidden()); guiActions->addAction(dockInformations->text(), dockInformations); } kdenliveCategoryMap.insert(QStringLiteral("interface"), guiActions); } QList MainWindow::getExtraActions(const QString &name) { if (!kdenliveCategoryMap.contains(name)) return QList (); return kdenliveCategoryMap.value(name)->actions(); } void MainWindow::slotTranscode(const QStringList &urls) { QString params; QString desc; QString condition; if (urls.isEmpty()) { QAction *action = qobject_cast(sender()); QStringList data = action->data().toStringList(); pCore->bin()->startClipJob(data); return; } if (urls.isEmpty()) { m_messageLabel->setMessage(i18n("No clip to transcode"), ErrorMessage); return; } ClipTranscode *d = new ClipTranscode(urls, params, QStringList(), desc); connect(d, SIGNAL(addClip(QUrl)), this, SLOT(slotAddProjectClip(QUrl))); d->show(); } void MainWindow::slotTranscodeClip() { QString allExtensions = ClipCreationDialog::getExtensions().join(QStringLiteral(" ")); const QString dialogFilter = i18n("All Supported Files") + '(' + allExtensions + ");;" + i18n("All Files") + "(*)"; QString clipFolder = KRecentDirs::dir(QStringLiteral(":KdenliveClipFolder")); QStringList urls = QFileDialog::getOpenFileNames(this, i18n("Files to transcode"), clipFolder, dialogFilter); if (urls.isEmpty()) return; slotTranscode(urls); } void MainWindow::slotSetDocumentRenderProfile(const QMap &props) { KdenliveDoc *project = pCore->projectManager()->current(); bool modified = false; QMapIterator i(props); while (i.hasNext()) { i.next(); if (project->getDocumentProperty(i.key()) == i.value()) continue; project->setDocumentProperty(i.key(), i.value()); modified = true; } if (modified) project->setModified(); } void MainWindow::slotPrepareRendering(bool scriptExport, bool zoneOnly, const QString &chapterFile) { KdenliveDoc *project = pCore->projectManager()->current(); if (m_renderWidget == NULL) return; QString scriptPath; QString playlistPath; QString mltSuffix(QStringLiteral(".mlt")); QList playlistPaths; QList trackNames; int tracksCount = 1; bool stemExport = m_renderWidget->isStemAudioExportEnabled(); if (scriptExport) { //QString scriptsFolder = project->projectFolder().path(QUrl::AddTrailingSlash) + "scripts/"; QString path = m_renderWidget->getFreeScriptName(project->url()); QPointer getUrl = new KUrlRequesterDialog(QUrl::fromLocalFile(path), i18n("Create Render Script"), this); getUrl->urlRequester()->setMode(KFile::File); if (getUrl->exec() == QDialog::Rejected) { delete getUrl; return; } scriptPath = getUrl->selectedUrl().path(); delete getUrl; QFile f(scriptPath); if (f.exists()) { if (KMessageBox::warningYesNo(this, i18n("Script file already exists. Do you want to overwrite it?")) != KMessageBox::Yes) return; } playlistPath = scriptPath; } else { QTemporaryFile temp(QDir::tempPath() + QStringLiteral("/kdenlive_rendering_XXXXXX.mlt")); temp.setAutoRemove(false); temp.open(); playlistPath = temp.fileName(); } QString playlistContent = pCore->projectManager()->projectSceneList(); if (!chapterFile.isEmpty()) { int in = 0; int out; if (!zoneOnly) out = (int) GenTime(project->projectDuration()).frames(project->fps()); else { in = pCore->projectManager()->currentTimeline()->inPoint(); out = pCore->projectManager()->currentTimeline()->outPoint(); } QDomDocument doc; QDomElement chapters = doc.createElement(QStringLiteral("chapters")); chapters.setAttribute(QStringLiteral("fps"), project->fps()); doc.appendChild(chapters); QMap guidesData = pCore->projectManager()->currentTimeline()->projectView()->guidesData(); QMapIterator g(guidesData); QLocale locale; while (g.hasNext()) { g.next(); int time = (int) GenTime(g.key()).frames(project->fps()); if (time >= in && time < out) { if (zoneOnly) time = time - in; QDomElement chapter = doc.createElement(QStringLiteral("chapter")); chapters.appendChild(chapter); chapter.setAttribute(QStringLiteral("title"), g.value()); chapter.setAttribute(QStringLiteral("time"), time); } } if (chapters.childNodes().count() > 0) { if (pCore->projectManager()->currentTimeline()->projectView()->hasGuide(out, 0) == -1) { // Always insert a guide in pos 0 QDomElement chapter = doc.createElement(QStringLiteral("chapter")); chapters.insertBefore(chapter, QDomNode()); chapter.setAttribute(QStringLiteral("title"), i18nc("the first in a list of chapters", "Start")); chapter.setAttribute(QStringLiteral("time"), QStringLiteral("0")); } // save chapters file QFile file(chapterFile); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qWarning() << "////// ERROR writing DVD CHAPTER file: " << chapterFile; } else { file.write(doc.toString().toUtf8()); if (file.error() != QFile::NoError) { qWarning() << "////// ERROR writing DVD CHAPTER file: " << chapterFile; } file.close(); } } } // check if audio export is selected bool exportAudio; if (m_renderWidget->automaticAudioExport()) { exportAudio = pCore->projectManager()->currentTimeline()->checkProjectAudio(); } else { exportAudio = m_renderWidget->selectedAudioExport(); } // Set playlist audio volume to 100% QDomDocument doc; doc.setContent(playlistContent); QDomElement tractor = doc.documentElement().firstChildElement(QStringLiteral("tractor")); if (!tractor.isNull()) { QDomNodeList props = tractor.elementsByTagName(QStringLiteral("property")); for (int i = 0; i < props.count(); ++i) { if (props.at(i).toElement().attribute(QStringLiteral("name")) == QLatin1String("meta.volume")) { props.at(i).firstChild().setNodeValue(QStringLiteral("1")); break; } } } // Add autoclose to playlists. QDomNodeList playlists = doc.elementsByTagName("playlist"); for (int i = 0; i < playlists.length();++i) { playlists.item(i).toElement().setAttribute("autoclose", 1); } // Do we want proxy rendering if (project->useProxy() && !m_renderWidget->proxyRendering()) { QString root = doc.documentElement().attribute(QStringLiteral("root")); // replace proxy clips with originals //TODO QMap proxies = pCore->binController()->getProxies(); QDomNodeList producers = doc.elementsByTagName(QStringLiteral("producer")); QString producerResource; QString producerService; QString suffix; QString prefix; for (int n = 0; n < producers.length(); ++n) { QDomElement e = producers.item(n).toElement(); producerResource = EffectsList::property(e, QStringLiteral("resource")); producerService = EffectsList::property(e, QStringLiteral("mlt_service")); if (producerResource.isEmpty()) { continue; } if (producerService == QLatin1String("timewarp")) { // slowmotion producer prefix = producerResource.section(':', 0, 0) + ":"; producerResource = producerResource.section(':', 1); } else { prefix.clear(); } if (producerService == QLatin1String("framebuffer")) { // slowmotion producer suffix = '?' + producerResource.section('?', 1); producerResource = producerResource.section('?', 0, 0); } else { suffix.clear(); } if (!producerResource.startsWith('/')) { producerResource.prepend(root + '/'); } if (!producerResource.isEmpty()) { if (proxies.contains(producerResource)) { QString replacementResource = proxies.value(producerResource); EffectsList::setProperty(e, QStringLiteral("resource"), prefix + replacementResource + suffix); if (producerService == QLatin1String("timewarp")) { EffectsList::setProperty(e, QStringLiteral("warp_resource"), replacementResource); } // We need to delete the "aspect_ratio" property because proxy clips // sometimes have different ratio than original clips EffectsList::removeProperty(e, QStringLiteral("aspect_ratio")); EffectsList::removeMetaProperties(e); } } } } QList docList; // check which audio tracks have to be exported if (stemExport) { Timeline* ct = pCore->projectManager()->currentTimeline(); int allTracksCount = ct->tracksCount(); // reset tracks count (tracks to be rendered) tracksCount = 0; // begin with track 1 (track zero is a hidden black track) for (int i = 1; i < allTracksCount; i++) { Track* track = ct->track(i); // add only tracks to render list that are not muted and have audio if (track && !track->info().isMute && track->hasAudio()) { QDomDocument docCopy = doc.cloneNode(true).toDocument(); QString trackName = track->info().trackName; // save track name trackNames << trackName; qDebug() << "Track-Name: " << trackName; // create stem export doc content QDomNodeList tracks = docCopy.elementsByTagName(QStringLiteral("track")); for (int j = 0; j < allTracksCount; j++) { if (j != i) { // mute other tracks tracks.at(j).toElement().setAttribute(QStringLiteral("hide"), QStringLiteral("both")); } } docList << docCopy; tracksCount++; } } } else { docList << doc; } // create full playlistPaths for (int i = 0; i < tracksCount; i++) { QString plPath(playlistPath); // add track number to path name if (stemExport) { plPath = plPath + "_" + QString(trackNames.at(i)).replace(QLatin1String(" "), QLatin1String("_")); } // add mlt suffix plPath += mltSuffix; playlistPaths << plPath; qDebug() << "playlistPath: " << plPath << endl; // Do save scenelist QFile file(plPath); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { m_messageLabel->setMessage(i18n("Cannot write to file %1", plPath), ErrorMessage); return; } file.write(docList.at(i).toString().toUtf8()); if (file.error() != QFile::NoError) { m_messageLabel->setMessage(i18n("Cannot write to file %1", plPath), ErrorMessage); file.close(); return; } file.close(); } m_renderWidget->slotExport(scriptExport, pCore->projectManager()->currentTimeline()->inPoint(), pCore->projectManager()->currentTimeline()->outPoint(), project->metadata(), playlistPaths, trackNames, scriptPath, exportAudio); } void MainWindow::slotUpdateTimecodeFormat(int ix) { KdenliveSettings::setFrametimecode(ix == 1); m_clipMonitor->updateTimecodeFormat(); m_projectMonitor->updateTimecodeFormat(); m_effectStack->transitionConfig()->updateTimecodeFormat(); m_effectStack->updateTimecodeFormat(); pCore->bin()->updateTimecodeFormat(); //pCore->projectManager()->currentTimeline()->projectView()->clearSelection(); pCore->projectManager()->currentTimeline()->updateRuler(); slotUpdateMousePosition(pCore->projectManager()->currentTimeline()->projectView()->getMousePos()); } void MainWindow::slotRemoveFocus() { pCore->projectManager()->currentTimeline()->setFocus(); } void MainWindow::slotShutdown() { pCore->projectManager()->current()->setModified(false); // Call shutdown QDBusConnectionInterface* interface = QDBusConnection::sessionBus().interface(); if (interface && interface->isServiceRegistered(QStringLiteral("org.kde.ksmserver"))) { QDBusInterface smserver(QStringLiteral("org.kde.ksmserver"), QStringLiteral("/KSMServer"), QStringLiteral("org.kde.KSMServerInterface")); smserver.call(QStringLiteral("logout"), 1, 2, 2); } else if (interface && interface->isServiceRegistered(QStringLiteral("org.gnome.SessionManager"))) { QDBusInterface smserver(QStringLiteral("org.gnome.SessionManager"), QStringLiteral("/org/gnome/SessionManager"), QStringLiteral("org.gnome.SessionManager")); smserver.call(QStringLiteral("Shutdown")); } } void MainWindow::slotUpdateTrackInfo() { m_effectStack->transitionConfig()->updateProjectFormat(); } void MainWindow::slotSwitchMonitors() { pCore->monitorManager()->slotSwitchMonitors(!m_clipMonitor->isActive()); if (m_projectMonitor->isActive()) pCore->projectManager()->currentTimeline()->projectView()->setFocus(); else pCore->bin()->focusBinView(); } void MainWindow::slotSwitchMonitorOverlay(QAction *action) { if (pCore->monitorManager()->isActive(Kdenlive::ClipMonitor)) { m_clipMonitor->switchMonitorInfo(action->data().toInt()); } else { m_projectMonitor->switchMonitorInfo(action->data().toInt()); } } void MainWindow::slotSwitchDropFrames(bool drop) { m_clipMonitor->switchDropFrames(drop); m_projectMonitor->switchDropFrames(drop); } void MainWindow::slotSetMonitorGamma(int gamma) { KdenliveSettings::setMonitor_gamma(gamma); m_clipMonitor->updateMonitorGamma(); m_projectMonitor->updateMonitorGamma(); } void MainWindow::slotInsertZoneToTree() { if (!m_clipMonitor->isActive() || m_clipMonitor->currentController() == NULL) return; QPoint info = m_clipMonitor->getZoneInfo(); pCore->bin()->slotAddClipCut(m_clipMonitor->activeClipId(), info.x(), info.y()); } void MainWindow::slotInsertZoneToTimeline() { if (pCore->projectManager()->currentTimeline() == NULL || m_clipMonitor->currentController() == NULL) return; QPoint info = m_clipMonitor->getZoneInfo(); pCore->projectManager()->currentTimeline()->projectView()->insertClipCut(m_clipMonitor->activeClipId(), info.x(), info.y()); } void MainWindow::slotMonitorRequestRenderFrame(bool request) { if (request) { m_projectMonitor->render->sendFrameForAnalysis = true; return; } else { for (int i = 0; i < m_gfxScopesList.count(); ++i) { if (m_gfxScopesList.at(i)->isVisible() && tabifiedDockWidgets(m_gfxScopesList.at(i)).isEmpty() && static_cast(m_gfxScopesList.at(i)->widget())->autoRefreshEnabled()) { request = true; break; } } } #ifdef DEBUG_MAINW qDebug() << "Any scope accepting new frames? " << request; #endif if (!request) { m_projectMonitor->render->sendFrameForAnalysis = false; } } void MainWindow::slotOpenStopmotion() { if (m_stopmotion == NULL) { m_stopmotion = new StopmotionWidget(pCore->monitorManager(), pCore->projectManager()->current()->projectFolder(), m_stopmotion_actions->actions(), this); //TODO //connect(m_stopmotion, SIGNAL(addOrUpdateSequence(QString)), m_projectList, SLOT(slotAddOrUpdateSequence(QString))); //for (int i = 0; i < m_gfxScopesList.count(); ++i) { // Check if we need the renderer to send a new frame for update /*if (!m_scopesList.at(i)->widget()->visibleRegion().isEmpty() && !(static_cast(m_scopesList.at(i)->widget())->autoRefreshEnabled())) request = true;*/ //connect(m_stopmotion, SIGNAL(gotFrame(QImage)), static_cast(m_gfxScopesList.at(i)->widget()), SLOT(slotRenderZoneUpdated(QImage))); //static_cast(m_scopesList.at(i)->widget())->slotMonitorCapture(); //} } m_stopmotion->show(); } void MainWindow::slotUpdateProxySettings() { KdenliveDoc *project = pCore->projectManager()->current(); if (m_renderWidget) m_renderWidget->updateProxyConfig(project->useProxy()); if (KdenliveSettings::enableproxy()) { QDir dir(pCore->projectManager()->current()->projectFolder().path()); dir.mkdir(QStringLiteral("proxy")); } pCore->bin()->refreshProxySettings(); } void MainWindow::slotArchiveProject() { QList list = pCore->binController()->getControllerList(); pCore->binController()->saveDocumentProperties(pCore->projectManager()->currentTimeline()->documentProperties(), pCore->projectManager()->current()->metadata(), pCore->projectManager()->currentTimeline()->projectView()->guidesData()); QDomDocument doc = pCore->projectManager()->current()->xmlSceneList(m_projectMonitor->sceneList()); QPointer d = new ArchiveWidget(pCore->projectManager()->current()->url().fileName(), doc, list, pCore->projectManager()->currentTimeline()->projectView()->extractTransitionsLumas(), this); if (d->exec()) { m_messageLabel->setMessage(i18n("Archiving project"), OperationCompletedMessage); } delete d; } void MainWindow::slotDownloadResources() { QString currentFolder; if (pCore->projectManager()->current()) currentFolder = pCore->projectManager()->current()->projectFolder().path(); else currentFolder = KdenliveSettings::defaultprojectfolder(); ResourceWidget *d = new ResourceWidget(currentFolder); connect(d, SIGNAL(addClip(QUrl)), this, SLOT(slotAddProjectClip(QUrl))); d->show(); } void MainWindow::slotProcessImportKeyframes(GraphicsRectItem type, const QString &tag, const QString& data) { if (type == AVWidget) { // This data should be sent to the effect stack m_effectStack->setKeyframes(tag, data); } else if (type == TransitionWidget) { // This data should be sent to the transition stack m_effectStack->transitionConfig()->setKeyframes(tag, data); } else { // Error } } void MainWindow::slotAlignPlayheadToMousePos() { pCore->monitorManager()->activateMonitor(Kdenlive::ProjectMonitor); pCore->projectManager()->currentTimeline()->projectView()->slotAlignPlayheadToMousePos(); } void MainWindow::triggerKey(QKeyEvent* ev) { // Hack: The QQuickWindow that displays fullscreen monitor does not integrate quith QActions. // so on keypress events we parse keys and check for shortcuts in all existing actions QKeySequence seq; // Remove the Num modifier or some shortcuts like "*" will not work if (ev->modifiers() != Qt::KeypadModifier) { seq = QKeySequence(ev->key() + ev->modifiers()); } else { seq = QKeySequence(ev->key()); } QList< KActionCollection * > collections = KActionCollection::allCollections(); for (int i = 0; i < collections.count(); ++i) { KActionCollection *coll = collections.at(i); foreach( QAction* tempAction, coll->actions()) { if (tempAction->shortcuts().contains(seq)) { // Trigger action tempAction->trigger(); ev->accept(); return; } } } } QDockWidget *MainWindow::addDock(const QString &title, const QString &objectName, QWidget* widget, Qt::DockWidgetArea area) { QDockWidget *dockWidget = new QDockWidget(title, this); dockWidget->setObjectName(objectName); dockWidget->setWidget(widget); addDockWidget(area, dockWidget); connect(dockWidget, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)), this, SLOT(updateDockTitleBars())); return dockWidget; } void MainWindow::slotUpdateMonitorOverlays(int id, int code) { QMenu *monitorOverlay = static_cast(factory()->container(QStringLiteral("monitor_config_overlay"), this)); if (!monitorOverlay) return; QList actions = monitorOverlay->actions(); foreach(QAction *ac, actions) { int data = ac->data().toInt(); if (data == 0x010) { ac->setEnabled(id == Kdenlive::ClipMonitor); } ac->setChecked(code & data); } } void MainWindow::slotChangeStyle(QAction *a) { QString style = a->data().toString(); KdenliveSettings::setWidgetstyle(style); doChangeStyle(); } void MainWindow::doChangeStyle() { QString newStyle = KdenliveSettings::widgetstyle(); if (newStyle.isEmpty() || newStyle == QStringLiteral("Default")) { newStyle = defaultStyle("Breeze"); } QApplication::setStyle(QStyleFactory::create(newStyle)); // Changing widget style resets color theme, so update color theme again ThemeManager::instance()->slotChangePalette(); } bool MainWindow::isTabbedWith(QDockWidget *widget, const QString & otherWidget) { QList tabbed = tabifiedDockWidgets(widget); for (int i = 0; i < tabbed.count(); i++) { if (tabbed.at(i)->objectName() == otherWidget) return true; } return false; } void MainWindow::updateDockTitleBars() { #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) QList docks = pCore->window()->findChildren(); for (int i = 0; i < docks.count(); ++i) { QDockWidget* dock = docks.at(i); if (dock->isFloating() || tabifiedDockWidgets(dock).isEmpty()) { QWidget *bar = dock->titleBarWidget(); if (bar) { dock->setTitleBarWidget(0); delete bar; } } else { dock->setTitleBarWidget(new QWidget); } } #endif } void MainWindow::slotToggleAutoPreview(bool enable) { KdenliveSettings::setAutopreview(enable); if (enable && pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->startPreviewRender(); } #ifdef DEBUG_MAINW #undef DEBUG_MAINW #endif diff --git a/src/timeline/customruler.cpp b/src/timeline/customruler.cpp index 8c1214468..c7b4d79ac 100644 --- a/src/timeline/customruler.cpp +++ b/src/timeline/customruler.cpp @@ -1,572 +1,573 @@ /*************************************************************************** * 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 "customruler.h" #include "kdenlivesettings.h" #include #include #include #include #include #include #include #include #include static int MAX_HEIGHT; // Width of a frame in pixels static int FRAME_SIZE; // Height of the timecode text static int LABEL_SIZE; // Width of a letter, used for cursor width static int FONT_WIDTH; static int BIG_MARK_X; static int MIDDLE_MARK_X; static int LITTLE_MARK_X; static int littleMarkDistance; static int mediumMarkDistance; static int bigMarkDistance; #define SEEK_INACTIVE (-1) #include "definitions.h" const int CustomRuler::comboScale[] = { 1, 2, 5, 10, 25, 50, 125, 250, 500, 750, 1500, 3000, 6000, 12000}; CustomRuler::CustomRuler(const Timecode &tc, const QList &rulerActions, CustomTrackView *parent) : QWidget(parent), m_timecode(tc), m_view(parent), m_duration(0), m_offset(0), m_headPosition(SEEK_INACTIVE), m_clickedGuide(-1), m_rate(-1), m_mouseMove(NO_MOVE) { setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); QFontMetricsF fontMetrics(font()); // Define size variables LABEL_SIZE = fontMetrics.ascent(); FONT_WIDTH = fontMetrics.averageCharWidth(); setMinimumHeight(LABEL_SIZE * 2); setMaximumHeight(LABEL_SIZE * 2); MAX_HEIGHT = height(); BIG_MARK_X = LABEL_SIZE + 1; int mark_length = MAX_HEIGHT - BIG_MARK_X; MIDDLE_MARK_X = BIG_MARK_X + mark_length / 2; LITTLE_MARK_X = BIG_MARK_X + mark_length / 3; updateFrameSize(); m_scale = 3; m_zoneStart = 0; m_zoneEnd = 100; m_contextMenu = new QMenu(this); m_contextMenu->addActions(rulerActions); QAction *addGuide = m_contextMenu->addAction(QIcon::fromTheme(QStringLiteral("document-new")), i18n("Add Guide")); connect(addGuide, SIGNAL(triggered()), m_view, SLOT(slotAddGuide())); m_editGuide = m_contextMenu->addAction(QIcon::fromTheme(QStringLiteral("document-properties")), i18n("Edit Guide")); connect(m_editGuide, SIGNAL(triggered()), this, SLOT(slotEditGuide())); m_deleteGuide = m_contextMenu->addAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18n("Delete Guide")); connect(m_deleteGuide , SIGNAL(triggered()), this, SLOT(slotDeleteGuide())); QAction *delAllGuides = m_contextMenu->addAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18n("Delete All Guides")); connect(delAllGuides, SIGNAL(triggered()), m_view, SLOT(slotDeleteAllGuides())); m_goMenu = m_contextMenu->addMenu(i18n("Go To")); connect(m_goMenu, SIGNAL(triggered(QAction*)), this, SLOT(slotGoToGuide(QAction*))); setMouseTracking(true); m_zoneBG = palette().color(QPalette::Highlight); m_zoneBG.setAlpha(KdenliveSettings::useTimelineZoneToEdit() ? 180 : 60); } void CustomRuler::updateProjectFps(const Timecode &t) { m_timecode = t; mediumMarkDistance = FRAME_SIZE * m_timecode.fps(); bigMarkDistance = FRAME_SIZE * m_timecode.fps() * 60; setPixelPerMark(m_rate); update(); } void CustomRuler::updateFrameSize() { FRAME_SIZE = m_view->getFrameWidth(); littleMarkDistance = FRAME_SIZE; mediumMarkDistance = FRAME_SIZE * m_timecode.fps(); bigMarkDistance = FRAME_SIZE * m_timecode.fps() * 60; updateProjectFps(m_timecode); if (m_rate > 0) setPixelPerMark(m_rate); } void CustomRuler::slotEditGuide() { m_view->slotEditGuide(m_clickedGuide); } void CustomRuler::slotDeleteGuide() { m_view->slotDeleteGuide(m_clickedGuide); } void CustomRuler::slotGoToGuide(QAction *act) { m_view->seekCursorPos(act->data().toInt()); m_view->initCursorPos(act->data().toInt()); } void CustomRuler::setZone(const QPoint &p) { m_zoneStart = p.x(); m_zoneEnd = p.y(); update(); } void CustomRuler::mouseReleaseEvent(QMouseEvent * /*event*/) { if (m_moveCursor == RULER_START || m_moveCursor == RULER_END || m_moveCursor == RULER_MIDDLE) { emit zoneMoved(m_zoneStart, m_zoneEnd); m_view->setDocumentModified(); } m_mouseMove = NO_MOVE; } // virtual void CustomRuler::mousePressEvent(QMouseEvent * event) { int pos = (int)((event->x() + offset())); if (event->button() == Qt::RightButton) { m_clickedGuide = m_view->hasGuide((int)(pos / m_factor), (int)(5 / m_factor + 1)); m_editGuide->setEnabled(m_clickedGuide > 0); m_deleteGuide->setEnabled(m_clickedGuide > 0); m_view->buildGuidesMenu(m_goMenu); m_contextMenu->exec(event->globalPos()); return; } setFocus(Qt::MouseFocusReason); m_view->activateMonitor(); m_moveCursor = RULER_CURSOR; if (event->y() > 10) { if (qAbs(pos - m_zoneStart * m_factor) < 4) m_moveCursor = RULER_START; else if (qAbs(pos - (m_zoneStart + (m_zoneEnd - m_zoneStart) / 2.0) * m_factor) < 4) m_moveCursor = RULER_MIDDLE; else if (qAbs(pos - (m_zoneEnd + 1)* m_factor) < 4) m_moveCursor = RULER_END; m_view->updateSnapPoints(NULL); } if (m_moveCursor == RULER_CURSOR) { m_view->seekCursorPos((int) pos / m_factor); m_clickPoint = event->pos(); m_startRate = m_rate; } } // virtual void CustomRuler::mouseMoveEvent(QMouseEvent * event) { int mappedXPos = (int)((event->x() + offset()) / m_factor); emit mousePosition(mappedXPos); if (event->buttons() == Qt::LeftButton) { int pos; if (m_moveCursor == RULER_START || m_moveCursor == RULER_END) { pos = m_view->getSnapPointForPos(mappedXPos); } else pos = mappedXPos; int zoneStart = m_zoneStart; int zoneEnd = m_zoneEnd; if (pos < 0) pos = 0; if (m_moveCursor == RULER_CURSOR) { QPoint diff = event->pos() - m_clickPoint; if (m_mouseMove == NO_MOVE) { if (qAbs(diff.x()) >= QApplication::startDragDistance()) { m_mouseMove = HORIZONTAL_MOVE; } else if (KdenliveSettings::verticalzoom() && qAbs(diff.y()) >= QApplication::startDragDistance()) { m_mouseMove = VERTICAL_MOVE; } else return; } if (m_mouseMove == HORIZONTAL_MOVE) { if (pos != m_headPosition && pos != m_view->cursorPos()) { int x = m_headPosition == SEEK_INACTIVE ? pos : m_headPosition; m_headPosition = pos; int min = qMin(x, m_headPosition); int max = qMax(x, m_headPosition); update(min * m_factor - offset() - 3, BIG_MARK_X, (max - min) * m_factor + 6, MAX_HEIGHT - BIG_MARK_X); emit seekCursorPos(pos); m_view->slotCheckPositionScrolling(); } } else { int verticalDiff = m_startRate - (diff.y()) / 7; if (verticalDiff != m_rate) emit adjustZoom(verticalDiff); } return; } else if (m_moveCursor == RULER_START) m_zoneStart = qMin(pos, m_zoneEnd); else if (m_moveCursor == RULER_END) m_zoneEnd = qMax(pos, m_zoneStart); else if (m_moveCursor == RULER_MIDDLE) { int move = pos - (m_zoneStart + (m_zoneEnd - m_zoneStart) / 2); if (move + m_zoneStart < 0) move = - m_zoneStart; m_zoneStart += move; m_zoneEnd += move; } int min = qMin(m_zoneStart, zoneStart); int max = qMax(m_zoneEnd, zoneEnd); update(min * m_factor - m_offset - 2, 0, (max - min + 1) * m_factor + 4, height()); } else { int pos = (int)((event->x() + m_offset)); if (event->y() <= 10) { setCursor(Qt::ArrowCursor); } else if (qAbs(pos - m_zoneStart * m_factor) < 4) { setCursor(QCursor(Qt::SizeHorCursor)); if (KdenliveSettings::frametimecode()) setToolTip(i18n("Zone start: %1", m_zoneStart)); else setToolTip(i18n("Zone start: %1", m_timecode.getTimecodeFromFrames(m_zoneStart))); } else if (qAbs(pos - (m_zoneEnd + 1) * m_factor) < 4) { setCursor(QCursor(Qt::SizeHorCursor)); if (KdenliveSettings::frametimecode()) setToolTip(i18n("Zone end: %1", m_zoneEnd)); else setToolTip(i18n("Zone end: %1", m_timecode.getTimecodeFromFrames(m_zoneEnd))); } else if (qAbs(pos - (m_zoneStart + (m_zoneEnd - m_zoneStart) / 2.0) * m_factor) < 4) { setCursor(Qt::SizeHorCursor); if (KdenliveSettings::frametimecode()) setToolTip(i18n("Zone duration: %1", m_zoneEnd - m_zoneStart)); else setToolTip(i18n("Zone duration: %1", m_timecode.getTimecodeFromFrames(m_zoneEnd - m_zoneStart))); } else { setCursor(Qt::ArrowCursor); if (KdenliveSettings::frametimecode()) setToolTip(i18n("Position: %1", (int)(pos / m_factor))); else setToolTip(i18n("Position: %1", m_timecode.getTimecodeFromFrames(pos / m_factor))); } } } // virtual void CustomRuler::wheelEvent(QWheelEvent * e) { int delta = 1; m_view->activateMonitor(); if (e->modifiers() == Qt::ControlModifier) delta = m_timecode.fps(); if (e->delta() < 0) delta = 0 - delta; m_view->moveCursorPos(delta); } int CustomRuler::inPoint() const { return m_zoneStart; } int CustomRuler::outPoint() const { return m_zoneEnd; } void CustomRuler::slotMoveRuler(int newPos) { if (m_offset != newPos) { m_offset = newPos; update(); } } int CustomRuler::offset() const { return m_offset; } void CustomRuler::slotCursorMoved(int oldpos, int newpos) { int min = qMin(oldpos, newpos); int max = qMax(oldpos, newpos); m_headPosition = newpos; update(min * m_factor - m_offset - FONT_WIDTH, BIG_MARK_X, (max - min) * m_factor + FONT_WIDTH * 2 + 2, MAX_HEIGHT - BIG_MARK_X); } void CustomRuler::updateRuler(int pos) { int x = m_headPosition; m_headPosition = pos; if (x == SEEK_INACTIVE) x = pos; int min = qMin(x, m_headPosition); int max = qMax(x, m_headPosition); update(min * m_factor - offset() - 3, BIG_MARK_X, (max - min) * m_factor + 6, MAX_HEIGHT - BIG_MARK_X); } void CustomRuler::setPixelPerMark(int rate, bool force) { if (rate < 0 || (rate == m_rate && !force)) return; int scale = comboScale[rate]; m_rate = rate; m_factor = 1.0 / (double) scale * FRAME_SIZE; m_scale = 1.0 / (double) scale; double fend = m_scale * littleMarkDistance; int textFactor = 1; int timeLabelSize = QWidget::fontMetrics().boundingRect(QStringLiteral("00:00:00:000")).width(); if (timeLabelSize > littleMarkDistance) { textFactor = timeLabelSize / littleMarkDistance + 1; } if (rate > 8) { mediumMarkDistance = (double) FRAME_SIZE * m_timecode.fps() * 60; bigMarkDistance = (double) FRAME_SIZE * m_timecode.fps() * 300; } else if (rate > 6) { mediumMarkDistance = (double) FRAME_SIZE * m_timecode.fps() * 10; bigMarkDistance = (double) FRAME_SIZE * m_timecode.fps() * 30; } else if (rate > 3) { mediumMarkDistance = (double) FRAME_SIZE * m_timecode.fps(); bigMarkDistance = (double) FRAME_SIZE * m_timecode.fps() * 5; } else { mediumMarkDistance = (double) FRAME_SIZE * m_timecode.fps(); bigMarkDistance = (double) FRAME_SIZE * m_timecode.fps() * 60; } m_textSpacing = fend * textFactor; if (m_textSpacing < timeLabelSize) { int roundedFps = (int) (m_timecode.fps() + 0.5); int factor = timeLabelSize / m_textSpacing; if (factor < 2) { m_textSpacing *= 2; } else if (factor < 5) { m_textSpacing *= 5; } else if (factor < 10) { m_textSpacing *= 10; } else if (factor < roundedFps) { m_textSpacing *= roundedFps; } else if (factor < 2 * roundedFps) { m_textSpacing *= 2 * roundedFps; } else if (factor < 5 * roundedFps) { m_textSpacing *= 5 * roundedFps; } else if (factor < 10 * roundedFps) { m_textSpacing *= 10 * roundedFps; } else if (factor < 20 * roundedFps) { m_textSpacing *= 20 * roundedFps; } else if (factor < 60 * roundedFps) { m_textSpacing *= 60 * roundedFps; } else if (factor < 120 * roundedFps) { m_textSpacing *= 120 * roundedFps; } else if (factor < 150 * roundedFps) { m_textSpacing *= 150 * roundedFps; } else if (factor < 300 * roundedFps) { m_textSpacing *= 300 * roundedFps; } else { factor /= (300 * roundedFps); m_textSpacing *= (factor + 1) * (300 * roundedFps); } } update(); } void CustomRuler::setDuration(int d) { int oldduration = m_duration; m_duration = d; update(qMin(oldduration, m_duration) * m_factor - 1 - offset(), 0, qAbs(oldduration - m_duration) * m_factor + 2, height()); } // virtual void CustomRuler::paintEvent(QPaintEvent *e) { QStylePainter p(this); const QRect &paintRect = e->rect(); p.setClipRect(paintRect); p.fillRect(paintRect, palette().midlight().color()); // Draw zone background const int zoneStart = (int)(m_zoneStart * m_factor); const int zoneEnd = (int)((m_zoneEnd + 1)* m_factor); p.fillRect(zoneStart - m_offset, LABEL_SIZE + 2, zoneEnd - zoneStart, MAX_HEIGHT - LABEL_SIZE - 2, m_zoneBG); double f, fend; const int offsetmax = ((paintRect.right() + m_offset) / FRAME_SIZE + 1) * FRAME_SIZE; int offsetmin; p.setPen(palette().text().color()); // draw time labels if (paintRect.y() < LABEL_SIZE) { offsetmin = (paintRect.left() + m_offset) / m_textSpacing; offsetmin = offsetmin * m_textSpacing; for (f = offsetmin; f < offsetmax; f += m_textSpacing) { QString lab; if (KdenliveSettings::frametimecode()) lab = QString::number((int)(f / m_factor + 0.5)); else lab = m_timecode.getTimecodeFromFrames((int)(f / m_factor + 0.5)); p.drawText(f - m_offset + 2, LABEL_SIZE, lab); } } p.setPen(palette().dark().color()); offsetmin = (paintRect.left() + m_offset) / littleMarkDistance; offsetmin = offsetmin * littleMarkDistance; // draw the little marks fend = m_scale * littleMarkDistance; if (fend > 5) { QLineF l(offsetmin - m_offset, LITTLE_MARK_X, offsetmin - m_offset, MAX_HEIGHT); for (f = offsetmin; f < offsetmax; f += fend) { l.translate(fend, 0); p.drawLine(l); } } offsetmin = (paintRect.left() + m_offset) / mediumMarkDistance; offsetmin = offsetmin * mediumMarkDistance; // draw medium marks fend = m_scale * mediumMarkDistance; if (fend > 5) { QLineF l(offsetmin - m_offset - fend, MIDDLE_MARK_X, offsetmin - m_offset - fend, MAX_HEIGHT); for (f = offsetmin - fend; f < offsetmax + fend; f += fend) { l.translate(fend, 0); p.drawLine(l); } } offsetmin = (paintRect.left() + m_offset) / bigMarkDistance; offsetmin = offsetmin * bigMarkDistance; // draw big marks fend = m_scale * bigMarkDistance; if (fend > 5) { QLineF l(offsetmin - m_offset, BIG_MARK_X, offsetmin - m_offset, MAX_HEIGHT); for (f = offsetmin; f < offsetmax; f += fend) { l.translate(fend, 0); p.drawLine(l); } } // draw zone cursors if (zoneStart > 0) { QPolygon pa(4); pa.setPoints(4, zoneStart - m_offset + FONT_WIDTH / 2, LABEL_SIZE + 2, zoneStart - m_offset, LABEL_SIZE + 2, zoneStart - m_offset, MAX_HEIGHT - 1, zoneStart - m_offset + FONT_WIDTH / 2, MAX_HEIGHT - 1); p.drawPolyline(pa); } if (zoneEnd > 0) { QColor center(Qt::white); center.setAlpha(150); QRect rec(zoneStart - m_offset + (zoneEnd - zoneStart) / 2 - 4, LABEL_SIZE + 2, 8, MAX_HEIGHT - LABEL_SIZE - 3); p.fillRect(rec, center); p.drawRect(rec); QPolygon pa(4); pa.setPoints(4, zoneEnd - m_offset - FONT_WIDTH / 2, LABEL_SIZE + 2, zoneEnd - m_offset, LABEL_SIZE + 2, zoneEnd - m_offset, MAX_HEIGHT - 1, zoneEnd - m_offset - FONT_WIDTH / 2, MAX_HEIGHT - 1); p.drawPolyline(pa); } // draw Rendering preview zones QColor preview(Qt::green); preview.setAlpha(120); foreach(int frame, m_renderingPreviews) { QRectF rec(frame * m_factor - m_offset, MAX_HEIGHT - 3, KdenliveSettings::timelinechunks() * m_factor, 3); p.fillRect(rec, preview); } preview = Qt::darkRed; preview.setAlpha(120); foreach(int frame, m_dirtyRenderingPreviews) { QRectF rec(frame * m_factor - m_offset, MAX_HEIGHT - 3, KdenliveSettings::timelinechunks() * m_factor, 3); p.fillRect(rec, preview); } // draw pointer const int value = m_view->cursorPos() * m_factor - m_offset; QPolygon pa(3); pa.setPoints(3, value - FONT_WIDTH, LABEL_SIZE + 3, value + FONT_WIDTH, LABEL_SIZE + 3, value, MAX_HEIGHT); p.setBrush(palette().brush(QPalette::Text)); p.setPen(Qt::NoPen); p.drawPolygon(pa); if (m_headPosition == m_view->cursorPos()) { m_headPosition = SEEK_INACTIVE; } if (m_headPosition != SEEK_INACTIVE) { p.fillRect(m_headPosition * m_factor - m_offset - 1, BIG_MARK_X + 1, 3, MAX_HEIGHT - BIG_MARK_X - 1, palette().linkVisited()); } } void CustomRuler::activateZone() { m_zoneBG.setAlpha(KdenliveSettings::useTimelineZoneToEdit() ? 180 : 60); update(); } -void CustomRuler::updatePreview(int frame, bool rendered) +void CustomRuler::updatePreview(int frame, bool rendered, bool refresh) { if (rendered) { m_renderingPreviews << frame; m_dirtyRenderingPreviews.removeAll(frame); } else { m_renderingPreviews.removeAll(frame); m_dirtyRenderingPreviews << frame; } - update(frame * m_factor - offset(), MAX_HEIGHT - 3, KdenliveSettings::timelinechunks() * m_factor + 1, 3); + if (refresh) + update(frame * m_factor - offset(), MAX_HEIGHT - 3, KdenliveSettings::timelinechunks() * m_factor + 1, 3); } const QStringList CustomRuler::previewChunks() const { QStringList resultChunks; QString clean; QString dirty; foreach(int frame, m_renderingPreviews) { clean += QString::number(frame) + QStringLiteral(","); } foreach(int frame, m_dirtyRenderingPreviews) { dirty += QString::number(frame) + QStringLiteral(","); } resultChunks << clean << dirty; return resultChunks; } const QList CustomRuler::getDirtyChunks() const { return m_dirtyRenderingPreviews; } bool CustomRuler::hasPreviewRange() const { return (!m_dirtyRenderingPreviews.isEmpty() || !m_renderingPreviews.isEmpty()); } void CustomRuler::addChunks(QList chunks, bool add) { qSort(chunks); if (add) { foreach(int frame, chunks) { if (m_renderingPreviews.contains(frame)) { // already rendered, ignore continue; } if (m_dirtyRenderingPreviews.contains(frame)) { continue; } m_dirtyRenderingPreviews << frame; } } else { foreach(int frame, chunks) { m_renderingPreviews.removeAll(frame); m_dirtyRenderingPreviews.removeAll(frame); } } update(chunks.first() * m_factor - offset(), MAX_HEIGHT - 3, (chunks.last() - chunks.first()) * KdenliveSettings::timelinechunks() * m_factor + 1, 3); } diff --git a/src/timeline/customruler.h b/src/timeline/customruler.h index 553b8d59b..81db9a1a0 100644 --- a/src/timeline/customruler.h +++ b/src/timeline/customruler.h @@ -1,113 +1,113 @@ /*************************************************************************** * 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 * ***************************************************************************/ /** * @class CustomRuler * @author Jean-Baptiste Mardelle * @brief Manages the timeline ruler. */ #ifndef CUSTOMRULER_H #define CUSTOMRULER_H #include #include "timeline/customtrackview.h" #include "timecode.h" enum RULER_MOVE { RULER_CURSOR = 0, RULER_START = 1, RULER_MIDDLE = 2, RULER_END = 3 }; enum MOUSE_MOVE { NO_MOVE = 0, HORIZONTAL_MOVE = 1, VERTICAL_MOVE = 2 }; class CustomRuler : public QWidget { Q_OBJECT public: CustomRuler(const Timecode &tc, const QList &rulerActions, CustomTrackView *parent); void setPixelPerMark(int rate, bool force = false); static const int comboScale[]; int outPoint() const; int inPoint() const; void setDuration(int d); void setZone(const QPoint &p); int offset() const; void updateProjectFps(const Timecode &t); void updateFrameSize(); void activateZone(); - void updatePreview(int frame, bool rendered = true); + void updatePreview(int frame, bool rendered = true, bool refresh = true); /** @brief Returns a list of rendered timeline preview chunks */ const QStringList previewChunks() const; /** @brief Returns a list of dirty timeline preview chunks (that need to be generated) */ const QList getDirtyChunks() const; void addChunks(QList chunks, bool add); /** @brief Returns true if a timeline preview zone has already be defined */ bool hasPreviewRange() const; protected: void paintEvent(QPaintEvent * /*e*/); void wheelEvent(QWheelEvent * e); void mousePressEvent(QMouseEvent * event); void mouseReleaseEvent(QMouseEvent * event); void mouseMoveEvent(QMouseEvent * event); private: Timecode m_timecode; CustomTrackView *m_view; int m_zoneStart; int m_zoneEnd; int m_duration; double m_textSpacing; double m_factor; double m_scale; int m_offset; /** @brief the position of the seek point */ int m_headPosition; QColor m_zoneBG; RULER_MOVE m_moveCursor; QMenu *m_contextMenu; QAction *m_editGuide; QAction *m_deleteGuide; int m_clickedGuide; /** Used for zooming through vertical move */ QPoint m_clickPoint; int m_rate; int m_startRate; MOUSE_MOVE m_mouseMove; QMenu *m_goMenu; QList m_renderingPreviews; QList m_dirtyRenderingPreviews; public slots: void slotMoveRuler(int newPos); void slotCursorMoved(int oldpos, int newpos); void updateRuler(int pos); private slots: void slotEditGuide(); void slotDeleteGuide(); void slotGoToGuide(QAction *act); signals: void zoneMoved(int, int); void adjustZoom(int); void mousePosition(int); void seekCursorPos(int); }; #endif diff --git a/src/timeline/timeline.cpp b/src/timeline/timeline.cpp index 3c57f2a1c..8666c3c6a 100644 --- a/src/timeline/timeline.cpp +++ b/src/timeline/timeline.cpp @@ -1,1895 +1,1939 @@ /*************************************************************************** * Copyright (C) 2007 by Jean-Baptiste Mardelle (jb@kdenlive.org) * * Copyright (C) 2015 by Vincent Pinon (vpinon@kde.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 "timeline.h" #include "track.h" #include "clip.h" #include "renderer.h" #include "headertrack.h" #include "clipitem.h" #include "transition.h" #include "transitionhandler.h" #include "timelinecommands.h" #include "customruler.h" #include "customtrackview.h" #include "dialogs/profilesdialog.h" #include "mltcontroller/clipcontroller.h" #include "bin/projectclip.h" #include "kdenlivesettings.h" #include "mainwindow.h" #include "doc/kdenlivedoc.h" #include "utils/KoIconUtils.h" #include "project/clipmanager.h" #include "effectslist/initeffects.h" #include "mltcontroller/effectscontroller.h" #include #include #include #include #include #include Timeline::Timeline(KdenliveDoc *doc, const QList &actions, const QList &rulerActions, bool *ok, QWidget *parent) : QWidget(parent), multitrackView(false) , videoTarget(-1) , audioTarget(-1) , m_hasOverlayTrack(false) , m_overlayTrack(NULL) , m_scale(1.0) , m_doc(doc) , m_verticalZoom(1) { m_trackActions << actions; setupUi(this); // ruler_frame->setMaximumHeight(); // size_frame->setMaximumHeight(); m_scene = new CustomTrackScene(this); m_trackview = new CustomTrackView(doc, this, m_scene, parent); if (m_doc->setSceneList() == -1) *ok = false; else *ok = true; Mlt::Service s(m_doc->renderer()->getProducer()->parent().get_service()); m_tractor = new Mlt::Tractor(s); m_ruler = new CustomRuler(doc->timecode(), rulerActions, m_trackview); connect(m_ruler, SIGNAL(zoneMoved(int,int)), this, SIGNAL(zoneMoved(int,int))); connect(m_ruler, SIGNAL(adjustZoom(int)), this, SIGNAL(setZoom(int))); connect(m_ruler, SIGNAL(mousePosition(int)), this, SIGNAL(mousePosition(int))); connect(m_ruler, SIGNAL(seekCursorPos(int)), m_doc->renderer(), SLOT(seek(int)), Qt::QueuedConnection); QHBoxLayout *layout = new QHBoxLayout; layout->setContentsMargins(m_trackview->frameWidth(), 0, 0, 0); layout->setSpacing(0); ruler_frame->setLayout(layout); ruler_frame->setMaximumHeight(m_ruler->height()); layout->addWidget(m_ruler); QHBoxLayout *sizeLayout = new QHBoxLayout; sizeLayout->setContentsMargins(0, 0, 0, 0); sizeLayout->setSpacing(0); size_frame->setLayout(sizeLayout); size_frame->setMaximumHeight(m_ruler->height()); QToolButton *butSmall = new QToolButton(this); butSmall->setIcon(KoIconUtils::themedIcon(QStringLiteral("kdenlive-zoom-small"))); butSmall->setToolTip(i18n("Smaller tracks")); butSmall->setAutoRaise(true); connect(butSmall, SIGNAL(clicked()), this, SLOT(slotVerticalZoomDown())); sizeLayout->addWidget(butSmall); QToolButton *butLarge = new QToolButton(this); butLarge->setIcon(KoIconUtils::themedIcon(QStringLiteral("kdenlive-zoom-large"))); butLarge->setToolTip(i18n("Bigger tracks")); butLarge->setAutoRaise(true); connect(butLarge, SIGNAL(clicked()), this, SLOT(slotVerticalZoomUp())); sizeLayout->addWidget(butLarge); QToolButton *enableZone = new QToolButton(this); KDualAction *ac = new KDualAction(i18n("Don't Use Timeline Zone for Insert"), i18n("Use Timeline Zone for Insert"), this); ac->setActiveIcon(KoIconUtils::themedIcon(QStringLiteral("timeline-use-zone-on"))); ac->setInactiveIcon(KoIconUtils::themedIcon(QStringLiteral("timeline-use-zone-off"))); ac->setShortcut(Qt::Key_G); enableZone->setAutoRaise(true); ac->setActive(KdenliveSettings::useTimelineZoneToEdit()); enableZone->setDefaultAction(ac); connect(ac, &KDualAction::activeChangedByUser, this, &Timeline::slotEnableZone); sizeLayout->addWidget(enableZone); m_doc->doAddAction(QStringLiteral("use_timeline_zone_in_edit"), ac); QHBoxLayout *tracksLayout = new QHBoxLayout; tracksLayout->setContentsMargins(0, 0, 0, 0); tracksLayout->setSpacing(0); tracks_frame->setLayout(tracksLayout); headers_area->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); headers_area->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); QVBoxLayout *headersLayout = new QVBoxLayout; headersLayout->setContentsMargins(0, m_trackview->frameWidth(), 0, 0); headersLayout->setSpacing(0); headers_container->setLayout(headersLayout); connect(headers_area->verticalScrollBar(), SIGNAL(valueChanged(int)), m_trackview->verticalScrollBar(), SLOT(setValue(int))); tracksLayout->addWidget(m_trackview); connect(m_trackview->verticalScrollBar(), SIGNAL(valueChanged(int)), headers_area->verticalScrollBar(), SLOT(setValue(int))); connect(m_trackview, SIGNAL(tracksChanged()), this, SLOT(slotReloadTracks())); connect(m_trackview, SIGNAL(updateTrackHeaders()), this, SLOT(slotRepaintTracks())); connect(m_trackview, SIGNAL(showTrackEffects(int,TrackInfo)), this, SIGNAL(showTrackEffects(int,TrackInfo))); connect(m_trackview, SIGNAL(updateTrackEffectState(int)), this, SLOT(slotUpdateTrackEffectState(int))); transitionHandler = new TransitionHandler(m_tractor); connect(transitionHandler, &TransitionHandler::refresh, m_doc->renderer(), &Render::doRefresh); connect(m_trackview, SIGNAL(cursorMoved(int,int)), m_ruler, SLOT(slotCursorMoved(int,int))); connect(m_trackview, SIGNAL(updateRuler(int)), m_ruler, SLOT(updateRuler(int)), Qt::DirectConnection); connect(m_trackview->horizontalScrollBar(), SIGNAL(valueChanged(int)), m_ruler, SLOT(slotMoveRuler(int))); connect(m_trackview->horizontalScrollBar(), SIGNAL(rangeChanged(int,int)), this, SLOT(slotUpdateVerticalScroll(int,int))); connect(m_trackview, SIGNAL(mousePosition(int)), this, SIGNAL(mousePosition(int))); connect(m_doc->renderer(), &Render::previewRender, this, &Timeline::gotPreviewRender); + connect(m_doc, &KdenliveDoc::reloadChunks, this, &Timeline::slotReloadChunks); m_previewTimer.setSingleShot(true); m_previewTimer.setInterval(3000); connect(&m_previewTimer, &QTimer::timeout, this, &Timeline::startPreviewRender); + m_previewGatherTimer.setSingleShot(true); + m_previewGatherTimer.setInterval(200); + connect(&m_previewGatherTimer, &QTimer::timeout, this, &Timeline::slotProcessDirtyChunks); } Timeline::~Timeline() { delete m_ruler; delete m_trackview; delete m_scene; delete transitionHandler; delete m_tractor; qDeleteAll<>(m_tracks); m_tracks.clear(); } void Timeline::loadTimeline() { parseDocument(m_doc->toXml()); m_trackview->slotUpdateAllThumbs(); m_trackview->slotSelectTrack(m_trackview->getNextVideoTrack(1)); slotChangeZoom(m_doc->zoom().x(), m_doc->zoom().y()); slotSetZone(m_doc->zone(), false); loadPreviewRender(); } QMap Timeline::documentProperties() { QMap props = m_doc->documentProperties(); props.insert(QStringLiteral("audiotargettrack"), QString::number(audioTarget)); props.insert(QStringLiteral("videotargettrack"), QString::number(videoTarget)); props.insert(QStringLiteral("previewchunks"), m_ruler->previewChunks().at(0)); props.insert(QStringLiteral("dirtypreviewchunks"), m_ruler->previewChunks().at(1)); return props; } Track* Timeline::track(int i) { if (i < 0 || i >= m_tracks.count()) return NULL; return m_tracks.at(i); } int Timeline::tracksCount() const { return m_tractor->count() - (m_hasOverlayTrack ? 1 : 0); } int Timeline::visibleTracksCount() const { return m_tractor->count() - 1 - (m_hasOverlayTrack ? 1 : 0); } //virtual void Timeline::keyPressEvent(QKeyEvent * event) { if (event->key() == Qt::Key_Up) { m_trackview->slotTrackUp(); event->accept(); } else if (event->key() == Qt::Key_Down) { m_trackview->slotTrackDown(); event->accept(); } else QWidget::keyPressEvent(event); } int Timeline::duration() const { return m_trackview->duration(); } bool Timeline::checkProjectAudio() { bool hasAudio = false; int max = m_tracks.count(); for (int i = 0; i < max; i++) { Track *sourceTrack = track(i); QScopedPointer track(m_tractor->track(i + 1)); int state = track->get_int("hide"); if (sourceTrack && sourceTrack->hasAudio() && !(state & 2)) { hasAudio = true; break; } } return hasAudio; } int Timeline::inPoint() const { return m_ruler->inPoint(); } int Timeline::outPoint() const { return m_ruler->outPoint(); } void Timeline::slotSetZone(const QPoint &p, bool updateDocumentProperties) { m_ruler->setZone(p); if (updateDocumentProperties) m_doc->setZone(p.x(), p.y()); } void Timeline::setDuration(int dur) { m_trackview->setDuration(dur); m_ruler->setDuration(dur); } int Timeline::getTracks() { int duration = 1; qDeleteAll<>(m_tracks); m_tracks.clear(); QVBoxLayout *headerLayout = qobject_cast< QVBoxLayout* >(headers_container->layout()); QLayoutItem *child; while ((child = headerLayout->takeAt(0)) != 0) { delete child; } int clipsCount = 0; for (int i = 0; i < m_tractor->count(); ++i) { QScopedPointer track(m_tractor->track(i)); QString playlist_name = track->get("id"); if (playlist_name == QLatin1String("black_track")) continue; clipsCount += track->count(); } emit startLoadingBin(clipsCount); emit resetUsageCount(); checkTrackHeight(false); int height = KdenliveSettings::trackheight() * m_scene->scale().y() - 1; int headerWidth = 0; int offset = 0; for (int i = 0; i < m_tractor->count(); ++i) { QScopedPointer track(m_tractor->track(i)); QString playlist_name = track->get("id"); if (playlist_name == QLatin1String("playlistmain")) continue; bool isBackgroundBlackTrack = playlist_name == QLatin1String("black_track"); // check track effects Mlt::Playlist playlist(*track); int trackduration = 0; int audio = 0; Track *tk = NULL; if (!isBackgroundBlackTrack) { audio = playlist.get_int("kdenlive:audio_track"); tk = new Track(i, m_trackActions, playlist, audio == 1 ? AudioTrack : VideoTrack, this); m_tracks.append(tk); trackduration = loadTrack(i, offset, playlist); QFrame *frame = new QFrame(headers_container); frame->setFrameStyle(QFrame::HLine); frame->setFixedHeight(1); headerLayout->insertWidget(0, frame); } else { // Black track tk = new Track(i, m_trackActions, playlist, audio == 1 ? AudioTrack : VideoTrack, this); m_tracks.append(tk); } offset += track->count(); if (audio == 0 && !isBackgroundBlackTrack) { // Check if we have a composite transition for this track QScopedPointer transition(transitionHandler->getTransition(KdenliveSettings::gpu_accel() ? "movit.overlay" : "frei0r.cairoblend", i, -1, true)); if (!transition) { tk->trackHeader->disableComposite(); } } if (!isBackgroundBlackTrack) { tk->trackHeader->setTrackHeight(height); int currentWidth = tk->trackHeader->minimumWidth(); if (currentWidth > headerWidth) headerWidth = currentWidth; headerLayout->insertWidget(0, tk->trackHeader); if (trackduration > duration) duration = trackduration; tk->trackHeader->setSelectedIndex(m_trackview->selectedTrack()); connect(tk->trackHeader, &HeaderTrack::switchTrackComposite, this, &Timeline::slotSwitchTrackComposite); connect(tk->trackHeader, SIGNAL(switchTrackVideo(int,bool)), m_trackview, SLOT(slotSwitchTrackVideo(int,bool))); connect(tk->trackHeader, SIGNAL(switchTrackAudio(int,bool)), m_trackview, SLOT(slotSwitchTrackAudio(int,bool))); connect(tk->trackHeader, SIGNAL(switchTrackLock(int,bool)), m_trackview, SLOT(slotSwitchTrackLock(int,bool))); connect(tk->trackHeader, SIGNAL(selectTrack(int,bool)), m_trackview, SLOT(slotSelectTrack(int,bool))); connect(tk->trackHeader, SIGNAL(renameTrack(int,QString)), this, SLOT(slotRenameTrack(int,QString))); connect(tk->trackHeader, SIGNAL(configTrack()), this, SIGNAL(configTrack())); connect(tk->trackHeader, SIGNAL(addTrackEffect(QDomElement,int)), m_trackview, SLOT(slotAddTrackEffect(QDomElement,int))); if (playlist.filter_count()) { getEffects(playlist, NULL, i); slotUpdateTrackEffectState(i); } connect(tk, &Track::newTrackDuration, this, &Timeline::checkDuration, Qt::DirectConnection); connect(tk, SIGNAL(storeSlowMotion(QString,Mlt::Producer *)), m_doc->renderer(), SLOT(storeSlowmotionProducer(QString,Mlt::Producer *))); } } headers_container->setFixedWidth(headerWidth); if (audioTarget > -1) { m_tracks.at(audioTarget)->trackHeader->switchTarget(true); } if (videoTarget > -1) { m_tracks.at(videoTarget)->trackHeader->switchTarget(true); } updatePalette(); refreshTrackActions(); return duration; } void Timeline::checkDuration(int duration) { Q_UNUSED(duration) m_doc->renderer()->mltCheckLength(m_tractor); return; /*FIXME for (int i = 1; i < m_tractor->count(); ++i) { QScopedPointer tk(m_tractor->track(i)); int len = tk->get_playtime() - 1; if (len > duration) duration = len; } QScopedPointer tk1(m_tractor->track(0)); Mlt::Service s(tk1->get_service()); Mlt::Playlist blackTrack(s); if (blackTrack.get_playtime() - 1 != duration) { QScopedPointer blackClip(blackTrack.get_clip(0)); if (blackClip->parent().get_length() <= duration) { blackClip->parent().set("length", duration + 1); blackClip->parent().set("out", duration); blackClip->set("length", duration + 1); } blackTrack.resize_clip(0, 0, duration); } //TODO: rewind consumer if beyond duration / emit durationChanged */ } void Timeline::getTransitions() { QLocale locale; locale.setNumberOptions(QLocale::OmitGroupSeparator); mlt_service service = mlt_service_get_producer(m_tractor->get_service()); QScopedPointer field(m_tractor->field()); while (service) { Mlt::Properties prop(MLT_SERVICE_PROPERTIES(service)); if (QString(prop.get("mlt_type")) != QLatin1String("transition")) break; //skip automatic mix if (prop.get_int("internal_added") == 237) { QString trans = prop.get("mlt_service"); if (trans == QLatin1String("movit.overlay") || trans == QLatin1String("frei0r.cairoblend")) { int ix = prop.get_int("b_track"); if (ix >= 0 && ix < m_tracks.count()) { TrackInfo info = track(ix)->info(); info.composite = !prop.get_int("disable"); track(ix)->setInfo(info); } else qWarning() << "Wrong composite track index: " << ix; } else if(trans == QLatin1String("mix")) { } service = mlt_service_producer(service); continue; } int a_track = prop.get_int("a_track"); int b_track = prop.get_int("b_track"); ItemInfo transitionInfo; transitionInfo.startPos = GenTime(prop.get_int("in"), m_doc->fps()); transitionInfo.endPos = GenTime(prop.get_int("out") + 1, m_doc->fps()); transitionInfo.track = b_track; // When adding composite transition, check if it is a wipe transition if (prop.get("kdenlive_id") == NULL && QString(prop.get("mlt_service")) == QLatin1String("composite") && isSlide(prop.get("geometry"))) prop.set("kdenlive_id", "slide"); QDomElement base = MainWindow::transitions.getEffectByTag(prop.get("mlt_service"), prop.get("kdenlive_id")).cloneNode().toElement(); //check invalid parameters if (a_track > m_tractor->count() - 1) { m_documentErrors.append(i18n("Transition %1 had an invalid track: %2 > %3", prop.get("id"), a_track, m_tractor->count() - 1) + '\n'); prop.set("a_track", m_tractor->count() - 1); } if (b_track > m_tractor->count() - 1) { m_documentErrors.append(i18n("Transition %1 had an invalid track: %2 > %3", prop.get("id"), b_track, m_tractor->count() - 1) + '\n'); prop.set("b_track", m_tractor->count() - 1); } if (a_track == b_track || b_track <= 0 || transitionInfo.startPos >= transitionInfo.endPos || base.isNull() //|| !m_trackview->canBePastedTo(transitionInfo, TransitionWidget) ) { // invalid transition, remove it m_documentErrors.append(i18n("Removed invalid transition: %1", prop.get("id")) + '\n'); mlt_service disconnect = service; service = mlt_service_producer(service); mlt_field_disconnect_service(field->get_field(), disconnect); } else { QDomNodeList params = base.elementsByTagName(QStringLiteral("parameter")); for (int i = 0; i < params.count(); ++i) { QDomElement e = params.item(i).toElement(); QString paramName = e.hasAttribute(QStringLiteral("tag")) ? e.attribute(QStringLiteral("tag")) : e.attribute(QStringLiteral("name")); QString value = prop.get(paramName.toUtf8().constData()); // if transition parameter has an "optional" attribute, it means that it can contain an empty value if (value.isEmpty() && !e.hasAttribute(QStringLiteral("optional"))) continue; if (e.hasAttribute("factor") || e.hasAttribute("offset")) adjustDouble(e, value); else e.setAttribute(QStringLiteral("value"), value); } Transition *tr = new Transition(transitionInfo, a_track, m_doc->fps(), base, QString(prop.get("automatic")) == QLatin1String("1")); connect(tr, &AbstractClipItem::selectItem, m_trackview, &CustomTrackView::slotSelectItem); tr->setPos(transitionInfo.startPos.frames(m_doc->fps()), KdenliveSettings::trackheight() * (visibleTracksCount() - transitionInfo.track) + 1 + tr->itemOffset()); if (QString(prop.get("force_track")) == QLatin1String("1")) tr->setForcedTrack(true, a_track); if (isTrackLocked(b_track)) tr->setItemLocked(true); m_scene->addItem(tr); service = mlt_service_producer(service); } } } // static bool Timeline::isSlide(QString geometry) { if (geometry.count(';') != 1) return false; geometry.remove(QChar('%'), Qt::CaseInsensitive); geometry.replace(QChar('x'), QChar(':'), Qt::CaseInsensitive); geometry.replace(QChar(','), QChar(':'), Qt::CaseInsensitive); geometry.replace(QChar('/'), QChar(':'), Qt::CaseInsensitive); QString start = geometry.section('=', 0, 0).section(':', 0, -2) + ':'; start.append(geometry.section('=', 1, 1).section(':', 0, -2)); QStringList numbers = start.split(':', QString::SkipEmptyParts); for (int i = 0; i < numbers.size(); ++i) { int checkNumber = qAbs(numbers.at(i).toInt()); if (checkNumber != 0 && checkNumber != 100) { return false; } } return true; } void Timeline::adjustDouble(QDomElement &e, const QString &value) { QLocale locale; locale.setNumberOptions(QLocale::OmitGroupSeparator); QString factor = e.attribute(QStringLiteral("factor"), QStringLiteral("1")); double offset = locale.toDouble(e.attribute(QStringLiteral("offset"), QStringLiteral("0"))); double fact = 1; if (factor.contains('%')) fact = EffectsController::getStringEval(m_doc->getProfileInfo(), factor); else fact = locale.toDouble(factor); QString type = e.attribute(QStringLiteral("type")); if (type == QLatin1String("double") || type == QLatin1String("constant")) { double val = locale.toDouble(value); e.setAttribute(QStringLiteral("value"), locale.toString(offset + val * fact)); } else if (type == QLatin1String("simplekeyframe")) { QStringList keys = value.split(QLatin1Char(';')); for (int j = 0; j < keys.count(); ++j) { QString pos = keys.at(j).section(QLatin1Char('='), 0, 0); double val = locale.toDouble(keys.at(j).section(QLatin1Char('='), 1, 1)) * fact + offset; keys[j] = pos + '=' + locale.toString(val); } e.setAttribute(QStringLiteral("value"), keys.join(QLatin1Char(';'))); } else { e.setAttribute(QStringLiteral("value"), value); } } void Timeline::parseDocument(const QDomDocument &doc) { //int cursorPos = 0; m_documentErrors.clear(); m_replacementProducerIds.clear(); // parse project tracks QDomElement mlt = doc.firstChildElement(QStringLiteral("mlt")); m_trackview->setDuration(getTracks()); getTransitions(); // Rebuild groups QDomDocument groupsDoc; groupsDoc.setContent(m_doc->renderer()->getBinProperty(QStringLiteral("kdenlive:clipgroups"))); QDomNodeList groups = groupsDoc.elementsByTagName(QStringLiteral("group")); m_trackview->loadGroups(groups); // Load custom effects QDomDocument effectsDoc; effectsDoc.setContent(m_doc->renderer()->getBinProperty(QStringLiteral("kdenlive:customeffects"))); QDomNodeList effects = effectsDoc.elementsByTagName(QStringLiteral("effect")); if (!effects.isEmpty()) { m_doc->saveCustomEffects(effects); } if (!m_documentErrors.isNull()) KMessageBox::sorry(this, m_documentErrors); if (mlt.hasAttribute(QStringLiteral("upgraded")) || mlt.hasAttribute(QStringLiteral("modified"))) { // Our document was upgraded, create a backup copy just in case QString baseFile = m_doc->url().path().section(QStringLiteral(".kdenlive"), 0, 0); int ct = 0; QString backupFile = baseFile + "_backup" + QString::number(ct) + ".kdenlive"; while (QFile::exists(backupFile)) { ct++; backupFile = baseFile + "_backup" + QString::number(ct) + ".kdenlive"; } QString message; if (mlt.hasAttribute(QStringLiteral("upgraded"))) message = i18n("Your project file was upgraded to the latest Kdenlive document version.\nTo make sure you don't lose data, a backup copy called %1 was created.", backupFile); else message = i18n("Your project file was modified by Kdenlive.\nTo make sure you don't lose data, a backup copy called %1 was created.", backupFile); KIO::FileCopyJob *copyjob = KIO::file_copy(m_doc->url(), QUrl::fromLocalFile(backupFile)); if (copyjob->exec()) KMessageBox::information(this, message); else KMessageBox::information(this, i18n("Your project file was upgraded to the latest Kdenlive document version, but it was not possible to create the backup copy %1.", backupFile)); } } void Timeline::slotDeleteClip(const QString &clipId, QUndoCommand *deleteCommand) { m_trackview->deleteClip(clipId, deleteCommand); } void Timeline::setCursorPos(int pos) { m_trackview->setCursorPos(pos); } void Timeline::moveCursorPos(int pos) { m_trackview->setCursorPos(pos); } void Timeline::slotChangeZoom(int horizontal, int vertical) { m_ruler->setPixelPerMark(horizontal); m_scale = (double) m_trackview->getFrameWidth() / m_ruler->comboScale[horizontal]; if (vertical == -1) { // user called zoom m_doc->setZoom(horizontal, m_verticalZoom); m_trackview->setScale(m_scale, m_scene->scale().y()); } else { m_verticalZoom = vertical; if (m_verticalZoom == 0) m_trackview->setScale(m_scale, 0.5); else m_trackview->setScale(m_scale, m_verticalZoom); adjustTrackHeaders(); } } int Timeline::fitZoom() const { int zoom = (int)((duration() + 20 / m_scale) * m_trackview->getFrameWidth() / m_trackview->width()); int i; for (i = 0; i < 13; ++i) if (m_ruler->comboScale[i] > zoom) break; return i; } KdenliveDoc *Timeline::document() { return m_doc; } void Timeline::refresh() { m_trackview->viewport()->update(); } void Timeline::slotRepaintTracks() { for (int i = 1; i < m_tracks.count(); i++) { m_tracks.at(i)->trackHeader->setSelectedIndex(m_trackview->selectedTrack()); } } void Timeline::blockTrackSignals(bool block) { for (int i = 1; i < m_tracks.count(); i++) { m_tracks.at(i)->blockSignals(block); } } void Timeline::slotReloadTracks() { emit updateTracksInfo(); } TrackInfo Timeline::getTrackInfo(int ix) { if (ix < 0 || ix > m_tracks.count()) { qWarning()<<"/// ARGH, requested info for track: "<info(); } bool Timeline::isLastClip(ItemInfo info) { Track *tk = track(info.track); if (tk == NULL) { return true; } return tk->isLastClip(info.endPos.seconds()); } void Timeline::setTrackInfo(int ix, TrackInfo info) { if (ix < 0 || ix > m_tracks.count()) { qWarning() << "Set Track effect outisde of range"; return; } Track *tk = track(ix); tk->setInfo(info); } QList Timeline::getTracksInfo() { QList tracks; for (int i = 0; i < tracksCount(); i++) { tracks << track(i)->info(); } return tracks; } QStringList Timeline::getTrackNames() { QStringList trackNames; for (int i = 0; i < tracksCount(); i++) { trackNames << track(i)->info().trackName; } return trackNames; } void Timeline::lockTrack(int ix, bool lock) { Track *tk = track(ix); if (tk == NULL) { qWarning() << "Set Track effect outisde of range: "<lockTrack(lock); } bool Timeline::isTrackLocked(int ix) { Track *tk = track(ix); if (tk == NULL) { qWarning() << "Set Track effect outisde of range: "<getIntProperty(QStringLiteral("kdenlive:locked_track")); return locked == 1; } void Timeline::updateTrackState(int ix, int state) { int currentState = 0; QScopedPointer track(m_tractor->track(ix)); currentState = track->get_int("hide"); if (state == currentState) return; if (state == 0) { // Show all if (currentState & 1) { switchTrackVideo(ix, false); } if (currentState & 2) { switchTrackAudio(ix, false); } } else if (state == 1) { // Mute video if (currentState & 2) { switchTrackAudio(ix, false); } switchTrackVideo(ix, true); } else if (state == 2) { // Mute audio if (currentState & 1) { switchTrackVideo(ix, false); } switchTrackAudio(ix, true); } else { switchTrackVideo(ix, true); switchTrackAudio(ix, true); } } void Timeline::switchTrackVideo(int ix, bool hide) { Track* tk = track(ix); if (tk == NULL) { qWarning() << "Set Track effect outisde of range: "<state(); if (hide && (state & 1)) { // Video is already muted return; } int newstate = 0; if (hide) { if (state & 2) { newstate = 3; } else { newstate = 1; } } else { if (state & 2) { newstate = 2; } else { newstate = 0; } } tk->setState(newstate); refreshTractor(); } void Timeline::slotSwitchTrackComposite(int trackIndex, bool enable) { if (trackIndex < 1 || trackIndex > m_tracks.count()) return; QScopedPointer transition(transitionHandler->getTransition(KdenliveSettings::gpu_accel() ? "movit.overlay" : "frei0r.cairoblend", trackIndex, -1, true)); if (transition) { transition->set("disable", enable); // When turning a track composite on/off, we need to re-plug transitions correctly updateComposites(); m_doc->renderer()->doRefresh(); m_doc->setModified(); //TODO: create undo/redo command for this } else { Track* tk = track(trackIndex); tk->trackHeader->setComposite(false); qWarning() << "Composite transition not found"; } } void Timeline::updateComposites() { int lowest = getLowestVideoTrack(); if (lowest >= 0) { transitionHandler->rebuildComposites(lowest); } } void Timeline::refreshTractor() { m_tractor->multitrack()->refresh(); m_tractor->refresh(); } void Timeline::switchTrackAudio(int ix, bool mute) { Track* tk = track(ix); if (tk == NULL) { qWarning() << "Set Track effect outisde of range: "<state(); if (mute && (state & 2)) { // audio is already muted return; } if (mute && state < 2 ) { // We mute a track with sound /*if (ix == getLowestNonMutedAudioTrack())*/ } else if (!mute && state > 1 ) { // We un-mute a previously muted track /*if (ix < getLowestNonMutedAudioTrack())*/ } int newstate; if (mute) { if (state & 1) newstate = 3; else newstate = 2; } else if (state & 1) { newstate = 1; } else { newstate = 0; } tk->setState(newstate); //if (audioMixingBroken) fixAudioMixing(); m_tractor->multitrack()->refresh(); m_tractor->refresh(); } int Timeline::getLowestVideoTrack() { for (int i = 1; i < m_tractor->count(); ++i) { QScopedPointer track(m_tractor->track(i)); Mlt::Playlist playlist(*track); if (playlist.get_int("kdenlive:audio_track") != 1) return i; } return -1; } void Timeline::fixAudioMixing() { QScopedPointer field(m_tractor->field()); field->lock(); mlt_service nextservice = mlt_service_get_producer(field->get_service()); mlt_properties properties = MLT_SERVICE_PROPERTIES(nextservice); QString mlt_type = mlt_properties_get(properties, "mlt_type"); QString resource = mlt_properties_get(properties, "mlt_service"); // Delete all audio mixing transitions while (mlt_type == QLatin1String("transition")) { if (resource == QLatin1String("mix")) { Mlt::Transition transition((mlt_transition) nextservice); nextservice = mlt_service_producer(nextservice); field->disconnect_service(transition); } else 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"); } // Re-add correct audio transitions for (int i = 1; i < m_tractor->count(); i++) { //bool muted = getTrackInfo(i).isMute; //if (muted) continue; /*int a_track = qMax(lowestTrack, i - 1); bool a_muted = getTrackInfo(a_track).isMute; while (a_muted && a_track > lowestTrack) { a_track = qMax(lowestTrack, a_track - 1); a_muted = getTrackInfo(a_track).isMute; } if (a_muted) continue;*/ Mlt::Transition *transition = new Mlt::Transition(*m_tractor->profile(), "mix"); transition->set("always_active", 1); transition->set("combine", 1); transition->set("a_track", 0); transition->set("b_track", i); transition->set("internal_added", 237); field->plant_transition(*transition, 0, i); } field->unlock(); } void Timeline::updatePalette() { headers_container->setStyleSheet(QLatin1String("")); setPalette(qApp->palette()); QPalette p = qApp->palette(); KColorScheme scheme(p.currentColorGroup(), KColorScheme::View, KSharedConfig::openConfig(KdenliveSettings::colortheme())); QColor col = scheme.background().color(); QColor col2 = scheme.foreground().color(); headers_container->setStyleSheet(QStringLiteral("QLineEdit { background-color: transparent;color: %1;} QLineEdit:hover{ background-color: %2;} QLineEdit:focus { background-color: %2;} ").arg(col2.name(), col.name())); m_trackview->updatePalette(); if (!m_tracks.isEmpty()) { int ix = m_trackview->selectedTrack(); for (int i = 0; i < m_tracks.count(); i++) { if (m_tracks.at(i)->trackHeader) { m_tracks.at(i)->trackHeader->refreshPalette(); if (i == ix) m_tracks.at(ix)->trackHeader->setSelectedIndex(ix); } } } m_ruler->activateZone(); } void Timeline::updateHeaders() { if (!m_tracks.isEmpty()) { for (int i = 0; i < m_tracks.count(); i++) { if (m_tracks.at(i)->trackHeader) { m_tracks.at(i)->trackHeader->updateLed(); } } } } void Timeline::refreshIcons() { QList allMenus = this->findChildren(); for (int i = 0; i < allMenus.count(); i++) { QAction *m = allMenus.at(i); QIcon ic = m->icon(); if (ic.isNull() || ic.name().isEmpty()) continue; QIcon newIcon = KoIconUtils::themedIcon(ic.name()); m->setIcon(newIcon); } QList allButtons = this->findChildren(); for (int i = 0; i < allButtons.count(); i++) { KDualAction *m = allButtons.at(i); QIcon ic = m->activeIcon(); if (ic.isNull() || ic.name().isEmpty()) continue; QIcon newIcon = KoIconUtils::themedIcon(ic.name()); m->setActiveIcon(newIcon); ic = m->inactiveIcon(); if (ic.isNull() || ic.name().isEmpty()) continue; newIcon = KoIconUtils::themedIcon(ic.name()); m->setInactiveIcon(newIcon); } } void Timeline::adjustTrackHeaders() { if (m_tracks.isEmpty()) return; int height = KdenliveSettings::trackheight() * m_scene->scale().y() - 1; for (int i = 1; i < m_tracks.count(); i++) { m_tracks.at(i)->trackHeader->adjustSize(height); } } void Timeline::reloadTrack(int ix, int start, int end) { // Get playlist Mlt::Playlist pl = m_tracks.at(ix)->playlist(); if (end == -1) end = pl.get_length(); // Remove current clips int startIndex = pl.get_clip_index_at(start); int endIndex = pl.get_clip_index_at(end); double startY = m_trackview->getPositionFromTrack(ix) + 2; QRectF r(start, startY, end - start, 2); QList selection = m_scene->items(r); QList toDelete; for (int i = 0; i < selection.count(); i++) { if (selection.at(i)->type() == AVWidget) toDelete << selection.at(i); } qDeleteAll(toDelete); // Reload items loadTrack(ix, 0, pl, startIndex, endIndex, false); } int Timeline::loadTrack(int ix, int offset, Mlt::Playlist &playlist, int start, int end, bool updateReferences) { // parse track Mlt::ClipInfo *info = new Mlt::ClipInfo(); double fps = m_doc->fps(); if (end == -1) end = playlist.count(); bool locked = playlist.get_int("kdenlive:locked_track") == 1; for(int i = start; i < end; ++i) { emit loadingBin(offset + i + 1); if (playlist.is_blank(i)) { continue; } playlist.clip_info(i, info); Mlt::Producer *clip = info->cut; // Found a clip int in = info->frame_in; int out = info->frame_out; QString idString = info->producer->get("id"); if (in > out || m_invalidProducers.contains(idString)) { QString trackName = playlist.get("kdenlive:track_name"); m_documentErrors.append(i18n("Invalid clip removed from track %1 at %2\n", trackName.isEmpty() ? QString::number(ix) : trackName, info->start)); playlist.remove(i); --i; continue; } QString id = idString; Track::SlowmoInfo slowInfo; slowInfo.speed = 1.0; slowInfo.strobe = 1; slowInfo.state = PlaylistState::Original; bool hasSpeedEffect = false; if (idString.endsWith(QLatin1String("_video"))) { // Video only producer, store it in BinController m_doc->renderer()->loadExtraProducer(idString, new Mlt::Producer(clip->parent())); } if (idString.startsWith(QLatin1String("slowmotion"))) { hasSpeedEffect = true; QLocale locale; locale.setNumberOptions(QLocale::OmitGroupSeparator); id = idString.section(':', 1, 1); slowInfo.speed = locale.toDouble(idString.section(':', 2, 2)); slowInfo.strobe = idString.section(':', 3, 3).toInt(); if (slowInfo.strobe == 0) slowInfo.strobe = 1; slowInfo.state = (PlaylistState::ClipState) idString.section(':', 4, 4).toInt(); // Slowmotion producer, store it for reuse Mlt::Producer *parentProd = new Mlt::Producer(clip->parent()); QString url = parentProd->get("warp_resource"); if (!m_doc->renderer()->storeSlowmotionProducer(slowInfo.toString(locale) + url, parentProd)) { delete parentProd; } } id = id.section('_', 0, 0); int length = out - in + 1; ProjectClip *binclip = m_doc->getBinClip(id); PlaylistState::ClipState originalState = PlaylistState::Original; if (binclip == NULL) { // Is this a disabled clip id = info->producer->get("kdenlive:binid"); binclip = m_doc->getBinClip(id); originalState = (PlaylistState::ClipState) info->producer->get_int("kdenlive:clipstate"); } if (binclip == NULL) { // Warning, unknown clip found, timeline corruption!! //TODO: fix this qDebug()<<"* * * * *UNKNOWN CLIP, WE ARE DEAD: "<addRef(); ItemInfo clipinfo; clipinfo.startPos = GenTime(info->start, fps); clipinfo.endPos = GenTime(info->start + length, fps); clipinfo.cropStart = GenTime(in, fps); clipinfo.cropDuration = GenTime(length, fps); clipinfo.track = ix; //qDebug()<<"// Loading clip: "<getFrameWidth(), true); connect(item, &AbstractClipItem::selectItem, m_trackview, &CustomTrackView::slotSelectItem); item->setPos(clipinfo.startPos.frames(fps), KdenliveSettings::trackheight() * (visibleTracksCount() - clipinfo.track) + 1 + item->itemOffset()); //qDebug()<<" * * Loaded clip on tk: "<updateState(idString, info->producer->get_int("audio_index"), info->producer->get_int("video_index"), originalState); m_scene->addItem(item); if (locked) item->setItemLocked(true); if (hasSpeedEffect) { QDomElement speedeffect = MainWindow::videoEffects.getEffectByTag(QString(), QStringLiteral("speed")).cloneNode().toElement(); EffectsList::setParameter(speedeffect, QStringLiteral("speed"), QString::number((int)(100 * slowInfo.speed + 0.5))); EffectsList::setParameter(speedeffect, QStringLiteral("strobe"), QString::number(slowInfo.strobe)); item->addEffect(m_doc->getProfileInfo(), speedeffect, false); } // parse clip effects getEffects(*clip, item); } delete info; return playlist.get_length(); } void Timeline::loadGuides(QMap guidesData) { QMapIterator i(guidesData); while (i.hasNext()) { i.next(); const GenTime pos = GenTime(i.key()); m_trackview->addGuide(pos, i.value(), true); } } void Timeline::getEffects(Mlt::Service &service, ClipItem *clip, int track) { int effectNb = clip == NULL ? 0 : clip->effectsCount(); QLocale locale; locale.setNumberOptions(QLocale::OmitGroupSeparator); for (int ix = 0; ix < service.filter_count(); ++ix) { QScopedPointer effect(service.filter(ix)); QDomElement clipeffect = getEffectByTag(effect->get("tag"), effect->get("kdenlive_id")); if (clipeffect.isNull()) { m_documentErrors.append(i18n("Effect %1:%2 not found in MLT, it was removed from this project\n", effect->get("tag"), effect->get("kdenlive_id"))); service.detach(*effect); --ix; continue; } effectNb++; QDomElement currenteffect = clipeffect.cloneNode().toElement(); currenteffect.setAttribute(QStringLiteral("kdenlive_ix"), QString::number(effectNb)); currenteffect.setAttribute(QStringLiteral("kdenlive_info"), effect->get("kdenlive_info")); currenteffect.setAttribute(QStringLiteral("disable"), effect->get("disable")); QDomNodeList clipeffectparams = currenteffect.childNodes(); QDomNodeList params = currenteffect.elementsByTagName(QStringLiteral("parameter")); ProfileInfo info = m_doc->getProfileInfo(); for (int i = 0; i < params.count(); ++i) { QDomElement e = params.item(i).toElement(); if (e.attribute(QStringLiteral("type")) == QLatin1String("keyframe")) e.setAttribute(QStringLiteral("keyframes"), getKeyframes(service, ix, e)); else setParam(info, e, effect->get(e.attribute(QStringLiteral("name")).toUtf8().constData())); } if (effect->get_out()) { // no keyframes but in/out points //EffectsList::setParameter(currenteffect, QStringLiteral("in"), effect->get("in")); //EffectsList::setParameter(currenteffect, QStringLiteral("out"), effect->get("out")); currenteffect.setAttribute(QStringLiteral("in"), effect->get_in()); currenteffect.setAttribute(QStringLiteral("out"), effect->get_out()); } QString sync = effect->get("kdenlive:sync_in_out"); if (!sync.isEmpty()) { currenteffect.setAttribute(QStringLiteral("kdenlive:sync_in_out"), sync); } if (QString(effect->get("tag")) == QLatin1String("region")) getSubfilters(effect.data(), currenteffect); if (clip) { clip->addEffect(m_doc->getProfileInfo(), currenteffect, false); } else { addTrackEffect(track, currenteffect, false); } } } QString Timeline::getKeyframes(Mlt::Service service, int &ix, QDomElement e) { QLocale locale; locale.setNumberOptions(QLocale::OmitGroupSeparator); QString starttag = e.attribute(QStringLiteral("starttag"), QStringLiteral("start")); QString endtag = e.attribute(QStringLiteral("endtag"), QStringLiteral("end")); double fact, offset = locale.toDouble(e.attribute(QStringLiteral("offset"), QStringLiteral("0"))); QString factor = e.attribute(QStringLiteral("factor"), QStringLiteral("1")); if (factor.contains('%')) fact = EffectsController::getStringEval(m_doc->getProfileInfo(), factor); else fact = locale.toDouble(factor); // retrieve keyframes QScopedPointer effect(service.filter(ix)); int effectNb = effect->get_int("kdenlive_ix"); QString keyframes = QString::number(effect->get_in()) + '=' + locale.toString(offset + fact * effect->get_double(starttag.toUtf8().constData())) + ';'; for (;ix < service.filter_count(); ++ix) { QScopedPointer eff2(service.filter(ix)); if (eff2->get_int("kdenlive_ix") != effectNb) break; if (eff2->get_in() < eff2->get_out()) { keyframes.append(QString::number(eff2->get_out()) + '=' + locale.toString(offset + fact * eff2->get_double(endtag.toUtf8().constData())) + ';'); } } --ix; return keyframes; } void Timeline::getSubfilters(Mlt::Filter *effect, QDomElement ¤teffect) { for (int i = 0; ; ++i) { QString name = "filter" + QString::number(i); if (!effect->get(name.toUtf8().constData())) break; //identify effect QString tag = effect->get(name.append(".tag").toUtf8().constData()); QString id = effect->get(name.append(".kdenlive_id").toUtf8().constData()); QDomElement subclipeffect = getEffectByTag(tag, id); if (subclipeffect.isNull()) { qWarning() << "Region sub-effect not found"; continue; } //load effect subclipeffect = subclipeffect.cloneNode().toElement(); subclipeffect.setAttribute(QStringLiteral("region_ix"), i); //get effect parameters (prefixed by subfilter name) QDomNodeList params = subclipeffect.elementsByTagName(QStringLiteral("parameter")); ProfileInfo info = m_doc->getProfileInfo(); for (int i = 0; i < params.count(); ++i) { QDomElement param = params.item(i).toElement(); setParam(info, param, effect->get((name + "." + param.attribute(QStringLiteral("name"))).toUtf8().constData())); } currenteffect.appendChild(currenteffect.ownerDocument().importNode(subclipeffect, true)); } } //static void Timeline::setParam(ProfileInfo info, QDomElement param, QString value) { QLocale locale; locale.setNumberOptions(QLocale::OmitGroupSeparator); //get Kdenlive scaling parameters double offset = locale.toDouble(param.attribute(QStringLiteral("offset"), QStringLiteral("0"))); double fact; QString factor = param.attribute(QStringLiteral("factor"), QStringLiteral("1")); if (factor.contains('%')) { fact = EffectsController::getStringEval(info, factor); } else { fact = locale.toDouble(factor); } //adjust parameter if necessary QString type = param.attribute(QStringLiteral("type")); if (type == QLatin1String("simplekeyframe")) { QStringList kfrs = value.split(';'); for (int l = 0; l < kfrs.count(); ++l) { QString fr = kfrs.at(l).section('=', 0, 0); double val = locale.toDouble(kfrs.at(l).section('=', 1, 1)); if (fact != 1) { // Add 0.5 since we are converting to integer below so that 0.8 is converted to 1 and not 0 val = val * fact + 0.5; } kfrs[l] = fr + '=' + QString::number((int) (val + offset)); } param.setAttribute(QStringLiteral("keyframes"), kfrs.join(QStringLiteral(";"))); } else if (type == QLatin1String("double") || type == QLatin1String("constant")) { param.setAttribute(QStringLiteral("value"), locale.toDouble(value) * fact + offset); } else { param.setAttribute(QStringLiteral("value"), value); } } QDomElement Timeline::getEffectByTag(const QString &effecttag, const QString &effectid) { QDomElement clipeffect = MainWindow::customEffects.getEffectByTag(QString(), effectid); if (clipeffect.isNull()) { clipeffect = MainWindow::videoEffects.getEffectByTag(effecttag, effectid); } if (clipeffect.isNull()) { clipeffect = MainWindow::audioEffects.getEffectByTag(effecttag, effectid); } return clipeffect; } QGraphicsScene *Timeline::projectScene() { return m_scene; } CustomTrackView *Timeline::projectView() { return m_trackview; } void Timeline::setEditMode(const QString & editMode) { m_editMode = editMode; } const QString & Timeline::editMode() const { return m_editMode; } void Timeline::slotVerticalZoomDown() { if (m_verticalZoom == 0) return; m_verticalZoom--; m_doc->setZoom(m_doc->zoom().x(), m_verticalZoom); if (m_verticalZoom == 0) m_trackview->setScale(m_scene->scale().x(), 0.5); else m_trackview->setScale(m_scene->scale().x(), 1); adjustTrackHeaders(); m_trackview->verticalScrollBar()->setValue(headers_area->verticalScrollBar()->value()); } void Timeline::slotVerticalZoomUp() { if (m_verticalZoom == 2) return; m_verticalZoom++; m_doc->setZoom(m_doc->zoom().x(), m_verticalZoom); if (m_verticalZoom == 2) m_trackview->setScale(m_scene->scale().x(), 2); else m_trackview->setScale(m_scene->scale().x(), 1); adjustTrackHeaders(); m_trackview->verticalScrollBar()->setValue(headers_area->verticalScrollBar()->value()); } void Timeline::slotRenameTrack(int ix, const QString &name) { QString currentName = track(ix)->getProperty(QStringLiteral("kdenlive:track_name")); if (currentName == name) return; ConfigTracksCommand *configTracks = new ConfigTracksCommand(this, ix, currentName, name); m_doc->commandStack()->push(configTracks); } void Timeline::renameTrack(int ix, const QString &name) { if (ix < 1) return; Track *tk = track(ix); if (!tk) return; tk->setProperty(QStringLiteral("kdenlive:track_name"), name); tk->trackHeader->renameTrack(name); slotReloadTracks(); } void Timeline::slotUpdateVerticalScroll(int /*min*/, int max) { int height = 0; if (max > 0) height = m_trackview->horizontalScrollBar()->height() - 1; headers_container->layout()->setContentsMargins(0, m_trackview->frameWidth(), 0, height); } void Timeline::updateRuler() { m_ruler->update(); } void Timeline::slotShowTrackEffects(int ix) { m_trackview->clearSelection(); emit showTrackEffects(ix, getTrackInfo(ix)); } void Timeline::slotUpdateTrackEffectState(int ix) { if (ix < 1) return; Track *tk = track(ix); if (!tk) return; tk->trackHeader->updateEffectLabel(tk->effectsList.effectNames()); } void Timeline::slotSaveTimelinePreview(const QString &path) { QImage img(width(), height(), QImage::Format_ARGB32_Premultiplied); img.fill(palette().base().color().rgb()); QPainter painter(&img); render(&painter); painter.end(); img = img.scaledToWidth(600, Qt::SmoothTransformation); img.save(path); } void Timeline::updateProfile(bool fpsChanged) { m_ruler->updateFrameSize(); m_ruler->updateProjectFps(m_doc->timecode()); m_ruler->setPixelPerMark(m_doc->zoom().x(), true); slotChangeZoom(m_doc->zoom().x(), m_doc->zoom().y()); slotSetZone(m_doc->zone(), false); m_trackview->updateSceneFrameWidth(fpsChanged); } void Timeline::checkTrackHeight(bool force) { if (m_trackview->checkTrackHeight(force)) { m_doc->clipManager()->clearCache(); m_ruler->updateFrameSize(); m_trackview->updateSceneFrameWidth(); slotChangeZoom(m_doc->zoom().x(), m_doc->zoom().y()); slotSetZone(m_doc->zone(), false); } } bool Timeline::moveClip(int startTrack, qreal startPos, int endTrack, qreal endPos, PlaylistState::ClipState state, TimelineMode::EditMode mode, bool duplicate) { if (startTrack == endTrack) { return track(startTrack)->move(startPos, endPos, mode); } Track *sourceTrack = track(startTrack); int pos = sourceTrack->frame(startPos); int clipIndex = sourceTrack->playlist().get_clip_index_at(pos); sourceTrack->playlist().lock(); Mlt::Producer *clipProducer = sourceTrack->playlist().replace_with_blank(clipIndex); sourceTrack->playlist().consolidate_blanks(); if (!clipProducer || clipProducer->is_blank()) { qDebug() << "// Cannot get clip at index: "<playlist().unlock(); return false; } sourceTrack->playlist().unlock(); Track *destTrack = track(endTrack); bool success = destTrack->add(endPos, clipProducer, GenTime(clipProducer->get_in(), destTrack->fps()).seconds(), GenTime(clipProducer->get_out() + 1, destTrack->fps()).seconds(), state, duplicate, mode); delete clipProducer; return success; } void Timeline::addTrackEffect(int trackIndex, QDomElement effect, bool addToPlaylist) { if (trackIndex < 0 || trackIndex >= m_tracks.count()) { qWarning() << "Set Track effect outisde of range"; return; } Track *sourceTrack = track(trackIndex); effect.setAttribute(QStringLiteral("kdenlive_ix"), sourceTrack->effectsList.count() + 1); // Init parameter value & keyframes if required QDomNodeList params = effect.elementsByTagName(QStringLiteral("parameter")); for (int i = 0; i < params.count(); ++i) { QDomElement e = params.item(i).toElement(); // Check if this effect has a variable parameter if (e.attribute(QStringLiteral("default")).contains('%')) { double evaluatedValue = EffectsController::getStringEval(m_doc->getProfileInfo(), e.attribute(QStringLiteral("default"))); e.setAttribute(QStringLiteral("default"), evaluatedValue); if (e.hasAttribute(QStringLiteral("value")) && e.attribute(QStringLiteral("value")).startsWith('%')) { e.setAttribute(QStringLiteral("value"), evaluatedValue); } } if (!e.isNull() && (e.attribute(QStringLiteral("type")) == QLatin1String("keyframe") || e.attribute(QStringLiteral("type")) == QLatin1String("simplekeyframe"))) { QString def = e.attribute(QStringLiteral("default")); // Effect has a keyframe type parameter, we need to set the values if (e.attribute(QStringLiteral("keyframes")).isEmpty()) { e.setAttribute(QStringLiteral("keyframes"), "0:" + def + ';'); //qDebug() << "///// EFFECT KEYFRAMES INITED: " << e.attribute("keyframes"); //break; } } if (effect.attribute(QStringLiteral("id")) == QLatin1String("crop")) { // default use_profile to 1 for clips with proxies to avoid problems when rendering if (e.attribute(QStringLiteral("name")) == QLatin1String("use_profile") && m_doc->useProxy()) e.setAttribute(QStringLiteral("value"), QStringLiteral("1")); } } sourceTrack->effectsList.append(effect); if (addToPlaylist) sourceTrack->addTrackEffect(EffectsController::getEffectArgs(m_doc->getProfileInfo(), effect)); } bool Timeline::removeTrackEffect(int trackIndex, int effectIndex, const QDomElement &effect) { if (trackIndex < 0 || trackIndex >= m_tracks.count()) { qWarning() << "Set Track effect outisde of range"; return false; } int toRemove = effect.attribute(QStringLiteral("kdenlive_ix")).toInt(); Track *sourceTrack = track(trackIndex); bool success = sourceTrack->removeTrackEffect(effectIndex, true); if (success) { int max = sourceTrack->effectsList.count(); for (int i = 0; i < max; ++i) { int index = sourceTrack->effectsList.at(i).attribute(QStringLiteral("kdenlive_ix")).toInt(); if (toRemove == index) { sourceTrack->effectsList.removeAt(toRemove); break; } } } return success; } void Timeline::setTrackEffect(int trackIndex, int effectIndex, QDomElement effect) { if (trackIndex < 0 || trackIndex >= m_tracks.count()) { qWarning() << "Set Track effect outisde of range"; return; } Track *sourceTrack = track(trackIndex); int max = sourceTrack->effectsList.count(); if (effectIndex <= 0 || effectIndex > (max) || effect.isNull()) { //qDebug() << "Invalid effect index: " << effectIndex; return; } sourceTrack->effectsList.removeAt(effect.attribute(QStringLiteral("kdenlive_ix")).toInt()); effect.setAttribute(QStringLiteral("kdenlive_ix"), effectIndex); sourceTrack->effectsList.insert(effect); } bool Timeline::enableTrackEffects(int trackIndex, const QList &effectIndexes, bool disable) { if (trackIndex < 0 || trackIndex >= m_tracks.count()) { qWarning() << "Set Track effect outisde of range"; return false; } Track *sourceTrack = track(trackIndex); EffectsList list = sourceTrack->effectsList; QDomElement effect; bool hasVideoEffect = false; for (int i = 0; i < effectIndexes.count(); ++i) { effect = list.itemFromIndex(effectIndexes.at(i)); if (!effect.isNull()) { effect.setAttribute(QStringLiteral("disable"), (int) disable); if (effect.attribute(QStringLiteral("type")) != QLatin1String("audio")) hasVideoEffect = true; } } return hasVideoEffect; } const EffectsList Timeline::getTrackEffects(int trackIndex) { if (trackIndex < 0 || trackIndex >= m_tracks.count()) { qWarning() << "Set Track effect outisde of range"; return EffectsList(); } Track *sourceTrack = track(trackIndex); return sourceTrack->effectsList; } QDomElement Timeline::getTrackEffect(int trackIndex, int effectIndex) { if (trackIndex < 0 || trackIndex >= m_tracks.count()) { qWarning() << "Set Track effect outisde of range"; return QDomElement(); } Track *sourceTrack = track(trackIndex); EffectsList list = sourceTrack->effectsList; if (effectIndex > list.count() || effectIndex < 1 || list.itemFromIndex(effectIndex).isNull()) return QDomElement(); return list.itemFromIndex(effectIndex).cloneNode().toElement(); } int Timeline::hasTrackEffect(int trackIndex, const QString &tag, const QString &id) { if (trackIndex < 0 || trackIndex >= m_tracks.count()) { qWarning() << "Set Track effect outisde of range"; return -1; } Track *sourceTrack = track(trackIndex); EffectsList list = sourceTrack->effectsList; return list.hasEffect(tag, id); } MltVideoProfile Timeline::mltProfile() const { return ProfilesDialog::getVideoProfile(*m_tractor->profile()); } double Timeline::fps() const { return m_doc->fps(); } QPoint Timeline::getTracksCount() { int audioTracks = 0; int videoTracks = 0; int max = m_tracks.count(); for (int i = 0; i < max; i++) { Track *tk = track(i); if (tk->type == AudioTrack) audioTracks++; else videoTracks++; } return QPoint(videoTracks, audioTracks); } int Timeline::getTrackSpaceLength(int trackIndex, int pos, bool fromBlankStart) { if (trackIndex < 0 || trackIndex >= m_tracks.count()) { qWarning() << "Set Track effect outisde of range"; return 0; } return track(trackIndex)->getBlankLength(pos, fromBlankStart); } void Timeline::updateClipProperties(const QString &id, QMap properties) { for (int i = 0; i< m_tracks.count(); i++) { track(i)->updateClipProperties(id, properties); } } int Timeline::changeClipSpeed(ItemInfo info, ItemInfo speedIndependantInfo, PlaylistState::ClipState state, double speed, int strobe, Mlt::Producer *originalProd, bool removeEffect) { QLocale locale; QString url = QString::fromUtf8(originalProd->get("resource")); Track::SlowmoInfo slowInfo; slowInfo.speed = speed; slowInfo.strobe = strobe; slowInfo.state = state; url.prepend(slowInfo.toString(locale)); //if (strobe > 1) url.append("&strobe=" + QString::number(strobe)); Mlt::Producer *prod; if (removeEffect) { // We want to remove framebuffer producer, so pass original prod = originalProd; } else { // Pass slowmotion producer prod = m_doc->renderer()->getSlowmotionProducer(url); } QString id = originalProd->get("id"); id = id.section(QStringLiteral("_"), 0, 0); Mlt::Properties passProperties; Mlt::Properties original(originalProd->get_properties()); passProperties.pass_list(original, ClipController::getPassPropertiesList(false)); return track(info.track)->changeClipSpeed(info, speedIndependantInfo, state, speed, strobe, prod, id, passProperties); } void Timeline::duplicateClipOnPlaylist(int tk, qreal startPos, int offset, Mlt::Producer *prod) { Track *sourceTrack = track(tk); int pos = sourceTrack->frame(startPos); int clipIndex = sourceTrack->playlist().get_clip_index_at(pos); if (sourceTrack->playlist().is_blank(clipIndex)) { qDebug()<<"// ERROR FINDING CLIP on TK: "<playlist().get_clip(clipIndex); Clip clp(clipProducer->parent()); Mlt::Producer *cln = clp.clone(); // Clip effects must be moved from clip to the playlist entry, so first delete them from parent clip Clip(*cln).deleteEffects(); cln->set_in_and_out(clipProducer->get_in(), clipProducer->get_out()); Mlt::Playlist trackPlaylist((mlt_playlist) prod->get_service()); trackPlaylist.lock(); int newIdx = trackPlaylist.insert_at(pos - offset, cln, 1); // Re-add source effects in playlist Mlt::Producer *inPlaylist = trackPlaylist.get_clip(newIdx); if (inPlaylist) { Clip(*inPlaylist).addEffects(*clipProducer); delete inPlaylist; } trackPlaylist.unlock(); delete clipProducer; delete cln; delete prod; } int Timeline::getSpaceLength(const GenTime &pos, int tk, bool fromBlankStart) { Track *sourceTrack = track(tk); if (!sourceTrack) return 0; int insertPos = pos.frames(m_doc->fps()); return sourceTrack->spaceLength(insertPos, fromBlankStart); } void Timeline::disableTimelineEffects(bool disable) { for (int i = 0; i< tracksCount(); i++) { track(i)->disableEffects(disable); } } void Timeline::importPlaylist(ItemInfo info, QMap processedUrl, QMap idMaps, QDomDocument doc, QUndoCommand *command) { projectView()->importPlaylist(info, processedUrl, idMaps, doc, command); } void Timeline::refreshTrackActions() { int tracks = tracksCount(); if (tracks > 3) { return; } foreach(QAction *action, m_trackActions) { if (action->data().toString() == "delete_track") { action->setEnabled(tracks > 2); } } } void Timeline::slotMultitrackView(bool enable) { multitrackView = enable; transitionHandler->enableMultiTrack(enable); } void Timeline::connectOverlayTrack(bool enable) { if (!m_hasOverlayTrack) return; m_tractor->lock(); if (enable) { // Re-add overlaytrack m_tractor->insert_track(*m_overlayTrack, tracksCount() + 1); delete m_overlayTrack; m_overlayTrack = NULL; } else { m_overlayTrack = m_tractor->track(tracksCount()); m_tractor->remove_track(tracksCount()); } m_tractor->unlock(); } void Timeline::removeSplitOverlay() { if (!m_hasOverlayTrack) return; m_tractor->lock(); m_tractor->remove_track(tracksCount()); m_hasOverlayTrack = false; m_tractor->unlock(); } bool Timeline::createOverlay(Mlt::Filter *filter, int tk, int startPos) { Track *sourceTrack = track(tk); if (!sourceTrack) return false; m_tractor->lock(); int clipIndex = sourceTrack->playlist().get_clip_index_at(startPos); Mlt::Producer *clipProducer = sourceTrack->playlist().get_clip(clipIndex); Clip clp(clipProducer->parent()); Mlt::Producer *cln = clp.clone(); Clip(*cln).deleteEffects(); cln->set_in_and_out(clipProducer->get_in(), clipProducer->get_out()); Mlt::Playlist overlay(*m_tractor->profile()); Mlt::Tractor trac(*m_tractor->profile()); trac.set_track(*clipProducer, 0); trac.set_track(*cln, 1); cln->attach(*filter); QString splitTransition = KdenliveSettings::gpu_accel() ? "movit.overlay" : "frei0r.cairoblend"; Mlt::Transition t(*m_tractor->profile(), splitTransition.toUtf8().constData()); t.set("always_active", 1); trac.plant_transition(t, 0, 1); delete cln; delete clipProducer; overlay.insert_blank(0, startPos); Mlt::Producer split(trac.get_producer()); overlay.insert_at(startPos, &split, 1); int trackIndex = tracksCount(); m_tractor->insert_track(overlay, trackIndex); Mlt::Producer *overlayTrack = m_tractor->track(trackIndex); overlayTrack->set("hide", 2); delete overlayTrack; m_hasOverlayTrack = true; m_tractor->unlock(); return true; } void Timeline::switchTrackTarget() { if (!KdenliveSettings::splitaudio()) { // This feature is only available on split mode return; } Track *current = m_tracks.at(m_trackview->selectedTrack()); TrackType trackType = current->info().type; if (trackType == VideoTrack) { if (m_trackview->selectedTrack() == videoTarget) { // Switch off current->trackHeader->switchTarget(false); videoTarget = -1; } else { if (videoTarget > -1) m_tracks.at(videoTarget)->trackHeader->switchTarget(false); current->trackHeader->switchTarget(true); videoTarget = m_trackview->selectedTrack(); } } else if (trackType == AudioTrack) { if (m_trackview->selectedTrack() == audioTarget) { // Switch off current->trackHeader->switchTarget(false); audioTarget = -1; } else { if (audioTarget > -1) m_tracks.at(audioTarget)->trackHeader->switchTarget(false); current->trackHeader->switchTarget(true); audioTarget = m_trackview->selectedTrack(); } } } void Timeline::slotEnableZone(bool enable) { KdenliveSettings::setUseTimelineZoneToEdit(enable); m_ruler->activateZone(); } void Timeline::gotPreviewRender(int frame, const QString &file, int progress) { if (!m_hasOverlayTrack) { // Create overlay track Mlt::Playlist overlay(*m_tractor->profile()); int trackIndex = tracksCount(); m_tractor->lock(); m_tractor->insert_track(overlay, trackIndex); m_tractor->unlock(); m_hasOverlayTrack = true; } if (file.isEmpty()) { m_doc->previewProgress(progress); return; } Mlt::Producer *overlayTrack = m_tractor->track(tracksCount()); m_tractor->lock(); Mlt::Playlist trackPlaylist((mlt_playlist) overlayTrack->get_service()); delete overlayTrack; if (trackPlaylist.is_blank_at(frame)) { Mlt::Producer prod(*m_tractor->profile(), 0, file.toUtf8().constData()); if (prod.is_valid()) { - m_ruler->updatePreview(frame); + m_ruler->updatePreview(frame, true, true); prod.set("mlt_service", "avformat-novalidate"); trackPlaylist.insert_at(frame, &prod, 1); } } + trackPlaylist.consolidate_blanks(); m_tractor->unlock(); m_doc->previewProgress(progress); m_doc->setModified(true); } void Timeline::addPreviewRange(bool add) { QPoint p = m_doc->zone(); int chunkSize = KdenliveSettings::timelinechunks(); int startChunk = p.x() / chunkSize; int endChunk = rintl(p.y() / chunkSize); QList frames; for (int i = startChunk; i <= endChunk; i++) { frames << i * chunkSize; } m_ruler->addChunks(frames, add); - if (KdenliveSettings::autopreview()) + if (add && KdenliveSettings::autopreview()) m_previewTimer.start(); } void Timeline::startPreviewRender() { if (!m_ruler->hasPreviewRange() && !KdenliveSettings::autopreview()) { addPreviewRange(true); } QList chunks = m_ruler->getDirtyChunks(); if (!chunks.isEmpty()) { QString cacheDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation); QString documentId = m_doc->getDocumentProperty(QStringLiteral("documentid")); QString docParams = m_doc->getDocumentProperty(QStringLiteral("previewparameters")); if (docParams.isEmpty()) { m_doc->selectPreviewProfile(); docParams = m_doc->getDocumentProperty(QStringLiteral("previewparameters")); } if (docParams.isEmpty()) { KMessageBox::sorry(this, i18n("No available preview profile found, please check the Timeline Settings")); return; } m_doc->renderer()->previewRendering(chunks, cacheDir, documentId, docParams.split(" "), m_doc->getDocumentProperty(QStringLiteral("previewextension"))); } } void Timeline::stopPreviewRender() { m_doc->renderer()->abortPreview(); } void Timeline::invalidateRange(ItemInfo info) { if (info.isValid()) invalidatePreview(info.startPos.frames(m_doc->fps()), info.endPos.frames(m_doc->fps())); else { invalidatePreview(0, m_trackview->duration()); } } void Timeline::invalidatePreview(int startFrame, int endFrame) { if (!m_hasOverlayTrack) return; int chunkSize = KdenliveSettings::timelinechunks(); int start = startFrame / chunkSize; int end = lrintf(endFrame / chunkSize); Mlt::Producer *overlayTrack = m_tractor->track(tracksCount()); m_tractor->lock(); Mlt::Playlist trackPlaylist((mlt_playlist) overlayTrack->get_service()); delete overlayTrack; QList list; for (int i = start; i <=end; i++) { int ix = trackPlaylist.get_clip_index_at(chunkSize * i); if (trackPlaylist.is_blank(ix)) continue; list << i * chunkSize; Mlt::Producer *prod = trackPlaylist.replace_with_blank(ix); delete prod; m_ruler->updatePreview(i * chunkSize, false); } + m_ruler->update(); trackPlaylist.consolidate_blanks(); m_tractor->unlock(); - m_doc->invalidatePreviews(list); + m_previewGatherTimer.start(); +} + +void Timeline::slotProcessDirtyChunks() +{ + m_doc->invalidatePreviews(m_ruler->getDirtyChunks()); if (KdenliveSettings::autopreview()) m_previewTimer.start(); } void Timeline::loadPreviewRender() { QString documentId = m_doc->getDocumentProperty(QStringLiteral("documentid")); QString chunks = m_doc->getDocumentProperty(QStringLiteral("previewchunks")); QString dirty = m_doc->getDocumentProperty(QStringLiteral("dirtypreviewchunks")); + QString ext = m_doc->getDocumentProperty(QStringLiteral("previewextension")); if (!chunks.isEmpty() || !dirty.isEmpty()) { if (!m_hasOverlayTrack) { // Create overlay track Mlt::Playlist overlay(*m_tractor->profile()); int trackIndex = tracksCount(); m_tractor->lock(); m_tractor->insert_track(overlay, trackIndex); m_tractor->unlock(); m_hasOverlayTrack = true; } - QDir dir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); + QDir dir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) ); QStringList previewChunks = chunks.split(",", QString::SkipEmptyParts); QStringList dirtyChunks = dirty.split(",", QString::SkipEmptyParts); foreach(const QString frame, previewChunks) { int pos = frame.toInt(); - const QString fileName = dir.absoluteFilePath(documentId + QString("-%1.%2").arg(pos).arg(KdenliveSettings::tl_extension())); + const QString fileName = dir.absoluteFilePath(documentId + QString("-%1.%2").arg(pos).arg(ext)); if (QFile::exists(fileName)) { gotPreviewRender(pos, fileName, 1000); } else dirtyChunks << frame; } if (!dirtyChunks.isEmpty()) { foreach(const QString i, dirtyChunks) { m_ruler->updatePreview(i.toInt(), false); } + m_ruler->update(); } } } void Timeline::updatePreviewSettings(const QString &profile) { if (profile.isEmpty()) return; QString params = profile.section(";", 0, 0); QString ext = profile.section(";", 1, 1); if (params != m_doc->getDocumentProperty(QStringLiteral("previewparameters")) || ext != m_doc->getDocumentProperty(QStringLiteral("previewextension"))) { // Timeline preview params changed, delete all existing previews. invalidateRange(ItemInfo()); m_doc->setDocumentProperty(QStringLiteral("previewparameters"), params); m_doc->setDocumentProperty(QStringLiteral("previewextension"), ext); } } + +void Timeline::slotReloadChunks(QList chunks) +{ + bool timer = false; + if (m_previewTimer.isActive()) { + m_previewTimer.stop(); + timer = true; + } + QString documentId = m_doc->getDocumentProperty(QStringLiteral("documentid")); + QString ext = m_doc->getDocumentProperty(QStringLiteral("previewextension")); + QDir dir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); + Mlt::Producer *overlayTrack = m_tractor->track(tracksCount()); + m_tractor->lock(); + Mlt::Playlist trackPlaylist((mlt_playlist) overlayTrack->get_service()); + delete overlayTrack; + foreach(int ix, chunks) { + if (trackPlaylist.is_blank_at(ix)) { + const QString fileName = dir.absoluteFilePath(documentId + QString("-%1.%2").arg(ix).arg(ext)); + Mlt::Producer prod(*m_tractor->profile(), 0, fileName.toUtf8().constData()); + if (prod.is_valid()) { + m_ruler->updatePreview(ix, true, true); + prod.set("mlt_service", "avformat-novalidate"); + trackPlaylist.insert_at(ix, &prod, 1); + } + } + } + trackPlaylist.consolidate_blanks(); + m_tractor->unlock(); + if (timer) + m_previewTimer.start(); +} diff --git a/src/timeline/timeline.h b/src/timeline/timeline.h index fbf443237..31e4f5346 100644 --- a/src/timeline/timeline.h +++ b/src/timeline/timeline.h @@ -1,272 +1,276 @@ /*************************************************************************** * 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 * ***************************************************************************/ /** * @class Timeline * @brief Manages the timline * @author Jean-Baptiste Mardelle */ #ifndef TRACKVIEW_H #define TRACKVIEW_H #include "timeline/customtrackscene.h" #include "effectslist/effectslist.h" #include "ui_timeline_ui.h" #include "definitions.h" #include #include #include #include #include class Track; class ClipItem; class CustomTrackView; class KdenliveDoc; class TransitionHandler; class CustomRuler; class QUndoCommand; class Timeline : public QWidget, public Ui::TimeLine_UI { Q_OBJECT public: explicit Timeline(KdenliveDoc *doc, const QList & actions, const QList &rulerActions, bool *ok, QWidget *parent = 0); virtual ~ Timeline(); /** @brief is multitrack view (split screen for tracks) enabled */ bool multitrackView; int videoTarget; int audioTarget; Track* track(int i); /** @brief Number of tracks in the MLT playlist. */ int tracksCount() const; /** @brief Number of visible tracks (= tracksCount() - 1 ) because black trck is not visible to user. */ int visibleTracksCount() const; void setEditMode(const QString & editMode); const QString & editMode() const; QGraphicsScene *projectScene(); CustomTrackView *projectView(); int duration() const; KdenliveDoc *document(); void refresh() ; int outPoint() const; int inPoint() const; int fitZoom() const; /** @brief This object handles all transition operation. */ TransitionHandler *transitionHandler; void lockTrack(int ix, bool lock); bool isTrackLocked(int ix); /** @brief Dis / enable video for a track. */ void switchTrackVideo(int ix, bool hide); /** @brief Dis / enable audio for a track. */ void switchTrackAudio(int ix, bool mute); /** @brief Adjust audio transitions depending on tracks muted state. */ void fixAudioMixing(); /** @brief Updates (redraws) the ruler. * * Used to change from displaying frames to timecode or vice versa. */ void updateRuler(); /** @brief Parse tracks to see if project has audio in it. * * Parses all tracks to check if there is audio data. */ bool checkProjectAudio(); /** @brief Load guides from data */ void loadGuides(QMap guidesData); void checkTrackHeight(bool force = false); void updatePalette(); void refreshIcons(); /** @brief Returns a kdenlive effect xml description from an effect tag / id */ static QDomElement getEffectByTag(const QString &effecttag, const QString &effectid); /** @brief Move a clip between tracks */ bool moveClip(int startTrack, qreal startPos, int endTrack, qreal endPos, PlaylistState::ClipState state, TimelineMode::EditMode mode, bool duplicate); void renameTrack(int ix, const QString &name); void updateTrackState(int ix, int state); /** @brief Returns info about a track. * @param ix The track number in MLT's coordinates (0 = black track, 1 = bottom audio, etc) * deprecated use string version with track name instead */ TrackInfo getTrackInfo(int ix); void setTrackInfo(int trackIndex, TrackInfo info); QList getTracksInfo(); QStringList getTrackNames(); void addTrackEffect(int trackIndex, QDomElement effect, bool addToPlaylist = true); bool removeTrackEffect(int trackIndex, int effectIndex, const QDomElement &effect); void setTrackEffect(int trackIndex, int effectIndex, QDomElement effect); bool enableTrackEffects(int trackIndex, const QList &effectIndexes, bool disable); const EffectsList getTrackEffects(int trackIndex); QDomElement getTrackEffect(int trackIndex, int effectIndex); int hasTrackEffect(int trackIndex, const QString &tag, const QString &id); MltVideoProfile mltProfile() const; double fps() const; QPoint getTracksCount(); /** @brief Check if we have a blank space on selected track. * Returns -1 if track is shorter, 0 if not blank and > 0 for blank length */ int getTrackSpaceLength(int trackIndex, int pos, bool fromBlankStart); void updateClipProperties(const QString &id, QMap properties); int changeClipSpeed(ItemInfo info, ItemInfo speedIndependantInfo, PlaylistState::ClipState state, double speed, int strobe, Mlt::Producer *originalProd, bool removeEffect = false); /** @brief Set an effect's XML accordingly to MLT::filter values. */ static void setParam(ProfileInfo info, QDomElement param, QString value); int getTracks(); void getTransitions(); void refreshTractor(); void duplicateClipOnPlaylist(int tk, qreal startPos, int offset, Mlt::Producer *prod); int getSpaceLength(const GenTime &pos, int tk, bool fromBlankStart); void blockTrackSignals(bool block); /** @brief Load document */ void loadTimeline(); /** @brief Dis/enable all effects in timeline*/ void disableTimelineEffects(bool disable); QString getKeyframes(Mlt::Service service, int &ix, QDomElement e); void getSubfilters(Mlt::Filter *effect, QDomElement ¤teffect); static bool isSlide(QString geometry); /** @brief Import amultitrack MLT playlist in timeline */ void importPlaylist(ItemInfo info, QMap processedUrl, QMap idMaps, QDomDocument doc, QUndoCommand *command); /** @brief Creates an overlay track with a filtered clip */ bool createOverlay(Mlt::Filter *filter, int tk, int startPos); void removeSplitOverlay(); /** @brief Temporarily add/remove track before saving */ void connectOverlayTrack(bool enable); /** @brief Update composite transitions's tracks */ void updateComposites(); /** @brief Switch current track target state */ void switchTrackTarget(); /** @brief Refresh Header Leds */ void updateHeaders(); /** @brief Returns true if position is on the last clip */ bool isLastClip(ItemInfo info); /** @brief find lowest video track in timeline. */ int getLowestVideoTrack(); /** @brief Returns the document properties with some added values from timeline. */ QMap documentProperties(); void reloadTrack(int ix, int start = 0, int end = -1); /** @brief Invalidate a preview rendering range. */ void invalidateRange(ItemInfo info = ItemInfo()); /** @brief Add or remove current timeline zone to preview render zone. */ void addPreviewRange(bool add); /** @brief Check if timeline preview profile changed and remove preview files if necessary. */ void updatePreviewSettings(const QString &profile); protected: void keyPressEvent(QKeyEvent * event); public slots: void slotDeleteClip(const QString &clipId, QUndoCommand *deleteCommand); void slotChangeZoom(int horizontal, int vertical = -1); void setDuration(int dur); void slotSetZone(const QPoint &p, bool updateDocumentProperties = true); /** @brief Save a snapshot image of current timeline view */ void slotSaveTimelinePreview(const QString &path); void checkDuration(int duration); void slotShowTrackEffects(int); void updateProfile(bool fpsChanged); /** @brief Enable/disable multitrack view (split monitor in 4) */ void slotMultitrackView(bool enable); /** @brief Start rendering preview rendering range. */ void startPreviewRender(); /** @brief Stop rendering preview. */ void stopPreviewRender(); private: Mlt::Tractor *m_tractor; QList m_tracks; /** @brief number of special overlay tracks to preview effects */ bool m_hasOverlayTrack; Mlt::Producer *m_overlayTrack; CustomRuler *m_ruler; CustomTrackView *m_trackview; QList m_invalidProducers; double m_scale; QString m_editMode; CustomTrackScene *m_scene; /** @brief A list of producer ids to be replaced when opening a corrupted document*/ QMap m_replacementProducerIds; KdenliveDoc *m_doc; int m_verticalZoom; QString m_documentErrors; QList m_trackActions; + /** @brief sometimes grouped commands quickly send invalidate commands, so wait a little bit before processing*/ + QTimer m_previewGatherTimer; QTimer m_previewTimer; void adjustTrackHeaders(); void parseDocument(const QDomDocument &doc); int loadTrack(int ix, int offset, Mlt::Playlist &playlist, int start = 0, int end = -1, bool updateReferences = true); void getEffects(Mlt::Service &service, ClipItem *clip, int track = 0); void adjustDouble(QDomElement &e, const QString &value); /** @brief Adjust kdenlive effect xml parameters to the MLT value*/ void adjustparameterValue(QDomNodeList clipeffectparams, const QString ¶mname, const QString ¶mvalue); /** @brief Enable/disable track actions depending on number of tracks */ void refreshTrackActions(); /** @brief load existing timeline previews */ void loadPreviewRender(); private slots: void slotSwitchTrackComposite(int trackIndex, bool enable); void setCursorPos(int pos); void moveCursorPos(int pos); /** @brief The tracks count or a track name changed, rebuild and notify */ void slotReloadTracks(); void slotVerticalZoomDown(); void slotVerticalZoomUp(); /** @brief Changes the name of a track. * @param ix Number of the track * @param name New name */ void slotRenameTrack(int ix, const QString &name); void slotRepaintTracks(); /** @brief Adjusts the margins of the header area. * * Avoid a shift between header area and trackview if * the horizontal scrollbar is visible and the position * of the vertical scrollbar is maximal */ void slotUpdateVerticalScroll(int min, int max); /** @brief Update the track label showing applied effects.*/ void slotUpdateTrackEffectState(int); /** @brief Toggle use of timeline zone for editing.*/ void slotEnableZone(bool enable); void gotPreviewRender(int frame, const QString &file, int progress); void invalidatePreview(int startFrame, int endFrame); + void slotReloadChunks(QList chunks); + void slotProcessDirtyChunks(); signals: void mousePosition(int); void cursorMoved(); void zoneMoved(int, int); void configTrack(); void updateTracksInfo(); void setZoom(int); void showTrackEffects(int, const TrackInfo&); /** @brief Indicate how many clips we are going to load */ void startLoadingBin(int); /** @brief Indicate which clip we are currently loading */ void loadingBin(int); /** @brief We are about to reload timeline, reset bin clip usage */ void resetUsageCount(); }; #endif