diff --git a/src/assets/assetlist/model/assetfilter.hpp b/src/assets/assetlist/model/assetfilter.hpp index 1761c0709..11172e5a4 100644 --- a/src/assets/assetlist/model/assetfilter.hpp +++ b/src/assets/assetlist/model/assetfilter.hpp @@ -1,71 +1,70 @@ /*************************************************************************** * Copyright (C) 2017 by Nicolas Carion * * This file is part of Kdenlive. See www.kdenlive.org. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) version 3 or any later version accepted by the * * membership of KDE e.V. (or its successor approved by the membership * * of KDE e.V.), which shall act as a proxy defined in Section 14 of * * version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #ifndef ASSETFILTER_H #define ASSETFILTER_H -#include "effects/effectsrepository.hpp" #include #include /* @brief This class is used as a proxy model to filter an asset list based on given criterion (name, ...) */ class TreeItem; class AssetFilter : public QSortFilterProxyModel { Q_OBJECT public: AssetFilter(QObject *parent = nullptr); /* @brief Manage the name filter @param enabled whether to enable this filter @param pattern to match against effects' names */ void setFilterName(bool enabled, const QString &pattern); /** @brief Returns true if the ModelIndex in the source model is visible after filtering */ bool isVisible(const QModelIndex &sourceIndex); /** @brief If we are in favorite view, invalidate filter to refresh. Call this after a favorite has changed */ virtual void reloadFilterOnFavorite() = 0; QVariantList getCategories(); Q_INVOKABLE QModelIndex getNextChild(const QModelIndex ¤t); Q_INVOKABLE QModelIndex getPreviousChild(const QModelIndex ¤t); Q_INVOKABLE QModelIndex firstVisibleItem(const QModelIndex ¤t); Q_INVOKABLE QModelIndex getCategory(int catRow); Q_INVOKABLE QModelIndex getModelIndex(QModelIndex current); Q_INVOKABLE QModelIndex getProxyIndex(QModelIndex current); protected: bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; bool filterName(const std::shared_ptr &item) const; /* @brief Apply all filter and returns true if the object should be kept after filtering */ virtual bool applyAll(std::shared_ptr item) const; bool m_name_enabled; QString m_name_value; }; #endif diff --git a/src/assets/assetlist/view/assetlistwidget.cpp b/src/assets/assetlist/view/assetlistwidget.cpp index d9fdfdf5a..980c0a152 100644 --- a/src/assets/assetlist/view/assetlistwidget.cpp +++ b/src/assets/assetlist/view/assetlistwidget.cpp @@ -1,107 +1,109 @@ /*************************************************************************** * Copyright (C) 2017 by Nicolas Carion * * This file is part of Kdenlive. See www.kdenlive.org. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) version 3 or any later version accepted by the * * membership of KDE e.V. (or its successor approved by the membership * * of KDE e.V.), which shall act as a proxy defined in Section 14 of * * version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "assetlistwidget.hpp" +#include "assets/assetlist/model/assetfilter.hpp" +#include "assets/assetlist/model/assettreemodel.hpp" #include "assets/assetlist/view/qmltypes/asseticonprovider.hpp" #include #include #include #include #include AssetListWidget::AssetListWidget(QWidget *parent) : QQuickWidget(parent) , m_assetIconProvider(nullptr) { KDeclarative::KDeclarative kdeclarative; kdeclarative.setDeclarativeEngine(engine()); #if KDECLARATIVE_VERSION >= QT_VERSION_CHECK(5, 45, 0) kdeclarative.setupEngine(engine()); kdeclarative.setupContext(); #else kdeclarative.setupBindings(); #endif } AssetListWidget::~AssetListWidget() { // clear source setSource(QUrl()); } void AssetListWidget::setup() { setResizeMode(QQuickWidget::SizeRootObjectToView); engine()->addImageProvider(QStringLiteral("asseticon"), m_assetIconProvider); setSource(QUrl(QStringLiteral("qrc:/qml/assetList.qml"))); setFocusPolicy(Qt::StrongFocus); } void AssetListWidget::reset() { setSource(QUrl(QStringLiteral("qrc:/qml/assetList.qml"))); } QString AssetListWidget::getName(const QModelIndex &index) const { return m_model->getName(m_proxyModel->mapToSource(index)); } bool AssetListWidget::isFavorite(const QModelIndex &index) const { return m_model->isFavorite(m_proxyModel->mapToSource(index)); } void AssetListWidget::setFavorite(const QModelIndex &index, bool favorite, bool isEffect) { m_model->setFavorite(m_proxyModel->mapToSource(index), favorite, isEffect); } QString AssetListWidget::getDescription(const QModelIndex &index) const { return m_model->getDescription(m_proxyModel->mapToSource(index)); } void AssetListWidget::setFilterName(const QString &pattern) { m_proxyModel->setFilterName(!pattern.isEmpty(), pattern); if (!pattern.isEmpty()) { QVariantList mapped = m_proxyModel->getCategories(); QMetaObject::invokeMethod(rootObject(), "expandNodes", Qt::DirectConnection, Q_ARG(QVariant, mapped)); } } QVariantMap AssetListWidget::getMimeData(const QString &assetId) const { QVariantMap mimeData; mimeData.insert(getMimeType(assetId), assetId); return mimeData; } void AssetListWidget::activate(const QModelIndex &ix) { if (!ix.isValid()) { return; } const QString assetId = m_model->data(m_proxyModel->mapToSource(ix), AssetTreeModel::IdRole).toString(); emit activateAsset(getMimeData(assetId)); } diff --git a/src/assets/assetlist/view/assetlistwidget.hpp b/src/assets/assetlist/view/assetlistwidget.hpp index 74919b06a..61a90039d 100644 --- a/src/assets/assetlist/view/assetlistwidget.hpp +++ b/src/assets/assetlist/view/assetlistwidget.hpp @@ -1,82 +1,83 @@ /*************************************************************************** * Copyright (C) 2017 by Nicolas Carion * * This file is part of Kdenlive. See www.kdenlive.org. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) version 3 or any later version accepted by the * * membership of KDE e.V. (or its successor approved by the membership * * of KDE e.V.), which shall act as a proxy defined in Section 14 of * * version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #ifndef ASSETLISTWIDGET_H #define ASSETLISTWIDGET_H -#include "../model/assetfilter.hpp" -#include "../model/assettreemodel.hpp" -#include "assets/assetlist/view/qmltypes/asseticonprovider.hpp" #include "effects/effectsrepository.hpp" #include #include /* @brief This class is a generic widget that display the list of available assets */ +class AssetIconProvider; +class AssetFilter; +class AssetTreeModel; + class AssetListWidget : public QQuickWidget { Q_OBJECT /* @brief Should the descriptive info box be displayed */ public: AssetListWidget(QWidget *parent = Q_NULLPTR); virtual ~AssetListWidget(); /* @brief Returns the name of the asset given its model index */ QString getName(const QModelIndex &index) const; /* @brief Returns true if this effect belongs to favorites */ bool isFavorite(const QModelIndex &index) const; /* @brief Returns true if this effect belongs to favorites */ void setFavorite(const QModelIndex &index, bool favorite = true, bool isEffect = true); /* @brief Returns the description of the asset given its model index */ QString getDescription(const QModelIndex &index) const; /* @brief Sets the pattern against which the assets' names are filtered */ void setFilterName(const QString &pattern); /*@brief Return mime type used for drag and drop. It can be kdenlive/effect, kdenlive/composition or kdenlive/transition*/ virtual QString getMimeType(const QString &assetId) const = 0; QVariantMap getMimeData(const QString &assetId) const; void activate(const QModelIndex &ix); /* @brief Rebuild the view by resetting the source. Is there a better way? */ void reset(); protected: void setup(); std::shared_ptr m_model; std::unique_ptr m_proxyModel; // the QmlEngine takes ownership of the image provider AssetIconProvider *m_assetIconProvider; signals: void activateAsset(const QVariantMap data); }; #endif diff --git a/src/effects/effectlist/model/effectfilter.hpp b/src/effects/effectlist/model/effectfilter.hpp index 709ba0a97..e0acce0a3 100644 --- a/src/effects/effectlist/model/effectfilter.hpp +++ b/src/effects/effectlist/model/effectfilter.hpp @@ -1,53 +1,54 @@ /*************************************************************************** * Copyright (C) 2017 by Nicolas Carion * * This file is part of Kdenlive. See www.kdenlive.org. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) version 3 or any later version accepted by the * * membership of KDE e.V. (or its successor approved by the membership * * of KDE e.V.), which shall act as a proxy defined in Section 14 of * * version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #ifndef EFFECTFILTER_H #define EFFECTFILTER_H #include "assets/assetlist/model/assetfilter.hpp" +#include "effects/effectsrepository.hpp" #include #include /* @brief This class is used as a proxy model to filter the effect tree based on given criterion (name, type). It simply adds a filter of type */ class EffectFilter : public AssetFilter { Q_OBJECT public: EffectFilter(QObject *parent = nullptr); /* @brief Manage the type filter @param enabled whether to enable this filter @param type Effect type to display */ void setFilterType(bool enabled, EffectType type); void reloadFilterOnFavorite() override; protected: bool filterType(const std::shared_ptr &item) const; bool applyAll(std::shared_ptr item) const override; bool m_type_enabled; EffectType m_type_value; }; #endif diff --git a/src/project/projectmanager.cpp b/src/project/projectmanager.cpp index c71cd6c42..442a97b46 100644 --- a/src/project/projectmanager.cpp +++ b/src/project/projectmanager.cpp @@ -1,954 +1,958 @@ /* Copyright (C) 2014 Till Theato This file is part of kdenlive. See www.kdenlive.org. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. */ #include "projectmanager.h" #include "bin/bin.h" #include "bin/projectitemmodel.h" #include "core.h" #include "doc/kdenlivedoc.h" #include "jobs/jobmanager.h" #include "kdenlivesettings.h" #include "mainwindow.h" #include "monitor/monitormanager.h" #include "profiles/profilemodel.hpp" #include "project/dialogs/archivewidget.h" #include "project/dialogs/backupwidget.h" #include "project/dialogs/noteswidget.h" #include "project/dialogs/projectsettings.h" #include "utils/thumbnailcache.hpp" +#include "xml/xml.hpp" + // Temporary for testing #include "bin/model/markerlistmodel.hpp" #include "profiles/profilerepository.hpp" #include "project/notesplugin.h" #include "timeline2/model/builders/meltBuilder.hpp" #include "timeline2/view/timelinecontroller.h" #include "timeline2/view/timelinewidget.h" #include #include #include #include #include #include "kdenlive_debug.h" #include #include #include #include #include #include #include #include #include ProjectManager::ProjectManager(QObject *parent) : QObject(parent) , m_project(nullptr) , m_progressDialog(nullptr) { m_fileRevert = KStandardAction::revert(this, SLOT(slotRevert()), pCore->window()->actionCollection()); m_fileRevert->setIcon(QIcon::fromTheme(QStringLiteral("document-revert"))); m_fileRevert->setEnabled(false); QAction *a = KStandardAction::open(this, SLOT(openFile()), pCore->window()->actionCollection()); a->setIcon(QIcon::fromTheme(QStringLiteral("document-open"))); a = KStandardAction::saveAs(this, SLOT(saveFileAs()), pCore->window()->actionCollection()); a->setIcon(QIcon::fromTheme(QStringLiteral("document-save-as"))); a = KStandardAction::openNew(this, SLOT(newFile()), pCore->window()->actionCollection()); a->setIcon(QIcon::fromTheme(QStringLiteral("document-new"))); m_recentFilesAction = KStandardAction::openRecent(this, SLOT(openFile(QUrl)), pCore->window()->actionCollection()); QAction *backupAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-undo")), i18n("Open Backup File"), this); pCore->window()->addAction(QStringLiteral("open_backup"), backupAction); connect(backupAction, SIGNAL(triggered(bool)), SLOT(slotOpenBackup())); m_notesPlugin = new NotesPlugin(this); m_autoSaveTimer.setSingleShot(true); connect(&m_autoSaveTimer, &QTimer::timeout, this, &ProjectManager::slotAutoSave); // Ensure the default data folder exist QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)); dir.mkpath(QStringLiteral(".backup")); dir.mkdir(QStringLiteral("titles")); } ProjectManager::~ProjectManager() {} void ProjectManager::slotLoadOnOpen() { if (m_startUrl.isValid()) { openFile(); } else if (KdenliveSettings::openlastproject()) { openLastFile(); } else { newFile(false); } if (!m_loadClipsOnOpen.isEmpty() && (m_project != nullptr)) { const QStringList list = m_loadClipsOnOpen.split(QLatin1Char(',')); QList urls; urls.reserve(list.count()); for (const QString &path : list) { // qCDebug(KDENLIVE_LOG) << QDir::current().absoluteFilePath(path); urls << QUrl::fromLocalFile(QDir::current().absoluteFilePath(path)); } pCore->bin()->droppedUrls(urls); } m_loadClipsOnOpen.clear(); } void ProjectManager::init(const QUrl &projectUrl, const QString &clipList) { m_startUrl = projectUrl; m_loadClipsOnOpen = clipList; } void ProjectManager::newFile(bool showProjectSettings) { QString profileName = KdenliveSettings::default_profile(); if (profileName.isEmpty()) { profileName = pCore->getCurrentProfile()->path(); } newFile(profileName, showProjectSettings); } void ProjectManager::newFile(QString profileName, bool showProjectSettings) { // fix mantis#3160 QUrl startFile = QUrl::fromLocalFile(KdenliveSettings::defaultprojectfolder() + QStringLiteral("/_untitled.kdenlive")); if (checkForBackupFile(startFile, true)) { return; } m_fileRevert->setEnabled(false); QString projectFolder; QMap documentProperties; QMap documentMetadata; QPoint projectTracks(KdenliveSettings::videotracks(), KdenliveSettings::audiotracks()); pCore->monitorManager()->resetDisplay(); QString documentId = QString::number(QDateTime::currentMSecsSinceEpoch()); documentProperties.insert(QStringLiteral("documentid"), documentId); if (!showProjectSettings) { if (!closeCurrentDocument()) { return; } if (KdenliveSettings::customprojectfolder()) { projectFolder = KdenliveSettings::defaultprojectfolder(); if (!projectFolder.endsWith(QLatin1Char('/'))) { projectFolder.append(QLatin1Char('/')); } documentProperties.insert(QStringLiteral("storagefolder"), projectFolder + documentId); } } else { QPointer w = new ProjectSettings(nullptr, QMap(), QStringList(), projectTracks.x(), projectTracks.y(), KdenliveSettings::defaultprojectfolder(), false, true, pCore->window()); connect(w.data(), &ProjectSettings::refreshProfiles, pCore->window(), &MainWindow::slotRefreshProfiles); if (w->exec() != QDialog::Accepted) { delete w; return; } if (!closeCurrentDocument()) { delete w; return; } if (KdenliveSettings::videothumbnails() != w->enableVideoThumbs()) { pCore->window()->slotSwitchVideoThumbs(); } if (KdenliveSettings::audiothumbnails() != w->enableAudioThumbs()) { pCore->window()->slotSwitchAudioThumbs(); } profileName = w->selectedProfile(); projectFolder = w->storageFolder(); projectTracks = w->tracks(); documentProperties.insert(QStringLiteral("enableproxy"), QString::number((int)w->useProxy())); documentProperties.insert(QStringLiteral("generateproxy"), QString::number((int)w->generateProxy())); documentProperties.insert(QStringLiteral("proxyminsize"), QString::number(w->proxyMinSize())); documentProperties.insert(QStringLiteral("proxyparams"), w->proxyParams()); documentProperties.insert(QStringLiteral("proxyextension"), w->proxyExtension()); documentProperties.insert(QStringLiteral("generateimageproxy"), QString::number((int)w->generateImageProxy())); QString preview = w->selectedPreview(); if (!preview.isEmpty()) { documentProperties.insert(QStringLiteral("previewparameters"), preview.section(QLatin1Char(';'), 0, 0)); documentProperties.insert(QStringLiteral("previewextension"), preview.section(QLatin1Char(';'), 1, 1)); } documentProperties.insert(QStringLiteral("proxyimageminsize"), QString::number(w->proxyImageMinSize())); if (!projectFolder.isEmpty()) { if (!projectFolder.endsWith(QLatin1Char('/'))) { projectFolder.append(QLatin1Char('/')); } documentProperties.insert(QStringLiteral("storagefolder"), projectFolder + documentId); } documentMetadata = w->metadata(); delete w; } bool openBackup; m_notesPlugin->clear(); documentProperties.insert(QStringLiteral("decimalPoint"), QLocale().decimalPoint()); KdenliveDoc *doc = new KdenliveDoc(QUrl(), projectFolder, pCore->window()->m_commandStack, profileName, documentProperties, documentMetadata, projectTracks, &openBackup, pCore->window()); doc->m_autosave = new KAutoSaveFile(startFile, doc); pCore->bin()->setDocument(doc); m_project = doc; pCore->monitorManager()->activateMonitor(Kdenlive::ProjectMonitor); updateTimeline(0); pCore->window()->connectDocument(); bool disabled = m_project->getDocumentProperty(QStringLiteral("disabletimelineeffects")) == QLatin1String("1"); QAction *disableEffects = pCore->window()->actionCollection()->action(QStringLiteral("disable_timeline_effects")); if (disableEffects) { if (disabled != disableEffects->isChecked()) { disableEffects->blockSignals(true); disableEffects->setChecked(disabled); disableEffects->blockSignals(false); } } emit docOpened(m_project); m_lastSave.start(); } bool ProjectManager::closeCurrentDocument(bool saveChanges, bool quit) { if ((m_project != nullptr) && m_project->isModified() && saveChanges) { QString message; if (m_project->url().fileName().isEmpty()) { message = i18n("Save changes to document?"); } else { message = i18n("The project \"%1\" has been changed.\nDo you want to save your changes?", m_project->url().fileName()); } switch (KMessageBox::warningYesNoCancel(pCore->window(), message)) { case KMessageBox::Yes: // save document here. If saving fails, return false; if (!saveFile()) { return false; } break; case KMessageBox::Cancel: return false; break; default: break; } } pCore->window()->getMainTimeline()->controller()->clipActions.clear(); if (!quit && !qApp->isSavingSession()) { m_autoSaveTimer.stop(); if (m_project) { pCore->jobManager()->slotCancelJobs(); pCore->bin()->abortOperations(); pCore->monitorManager()->clipMonitor()->slotOpenClip(nullptr); pCore->window()->clearAssetPanel(); delete m_project; m_project = nullptr; } pCore->monitorManager()->setDocument(m_project); } /* // Make sure to reset locale to system's default QString requestedLocale = QLocale::system().name(); QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); if (env.contains(QStringLiteral("LC_NUMERIC"))) { requestedLocale = env.value(QStringLiteral("LC_NUMERIC")); } qDebug()<<"//////////// RESETTING LOCALE TO: "<decimal_point; if (QString::fromUtf8(separator) != QString(newLocale.decimalPoint())) { pCore->displayBinMessage(i18n("There is a locale conflict on your system, project might get corrupt"), KMessageWidget::Warning); } setlocale(LC_NUMERIC, requestedLocale.toUtf8().constData()); #endif QLocale::setDefault(newLocale);*/ return true; } bool ProjectManager::saveFileAs(const QString &outputFileName) { pCore->monitorManager()->pauseActiveMonitor(); // Sync document properties prepareSave(); QString saveFolder = QFileInfo(outputFileName).absolutePath(); QString scene = projectSceneList(saveFolder); if (!m_replacementPattern.isEmpty()) { QMapIterator i(m_replacementPattern); while (i.hasNext()) { i.next(); scene.replace(i.key(), i.value()); } } if (!m_project->saveSceneList(outputFileName, scene)) { return false; } QUrl url = QUrl::fromLocalFile(outputFileName); // Save timeline thumbnails QStringList thumbKeys = pCore->window()->getMainTimeline()->controller()->getThumbKeys(); ThumbnailCache::get()->saveCachedThumbs(thumbKeys); m_project->setUrl(url); // setting up autosave file in ~/.kde/data/stalefiles/kdenlive/ // saved under file name // actual saving by KdenliveDoc::slotAutoSave() called by a timer 3 seconds after the document has been edited // This timer is set by KdenliveDoc::setModified() const QString projectId = QCryptographicHash::hash(url.fileName().toUtf8(), QCryptographicHash::Md5).toHex(); QUrl autosaveUrl = QUrl::fromLocalFile(QFileInfo(outputFileName).absoluteDir().absoluteFilePath(projectId + QStringLiteral(".kdenlive"))); if (m_project->m_autosave == nullptr) { // The temporary file is not opened or created until actually needed. // The file filename does not have to exist for KAutoSaveFile to be constructed (if it exists, it will not be touched). m_project->m_autosave = new KAutoSaveFile(autosaveUrl, m_project); } else { m_project->m_autosave->setManagedFile(autosaveUrl); } pCore->window()->setWindowTitle(m_project->description()); m_project->setModified(false); m_recentFilesAction->addUrl(url); // remember folder for next project opening KRecentDirs::add(QStringLiteral(":KdenliveProjectsFolder"), saveFolder); saveRecentFiles(); m_fileRevert->setEnabled(true); pCore->window()->m_undoView->stack()->setClean(); return true; } void ProjectManager::saveRecentFiles() { KSharedConfigPtr config = KSharedConfig::openConfig(); m_recentFilesAction->saveEntries(KConfigGroup(config, "Recent Files")); config->sync(); } bool ProjectManager::saveFileAs() { QFileDialog fd(pCore->window()); fd.setDirectory(m_project->url().isValid() ? m_project->url().adjusted(QUrl::RemoveFilename).toLocalFile() : KdenliveSettings::defaultprojectfolder()); fd.setMimeTypeFilters(QStringList() << QStringLiteral("application/x-kdenlive")); fd.setAcceptMode(QFileDialog::AcceptSave); fd.setFileMode(QFileDialog::AnyFile); fd.setDefaultSuffix(QStringLiteral("kdenlive")); if (fd.exec() != QDialog::Accepted || fd.selectedFiles().isEmpty()) { return false; } QString outputFile = fd.selectedFiles().constFirst(); #if KXMLGUI_VERSION_MINOR < 23 && KXMLGUI_VERSION_MAJOR == 5 // Since Plasma 5.7 (release at same time as KF 5.23, // the file dialog manages the overwrite check if (QFile::exists(outputFile)) { // Show the file dialog again if the user does not want to overwrite the file if (KMessageBox::questionYesNo(pCore->window(), i18n("File %1 already exists.\nDo you want to overwrite it?", outputFile)) == KMessageBox::No) { return saveFileAs(); } } #endif bool ok = false; QDir cacheDir = m_project->getCacheDir(CacheBase, &ok); if (ok) { QFile file(cacheDir.absoluteFilePath(QString::fromLatin1(QUrl::toPercentEncoding(QStringLiteral(".") + outputFile)))); file.open(QIODevice::ReadWrite | QIODevice::Text); file.close(); } return saveFileAs(outputFile); } bool ProjectManager::saveFile() { if (!m_project) { // Calling saveFile before a project was created, something is wrong qCDebug(KDENLIVE_LOG) << "SaveFile called without project"; return false; } if (m_project->url().isEmpty()) { return saveFileAs(); } bool result = saveFileAs(m_project->url().toLocalFile()); m_project->m_autosave->resize(0); return result; } void ProjectManager::openFile() { if (m_startUrl.isValid()) { openFile(m_startUrl); m_startUrl.clear(); return; } QUrl url = QFileDialog::getOpenFileUrl(pCore->window(), QString(), QUrl::fromLocalFile(KRecentDirs::dir(QStringLiteral(":KdenliveProjectsFolder"))), getMimeType()); if (!url.isValid()) { return; } KRecentDirs::add(QStringLiteral(":KdenliveProjectsFolder"), url.adjusted(QUrl::RemoveFilename).toLocalFile()); m_recentFilesAction->addUrl(url); saveRecentFiles(); openFile(url); } void ProjectManager::openLastFile() { if (m_recentFilesAction->selectableActionGroup()->actions().isEmpty()) { // No files in history newFile(false); return; } QAction *firstUrlAction = m_recentFilesAction->selectableActionGroup()->actions().last(); if (firstUrlAction) { firstUrlAction->trigger(); } else { newFile(false); } } // fix mantis#3160 separate check from openFile() so we can call it from newFile() // to find autosaved files (in ~/.local/share/stalefiles/kdenlive) and recover it bool ProjectManager::checkForBackupFile(const QUrl &url, bool newFile) { // Check for autosave file that belong to the url we passed in. const QString projectId = QCryptographicHash::hash(url.fileName().toUtf8(), QCryptographicHash::Md5).toHex(); QUrl autosaveUrl = newFile ? url : QUrl::fromLocalFile(QFileInfo(url.path()).absoluteDir().absoluteFilePath(projectId + QStringLiteral(".kdenlive"))); QList staleFiles = KAutoSaveFile::staleFiles(autosaveUrl); KAutoSaveFile *orphanedFile = nullptr; // Check if we can have a lock on one of the file, // meaning it is not handled by any Kdenlive instance if (!staleFiles.isEmpty()) { for (KAutoSaveFile *stale : staleFiles) { if (stale->open(QIODevice::QIODevice::ReadWrite)) { // Found orphaned autosave file orphanedFile = stale; break; } else { // Another Kdenlive instance is probably handling this autosave file staleFiles.removeAll(stale); delete stale; continue; } } } if (orphanedFile) { if (KMessageBox::questionYesNo(nullptr, i18n("Auto-saved files exist. Do you want to recover them now?"), i18n("File Recovery"), KGuiItem(i18n("Recover")), KGuiItem(i18n("Don't recover"))) == KMessageBox::Yes) { doOpenFile(url, orphanedFile); return true; } // remove the stale files for (KAutoSaveFile *stale : staleFiles) { stale->open(QIODevice::ReadWrite); delete stale; } return false; } return false; } void ProjectManager::openFile(const QUrl &url) { QMimeDatabase db; // Make sure the url is a Kdenlive project file QMimeType mime = db.mimeTypeForUrl(url); if (mime.inherits(QStringLiteral("application/x-compressed-tar"))) { // Opening a compressed project file, we need to process it // qCDebug(KDENLIVE_LOG)<<"Opening archive, processing"; QPointer ar = new ArchiveWidget(url); if (ar->exec() == QDialog::Accepted) { openFile(QUrl::fromLocalFile(ar->extractedProjectFile())); } else if (m_startUrl.isValid()) { // we tried to open an invalid file from command line, init new project newFile(false); } delete ar; return; } /*if (!url.fileName().endsWith(".kdenlive")) { // This is not a Kdenlive project file, abort loading KMessageBox::sorry(pCore->window(), i18n("File %1 is not a Kdenlive project file", url.toLocalFile())); if (m_startUrl.isValid()) { // we tried to open an invalid file from command line, init new project newFile(false); } return; }*/ if ((m_project != nullptr) && m_project->url() == url) { return; } if (!closeCurrentDocument()) { return; } if (checkForBackupFile(url)) { return; } pCore->window()->slotGotProgressInfo(i18n("Opening file %1", url.toLocalFile()), 100, InformationMessage); doOpenFile(url, nullptr); } void ProjectManager::doOpenFile(const QUrl &url, KAutoSaveFile *stale) { Q_ASSERT(m_project == nullptr); m_fileRevert->setEnabled(true); delete m_progressDialog; pCore->monitorManager()->resetDisplay(); pCore->monitorManager()->activateMonitor(Kdenlive::ProjectMonitor); m_progressDialog = new QProgressDialog(pCore->window()); m_progressDialog->setWindowTitle(i18n("Loading project")); m_progressDialog->setCancelButton(nullptr); m_progressDialog->setLabelText(i18n("Loading project")); m_progressDialog->setMaximum(0); m_progressDialog->show(); bool openBackup; m_notesPlugin->clear(); KdenliveDoc *doc = new KdenliveDoc(stale ? QUrl::fromLocalFile(stale->fileName()) : url, QString(), pCore->window()->m_commandStack, KdenliveSettings::default_profile().isEmpty() ? pCore->getCurrentProfile()->path() : KdenliveSettings::default_profile(), QMap(), QMap(), QPoint(KdenliveSettings::videotracks(), KdenliveSettings::audiotracks()), &openBackup, pCore->window()); if (stale == nullptr) { const QString projectId = QCryptographicHash::hash(url.fileName().toUtf8(), QCryptographicHash::Md5).toHex(); QUrl autosaveUrl = QUrl::fromLocalFile(QFileInfo(url.path()).absoluteDir().absoluteFilePath(projectId + QStringLiteral(".kdenlive"))); stale = new KAutoSaveFile(autosaveUrl, doc); doc->m_autosave = stale; } else { doc->m_autosave = stale; stale->setParent(doc); // if loading from an autosave of unnamed file then keep unnamed if (url.fileName().contains(QStringLiteral("_untitled.kdenlive"))) { doc->setUrl(QUrl()); } else { doc->setUrl(url); } doc->setModified(true); stale->setParent(doc); } m_progressDialog->setLabelText(i18n("Loading clips")); // TODO refac delete this pCore->bin()->setDocument(doc); QList rulerActions; rulerActions << pCore->window()->actionCollection()->action(QStringLiteral("set_render_timeline_zone")); rulerActions << pCore->window()->actionCollection()->action(QStringLiteral("unset_render_timeline_zone")); rulerActions << pCore->window()->actionCollection()->action(QStringLiteral("clear_render_timeline_zone")); // Set default target tracks to upper audio / lower video tracks m_project = doc; updateTimeline(m_project->getDocumentProperty(QStringLiteral("position")).toInt()); pCore->window()->connectDocument(); QDateTime documentDate = QFileInfo(m_project->url().toLocalFile()).lastModified(); pCore->window()->getMainTimeline()->controller()->loadPreview(m_project->getDocumentProperty(QStringLiteral("previewchunks")), m_project->getDocumentProperty(QStringLiteral("dirtypreviewchunks")), documentDate, m_project->getDocumentProperty(QStringLiteral("disablepreview")).toInt()); emit docOpened(m_project); pCore->window()->slotGotProgressInfo(QString(), 100); if (openBackup) { slotOpenBackup(url); } m_lastSave.start(); delete m_progressDialog; m_progressDialog = nullptr; } void ProjectManager::slotRevert() { if (m_project->isModified() && KMessageBox::warningContinueCancel(pCore->window(), i18n("This will delete all changes made since you last saved your project. Are you sure you want to continue?"), i18n("Revert to last saved version")) == KMessageBox::Cancel) { return; } QUrl url = m_project->url(); if (closeCurrentDocument(false)) { doOpenFile(url, nullptr); } } QString ProjectManager::getMimeType(bool open) { QString mimetype = i18n("Kdenlive project (*.kdenlive)"); if (open) { mimetype.append(QStringLiteral(";;") + i18n("Archived project (*.tar.gz)")); } return mimetype; } KdenliveDoc *ProjectManager::current() { return m_project; } void ProjectManager::slotOpenBackup(const QUrl &url) { QUrl projectFile; QUrl projectFolder; QString projectId; if (url.isValid()) { // we could not open the project file, guess where the backups are projectFolder = QUrl::fromLocalFile(KdenliveSettings::defaultprojectfolder()); projectFile = url; } else { projectFolder = QUrl::fromLocalFile(m_project->projectTempFolder()); projectFile = m_project->url(); projectId = m_project->getDocumentProperty(QStringLiteral("documentid")); } QPointer dia = new BackupWidget(projectFile, projectFolder, projectId, pCore->window()); if (dia->exec() == QDialog::Accepted) { QString requestedBackup = dia->selectedFile(); m_project->backupLastSavedVersion(projectFile.toLocalFile()); closeCurrentDocument(false); doOpenFile(QUrl::fromLocalFile(requestedBackup), nullptr); if (m_project) { m_project->setUrl(projectFile); m_project->setModified(true); pCore->window()->setWindowTitle(m_project->description()); } } delete dia; } KRecentFilesAction *ProjectManager::recentFilesAction() { return m_recentFilesAction; } void ProjectManager::slotStartAutoSave() { if (m_lastSave.elapsed() > 300000) { // If the project was not saved in the last 5 minute, force save m_autoSaveTimer.stop(); slotAutoSave(); } else { m_autoSaveTimer.start(3000); // will trigger slotAutoSave() in 3 seconds } } void ProjectManager::slotAutoSave() { prepareSave(); QString saveFolder = m_project->url().adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).toLocalFile(); QString scene = projectSceneList(saveFolder); if (!m_replacementPattern.isEmpty()) { QMapIterator i(m_replacementPattern); while (i.hasNext()) { i.next(); scene.replace(i.key(), i.value()); } } m_project->slotAutoSave(scene); m_lastSave.start(); } QString ProjectManager::projectSceneList(const QString &outputFolder) { // TODO: re-implement overlay and all // TODO refac: repair this return pCore->monitorManager()->projectMonitor()->sceneList(outputFolder); /*bool multitrackEnabled = m_trackView->multitrackView; if (multitrackEnabled) { // Multitrack view was enabled, disable for auto save m_trackView->slotMultitrackView(false); } m_trackView->connectOverlayTrack(false); QString scene = pCore->monitorManager()->projectMonitor()->sceneList(outputFolder); m_trackView->connectOverlayTrack(true); if (multitrackEnabled) { // Multitrack view was enabled, re-enable for auto save m_trackView->slotMultitrackView(true); } return scene; */ } void ProjectManager::setDocumentNotes(const QString ¬es) { m_notesPlugin->widget()->setHtml(notes); } QString ProjectManager::documentNotes() const { QString text = m_notesPlugin->widget()->toPlainText().simplified(); if (text.isEmpty()) { return QString(); } return m_notesPlugin->widget()->toHtml(); } void ProjectManager::slotAddProjectNote() { m_notesPlugin->widget()->raise(); m_notesPlugin->widget()->setFocus(); m_notesPlugin->widget()->addProjectNote(); } void ProjectManager::prepareSave() { pCore->projectItemModel()->saveDocumentProperties(pCore->window()->getMainTimeline()->controller()->documentProperties(), m_project->metadata(), m_project->getGuideModel()); pCore->projectItemModel()->saveProperty(QStringLiteral("kdenlive:documentnotes"), documentNotes()); pCore->projectItemModel()->saveProperty(QStringLiteral("kdenlive:docproperties.groups"), m_mainTimelineModel->groupsData()); } void ProjectManager::slotResetProfiles() { m_project->resetProfile(); pCore->monitorManager()->resetProfiles(m_project->timecode()); pCore->monitorManager()->updateScopeSource(); } void ProjectManager::slotResetConsumers(bool fullReset) { pCore->monitorManager()->resetConsumers(fullReset); } void ProjectManager::slotExpandClip() { // TODO refac // m_trackView->projectView()->expandActiveClip(); } void ProjectManager::disableBinEffects(bool disable) { if (m_project) { if (disable) { m_project->setDocumentProperty(QStringLiteral("disablebineffects"), QString::number((int)true)); } else { m_project->setDocumentProperty(QStringLiteral("disablebineffects"), QString()); } } pCore->monitorManager()->refreshProjectMonitor(); pCore->monitorManager()->refreshClipMonitor(); } void ProjectManager::slotDisableTimelineEffects(bool disable) { if (disable) { m_project->setDocumentProperty(QStringLiteral("disabletimelineeffects"), QString::number((int)true)); } else { m_project->setDocumentProperty(QStringLiteral("disabletimelineeffects"), QString()); } m_mainTimelineModel->setTimelineEffectsEnabled(!disable); pCore->monitorManager()->refreshProjectMonitor(); } void ProjectManager::slotSwitchTrackLock() { pCore->window()->getMainTimeline()->controller()->switchTrackLock(); } void ProjectManager::slotSwitchAllTrackLock() { pCore->window()->getMainTimeline()->controller()->switchTrackLock(true); } void ProjectManager::slotSwitchTrackTarget() { pCore->window()->getMainTimeline()->controller()->switchTargetTrack(); } QString ProjectManager::getDefaultProjectFormat() { // On first run, lets use an HD1080p profile with fps related to timezone country. Then, when the first video is added to a project, if it does not match // our profile, propose a new default. QTimeZone zone; zone = QTimeZone::systemTimeZone(); QList ntscCountries; ntscCountries << QLocale::Canada << QLocale::Chile << QLocale::CostaRica << QLocale::Cuba << QLocale::DominicanRepublic << QLocale::Ecuador; ntscCountries << QLocale::Japan << QLocale::Mexico << QLocale::Nicaragua << QLocale::Panama << QLocale::Peru << QLocale::Philippines; ntscCountries << QLocale::PuertoRico << QLocale::SouthKorea << QLocale::Taiwan << QLocale::UnitedStates; bool ntscProject = ntscCountries.contains(zone.country()); if (!ntscProject) { return QStringLiteral("atsc_1080p_25"); } return QStringLiteral("atsc_1080p_2997"); } void ProjectManager::saveZone(const QStringList &info, const QDir &dir) { pCore->bin()->saveZone(info, dir); } void ProjectManager::moveProjectData(const QString &src, const QString &dest) { // Move tmp folder (thumbnails, timeline preview) KIO::CopyJob *copyJob = KIO::move(QUrl::fromLocalFile(src), QUrl::fromLocalFile(dest)); connect(copyJob, &KJob::result, this, &ProjectManager::slotMoveFinished); connect(copyJob, SIGNAL(percent(KJob *, ulong)), this, SLOT(slotMoveProgress(KJob *, ulong))); m_project->moveProjectData(src, dest); } void ProjectManager::slotMoveProgress(KJob *, unsigned long progress) { pCore->window()->slotGotProgressInfo(i18n("Moving project folder"), static_cast(progress), ProcessingJobMessage); } void ProjectManager::slotMoveFinished(KJob *job) { if (job->error() == 0) { pCore->window()->slotGotProgressInfo(QString(), 100, InformationMessage); KIO::CopyJob *copyJob = static_cast(job); QString newFolder = copyJob->destUrl().toLocalFile(); // Check if project folder is inside document folder, in which case, paths will be relative QDir projectDir(m_project->url().toString(QUrl::RemoveFilename | QUrl::RemoveScheme)); QDir srcDir(m_project->projectTempFolder()); if (srcDir.absolutePath().startsWith(projectDir.absolutePath())) { m_replacementPattern.insert(QStringLiteral(">proxy/"), QStringLiteral(">") + newFolder + QStringLiteral("/proxy/")); } else { m_replacementPattern.insert(m_project->projectTempFolder() + QStringLiteral("/proxy/"), newFolder + QStringLiteral("/proxy/")); } m_project->setProjectFolder(QUrl::fromLocalFile(newFolder)); saveFile(); m_replacementPattern.clear(); slotRevert(); } else { KMessageBox::sorry(pCore->window(), i18n("Error moving project folder: %1", job->errorText())); } } void ProjectManager::updateTimeline(int pos, int scrollPos) { Q_UNUSED(scrollPos); pCore->jobManager()->slotCancelJobs(); /*qDebug() << "Loading xml"<getProjectXml().constData(); QFile file("/tmp/data.xml"); if (file.open(QIODevice::ReadWrite)) { QTextStream stream(&file); stream << m_project->getProjectXml() << endl; }*/ pCore->window()->getMainTimeline()->loading = true; pCore->window()->slotSwitchTimelineZone(m_project->getDocumentProperty(QStringLiteral("enableTimelineZone")).toInt() == 1); QScopedPointer xmlProd(new Mlt::Producer(pCore->getCurrentProfile()->profile(), "xml-string", m_project->getProjectXml().constData())); Mlt::Service s(*xmlProd); Mlt::Tractor tractor(s); m_mainTimelineModel = TimelineItemModel::construct(&pCore->getCurrentProfile()->profile(), m_project->getGuideModel(), m_project->commandStack()); constructTimelineFromMelt(m_mainTimelineModel, tractor); const QString groupsData = m_project->getDocumentProperty(QStringLiteral("groups")); if (!groupsData.isEmpty()) { m_mainTimelineModel->loadGroups(groupsData); } pCore->monitorManager()->projectMonitor()->setProducer(m_mainTimelineModel->producer(), pos); pCore->window()->getMainTimeline()->setModel(m_mainTimelineModel); pCore->monitorManager()->projectMonitor()->adjustRulerSize(m_mainTimelineModel->duration() - 1, m_project->getGuideModel()); pCore->window()->getMainTimeline()->controller()->setZone(m_project->zone()); pCore->window()->getMainTimeline()->controller()->setTargetTracks(m_project->targetTracks()); pCore->window()->getMainTimeline()->controller()->setScrollPos(m_project->getDocumentProperty(QStringLiteral("scrollPos")).toInt()); int activeTrackPosition = m_project->getDocumentProperty(QStringLiteral("activeTrack")).toInt(); if (activeTrackPosition > -1) { pCore->window()->getMainTimeline()->controller()->setActiveTrack(m_mainTimelineModel->getTrackIndexFromPosition(activeTrackPosition)); } m_mainTimelineModel->setUndoStack(m_project->commandStack()); } void ProjectManager::adjustProjectDuration() { pCore->monitorManager()->projectMonitor()->adjustRulerSize(m_mainTimelineModel->duration() - 1, nullptr); } void ProjectManager::activateAsset(const QVariantMap effectData) { if (effectData.contains(QStringLiteral("kdenlive/effect"))) { pCore->window()->addEffect(effectData.value(QStringLiteral("kdenlive/effect")).toString()); } else { pCore->window()->getMainTimeline()->controller()->addAsset(effectData); } } std::shared_ptr ProjectManager::getGuideModel() { return current()->getGuideModel(); } std::shared_ptr ProjectManager::undoStack() { return current()->commandStack(); } void ProjectManager::saveWithUpdatedProfile(const QString updatedProfile) { // First backup current project with fps appended QString message; if (m_project && m_project->isModified()) { - switch (KMessageBox::warningYesNoCancel(pCore->window(), i18n("The project \"%1\" has been changed.\nDo you want to save your changes?", m_project->url().fileName().isEmpty() ? i18n("Untitled") : m_project->url().fileName()))) { - case KMessageBox::Yes: - // save document here. If saving fails, return false; - if (!saveFile()) { - pCore->displayBinMessage(i18n("Project profile change aborted"), KMessageWidget::Information); - return; - } - break; - default: + switch ( + KMessageBox::warningYesNoCancel(pCore->window(), i18n("The project \"%1\" has been changed.\nDo you want to save your changes?", + m_project->url().fileName().isEmpty() ? i18n("Untitled") : m_project->url().fileName()))) { + case KMessageBox::Yes: + // save document here. If saving fails, return false; + if (!saveFile()) { pCore->displayBinMessage(i18n("Project profile change aborted"), KMessageWidget::Information); return; - break; + } + break; + default: + pCore->displayBinMessage(i18n("Project profile change aborted"), KMessageWidget::Information); + return; + break; } } if (!m_project || m_project->isModified()) { pCore->displayBinMessage(i18n("Project profile change aborted"), KMessageWidget::Information); return; } const QString currentFile = m_project->url().toLocalFile(); // Now update to new profile auto &newProfile = ProfileRepository::get()->getProfile(updatedProfile); QString convertedFile = currentFile.section(QLatin1Char('.'), 0, -2); convertedFile.append(QString("-%1.kdenlive").arg((int)(newProfile->fps() * 100))); QFile f(currentFile); QDomDocument doc; doc.setContent(&f, false); f.close(); QDomElement mltProfile = doc.documentElement().firstChildElement(QStringLiteral("profile")); if (!mltProfile.isNull()) { mltProfile.setAttribute(QStringLiteral("frame_rate_num"), newProfile->frame_rate_num()); mltProfile.setAttribute(QStringLiteral("frame_rate_den"), newProfile->frame_rate_den()); mltProfile.setAttribute(QStringLiteral("display_aspect_num"), newProfile->display_aspect_num()); mltProfile.setAttribute(QStringLiteral("display_aspect_den"), newProfile->display_aspect_den()); mltProfile.setAttribute(QStringLiteral("sample_aspect_num"), newProfile->sample_aspect_num()); mltProfile.setAttribute(QStringLiteral("sample_aspect_den"), newProfile->sample_aspect_den()); mltProfile.setAttribute(QStringLiteral("colorspace"), newProfile->colorspace()); mltProfile.setAttribute(QStringLiteral("progressive"), newProfile->progressive()); mltProfile.setAttribute(QStringLiteral("description"), newProfile->description()); mltProfile.setAttribute(QStringLiteral("width"), newProfile->width()); mltProfile.setAttribute(QStringLiteral("height"), newProfile->height()); } QDomNodeList playlists = doc.documentElement().elementsByTagName(QStringLiteral("playlist")); for (int i = 0; i < playlists.count(); ++i) { QDomElement e = playlists.at(i).toElement(); if (e.attribute(QStringLiteral("id")) == QLatin1String("main_bin")) { Xml::setXmlProperty(e, QStringLiteral("kdenlive:docproperties.profile"), updatedProfile); break; } } QFile file(convertedFile); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { return; } QTextStream out(&file); out << doc.toString(); if (file.error() != QFile::NoError) { KMessageBox::error(qApp->activeWindow(), i18n("Cannot write to file %1", convertedFile)); file.close(); return; } file.close(); openFile(QUrl::fromLocalFile(convertedFile)); pCore->displayBinMessage(i18n("Project profile changed"), KMessageWidget::Information); } diff --git a/src/timeline2/model/clipmodel.hpp b/src/timeline2/model/clipmodel.hpp index a8302085e..238ecf08d 100644 --- a/src/timeline2/model/clipmodel.hpp +++ b/src/timeline2/model/clipmodel.hpp @@ -1,216 +1,215 @@ /*************************************************************************** * Copyright (C) 2017 by Nicolas Carion * * This file is part of Kdenlive. See www.kdenlive.org. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) version 3 or any later version accepted by the * * membership of KDE e.V. (or its successor approved by the membership * * of KDE e.V.), which shall act as a proxy defined in Section 14 of * * version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #ifndef CLIPMODEL_H #define CLIPMODEL_H #include "moveableItem.hpp" #include "undohelper.hpp" #include #include namespace Mlt { class Producer; } class EffectStackModel; class MarkerListModel; -class ProjectClip; class TimelineModel; class TrackModel; class KeyframeModel; /* @brief This class represents a Clip object, as viewed by the backend. In general, the Gui associated with it will send modification queries (such as resize or move), and this class authorize them or not depending on the validity of the modifications */ class ClipModel : public MoveableItem { ClipModel() = delete; protected: /* This constructor is not meant to be called, call the static construct instead */ ClipModel(std::shared_ptr parent, std::shared_ptr prod, const QString &binClipId, int id, PlaylistState::ClipState state, double speed = 1.); public: ~ClipModel(); /* @brief Creates a clip, which references itself to the parent timeline Returns the (unique) id of the created clip @param parent is a pointer to the timeline @param binClip is the id of the bin clip associated @param id Requested id of the clip. Automatic if -1 */ static int construct(const std::shared_ptr &parent, const QString &binClipId, int id, PlaylistState::ClipState state, double speed = 1.); /* @brief Creates a clip, which references itself to the parent timeline Returns the (unique) id of the created clip This variants assumes a producer is already known, which should typically happen only at loading time. Note that there is no guarantee that this producer is actually going to be used. It might be discarded. */ static int construct(const std::shared_ptr &parent, const QString &binClipId, std::shared_ptr producer, PlaylistState::ClipState state); /* @brief returns a property of the clip, or from it's parent if it's a cut */ const QString getProperty(const QString &name) const override; int getIntProperty(const QString &name) const; double getDoubleProperty(const QString &name) const; QSize getFrameSize() const; Q_INVOKABLE bool showKeyframes() const; Q_INVOKABLE void setShowKeyframes(bool show); /* @brief Returns true if the clip can be converted to a video clip */ bool canBeVideo() const; /* @brief Returns true if the clip can be converted to an audio clip */ bool canBeAudio() const; /* @brief Returns a comma separated list of effect names */ const QString effectNames() const; /** @brief Returns the timeline clip status (video / audio only) */ PlaylistState::ClipState clipState() const; /** @brief Returns the bin clip type (image, color, AV, ...) */ ClipType::ProducerType clipType() const; /** @brief Sets the timeline clip status (video / audio only) */ bool setClipState(PlaylistState::ClipState state, Fun &undo, Fun &redo); /** @brief The fake track is used in insrt/overwrote mode. * in this case, dragging a clip is always accepted, but the change is not applied to the model. * so we use a 'fake' track id to pass to the qml view */ int getFakeTrackId() const; void setFakeTrackId(int fid); int getFakePosition() const; void setFakePosition(int fid); /* @brief Returns an XML representation of the clip with its effects */ QDomElement toXml(QDomDocument &document); protected: // helper functions that creates the lambda Fun setClipState_lambda(PlaylistState::ClipState state); public: /* @brief returns the length of the item on the timeline */ int getPlaytime() const override; /** @brief Returns audio cache data from bin clip to display audio thumbs */ QVariant getAudioWaveform(); /** @brief Returns the bin clip's id */ const QString &binId() const; void registerClipToBin(std::shared_ptr service, bool registerProducer); void deregisterClipToBin(); bool addEffect(const QString &effectId); bool copyEffect(std::shared_ptr stackModel, int rowId); /* @brief Import effects from a different stackModel */ bool importEffects(std::shared_ptr stackModel); /* @brief Import effects from a service that contains some (another clip?) */ bool importEffects(std::weak_ptr service); bool removeFade(bool fromStart); /** @brief Adjust effects duration. Should be called after each resize / cut operation */ bool adjustEffectLength(bool adjustFromEnd, int oldIn, int newIn, int oldDuration, int duration, int offset, Fun &undo, Fun &redo, bool logUndo); bool adjustEffectLength(const QString &effectName, int duration, int originalDuration, Fun &undo, Fun &redo); void passTimelineProperties(std::shared_ptr other); KeyframeModel *getKeyframeModel(); int fadeIn() const; int fadeOut() const; friend class TrackModel; friend class TimelineModel; friend class TimelineItemModel; friend class TimelineController; friend struct TimelineFunctions; protected: Mlt::Producer *service() const override; /* @brief Performs a resize of the given clip. Returns true if the operation succeeded, and otherwise nothing is modified This method is protected because it shouldn't be called directly. Call the function in the timeline instead. If a snap point is within reach, the operation will be coerced to use it. @param size is the new size of the clip @param right is true if we change the right side of the clip, false otherwise @param undo Lambda function containing the current undo stack. Will be updated with current operation @param redo Lambda function containing the current redo queue. Will be updated with current operation */ bool requestResize(int size, bool right, Fun &undo, Fun &redo, bool logUndo = true) override; /* @brief This function change the global (timeline-wise) enabled state of the effects */ void setTimelineEffectsEnabled(bool enabled); /* @brief This functions should be called when the producer of the binClip changes, to allow refresh * @param state corresponds to the state of the clip we want (audio or video) * @param speed corresponds to the speed we need. Leave to 0 to keep current speed. Warning: this function doesn't notify the model. Unless you know what * you are doing, better use useTimewarProducer to change the speed */ void refreshProducerFromBin(PlaylistState::ClipState state, double speed = 0); void refreshProducerFromBin(); /* @brief This functions replaces the current producer with a slowmotion one It also resizes the producer so that set of frames contained in the clip is the same */ bool useTimewarpProducer(double speed, Fun &undo, Fun &redo); // @brief Lambda that merely changes the speed (in and out are untouched) Fun useTimewarpProducer_lambda(double speed); /** @brief Returns the marker model associated with this clip */ std::shared_ptr getMarkerModel() const; /** @brief Returns the number of audio channels for this clip */ int audioChannels() const; bool audioEnabled() const; bool isAudioOnly() const; double getSpeed() const; /*@brief This is a debug function to ensure the clip is in a valid state */ bool checkConsistency(); protected: std::shared_ptr m_producer; std::shared_ptr getProducer(); std::shared_ptr m_effectStack; QString m_binClipId; // This is the Id of the bin clip this clip corresponds to. bool m_endlessResize; // Whether this clip can be freely resized bool forceThumbReload; // Used to trigger a forced thumb reload, when producer changes PlaylistState::ClipState m_currentState; ClipType::ProducerType m_clipType; double m_speed = -1; // Speed of the clip bool m_canBeVideo, m_canBeAudio; // Fake track id, used when dragging in insert/overwrite mode int m_fakeTrack; int m_fakePosition; }; #endif diff --git a/src/timeline2/model/moveableItem.hpp b/src/timeline2/model/moveableItem.hpp index bd213c24f..c5f84ccd7 100644 --- a/src/timeline2/model/moveableItem.hpp +++ b/src/timeline2/model/moveableItem.hpp @@ -1,124 +1,123 @@ /*************************************************************************** * Copyright (C) 2017 by Nicolas Carion * * This file is part of Kdenlive. See www.kdenlive.org. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) version 3 or any later version accepted by the * * membership of KDE e.V. (or its successor approved by the membership * * of KDE e.V.), which shall act as a proxy defined in Section 14 of * * version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #ifndef MOVEABLEITEM_H #define MOVEABLEITEM_H #include "timelinemodel.hpp" -#include "trackmodel.hpp" #include "undohelper.hpp" #include #include #include /* @brief This is the base class for objects that can move, for example clips and compositions */ template class MoveableItem { MoveableItem() = delete; protected: virtual ~MoveableItem() {} public: MoveableItem(std::weak_ptr parent, int id = -1); /* @brief returns (unique) id of current item */ int getId() const; /* @brief returns the length of the item on the timeline */ virtual int getPlaytime() const = 0; /* @brief returns the id of the track in which this items is inserted (-1 if none) */ int getCurrentTrackId() const; /* @brief returns the current position of the item (-1 if not inserted) */ int getPosition() const; /* @brief returns the in and out times of the item */ std::pair getInOut() const; virtual int getIn() const; virtual int getOut() const; friend class TrackModel; friend class TimelineModel; /* Implicit conversion operator to access the underlying producer */ operator Service &() { return *service(); } /* Returns true if the underlying producer is valid */ bool isValid(); /* @brief returns a property of the current item */ virtual const QString getProperty(const QString &name) const = 0; /* Set if the item is in grab state */ bool isGrabbed() const; void setGrab(bool grab); protected: /* @brief Returns a pointer to the service. It may be used but do NOT store it*/ virtual Service *service() const = 0; /* @brief Performs a resize of the given item. Returns true if the operation succeeded, and otherwise nothing is modified This method is protected because it shouldn't be called directly. Call the function in the timeline instead. If a snap point is within reach, the operation will be coerced to use it. @param size is the new size of the item @param right is true if we change the right side of the item, false otherwise @param undo Lambda function containing the current undo stack. Will be updated with current operation @param redo Lambda function containing the current redo queue. Will be updated with current operation */ virtual bool requestResize(int size, bool right, Fun &undo, Fun &redo, bool logUndo = true) = 0; /* Updates the stored position of the item This function is meant to be called by the trackmodel, not directly by the user. If you wish to actually move the item, use the requestMove slot. */ void setPosition(int position); /* Updates the stored track id of the item This function is meant to be called by the timeline, not directly by the user. If you wish to actually change the track the item, use the slot in the timeline slot. */ virtual void setCurrentTrackId(int tid); /* Set in and out of service */ virtual void setInOut(int in, int out); protected: std::weak_ptr m_parent; int m_id; // this is the creation id of the item, used for book-keeping int m_position; int m_currentTrackId; bool m_grabbed; mutable QReadWriteLock m_lock; // This is a lock that ensures safety in case of concurrent access }; #include "moveableItem.ipp" #endif diff --git a/src/transitions/transitionlist/view/transitionlistwidget.cpp b/src/transitions/transitionlist/view/transitionlistwidget.cpp index fc9790b2f..fa7db8a0a 100644 --- a/src/transitions/transitionlist/view/transitionlistwidget.cpp +++ b/src/transitions/transitionlist/view/transitionlistwidget.cpp @@ -1,104 +1,105 @@ /*************************************************************************** * Copyright (C) 2017 by Nicolas Carion * * This file is part of Kdenlive. See www.kdenlive.org. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) version 3 or any later version accepted by the * * membership of KDE e.V. (or its successor approved by the membership * * of KDE e.V.), which shall act as a proxy defined in Section 14 of * * version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "transitionlistwidget.hpp" #include "../model/transitiontreemodel.hpp" +#include "assets/assetlist/view/qmltypes/asseticonprovider.hpp" #include "dialogs/profilesdialog.h" #include "mainwindow.h" #include "transitions/transitionlist/model/transitionfilter.hpp" #include "transitions/transitionsrepository.hpp" #include #include TransitionListWidget::TransitionListWidget(QWidget *parent) : AssetListWidget(parent) { m_model = TransitionTreeModel::construct(true, this); m_proxyModel.reset(new TransitionFilter(this)); m_proxyModel->setSourceModel(m_model.get()); m_proxyModel->setSortRole(AssetTreeModel::NameRole); m_proxyModel->sort(0, Qt::AscendingOrder); m_proxy = new TransitionListWidgetProxy(this); rootContext()->setContextProperty("assetlist", m_proxy); rootContext()->setContextProperty("assetListModel", m_proxyModel.get()); rootContext()->setContextProperty("isEffectList", false); m_assetIconProvider = new AssetIconProvider(false); setup(); } TransitionListWidget::~TransitionListWidget() { delete m_proxy; qDebug() << " - - -Deleting transition list widget"; } QString TransitionListWidget::getMimeType(const QString &assetId) const { if (TransitionsRepository::get()->isComposition(assetId)) { return QStringLiteral("kdenlive/composition"); } return QStringLiteral("kdenlive/transition"); } void TransitionListWidget::updateFavorite(const QModelIndex &index) { m_proxyModel->dataChanged(index, index, QVector()); m_proxyModel->reloadFilterOnFavorite(); emit reloadFavorites(); } void TransitionListWidget::setFilterType(const QString &type) { if (type == "favorites") { static_cast(m_proxyModel.get())->setFilterType(true, TransitionType::Favorites); } else { static_cast(m_proxyModel.get())->setFilterType(false, TransitionType::Favorites); } } int TransitionListWidget::getNewStuff(const QString &configFile) { KNS3::Entry::List entries; QPointer dialog = new KNS3::DownloadDialog(configFile); if (dialog->exec() != 0) { entries = dialog->changedEntries(); } for (const KNS3::Entry &entry : entries) { if (entry.status() == KNS3::Entry::Installed) { qCDebug(KDENLIVE_LOG) << "// Installed files: " << entry.installedFiles(); } } delete dialog; return entries.size(); } void TransitionListWidget::downloadNewLumas() { if (getNewStuff(QStringLiteral(":data/kdenlive_wipes.knsrc")) > 0) { MainWindow::refreshLumas(); // TODO: refresh currently displayd trans ? } }