Commit c1ca48e3 authored by Eric Jiang's avatar Eric Jiang Committed by Jean-Baptiste Mardelle
Browse files

Re-organize KdenliveDoc constructor

This patch re-organizes big chunks of the code in kdenlivedoc.cpp along
with the calling code in projectmanager.cpp to make it clearer and more

* Split out the KdenliveDoc ctor into two ctors, one for opening a file
  (private) and one for creating a new file (public).
* Add KdenliveDoc::Open factory method, which returns several flags plus
  a pointer to the doc only if it was successful. Callers should use
  Open so that the actual constructor won't have so much code that can
* Lift all GUI interactions into the caller (projectmanager.cpp) so that
  creating a KdenliveDoc can be unit tested.
parent c3e74e3f
......@@ -19,6 +19,13 @@ class DocumentValidator
DocumentValidator(const QDomDocument &doc, QUrl documentUrl);
bool isProject() const;
/** @brief Check if the document is a valid Kdenlive project
* @param currentVersion The version of the document, with the current
* version defined as DOCUMENTVERSION in kdenlivedoc.cpp.
* @return A QPair with the first value true if the document is valid, and
* the second value the original decimal point string only if upgradeTo100
* changed the decimal point.
QPair<bool, QString> validate(const double currentVersion);
bool isModified() const;
/** @brief Check if the project contains references to Movit stuff (GLSL), and try to convert if wanted. */
This diff is collapsed.
......@@ -16,6 +16,7 @@ SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include <QDir>
#include <QList>
#include <QMap>
#include <QObject>
#include <QUuid>
#include <memory>
#include <qdom.h>
......@@ -43,12 +44,45 @@ namespace Mlt {
class Profile;
/** Object returned by KdenliveDoc::Open(), containing a pointer to a KdenliveDoc
* (if successful) and also additional information about whether the doc was
* modified or upgraded, and any error message. If the doc is nullptr, then
* errorMessage() will return an error string that can be shown to the user.
class DocOpenResult {
bool isSuccessful() const { return m_doc != nullptr; }
KdenliveDoc * getDocument() const { return m_doc; }
/** @returns an error message if the doc could not be opened. */
QString getError() const { return m_errorMessage; }
/** @return true if the doc was upgraded from an older version */
bool wasUpgraded() const { return m_upgraded; }
/** @return true if the doc was modified by the validator */
bool wasModified() const { return m_modified; }
void setDocument(KdenliveDoc *doc) { m_doc = doc; }
void setError(const QString &error) { m_errorMessage = error; }
void setUpgraded(bool upgraded) { m_upgraded = upgraded; }
void setModified(bool modified) { m_modified = modified; }
KdenliveDoc *m_doc = nullptr;
QString m_errorMessage = QString();
QString m_notification = QString();
bool m_upgraded = false;
bool m_modified = false;
class KdenliveDoc : public QObject
KdenliveDoc(const QUrl &url, QString projectFolder, QUndoGroup *undoGroup, const QString &profileName, const QMap<QString, QString> &properties,
const QMap<QString, QString> &metadata, const QPair<int, int> &tracks, int audioChannels, bool *openBackup, MainWindow *parent = nullptr);
/** @brief Create a new empty Kdenlive project with the specified profile and requested number of tracks. */
KdenliveDoc(QString projectFolder, QUndoGroup *undoGroup, const QString &profileName, const QMap<QString, QString> &properties,
const QMap<QString, QString> &metadata, const QPair<int, int> &tracks, int audioChannels, MainWindow *parent = nullptr);
/** @brief Open an existing Kdenlive project, returning nothing if the project cannot be opened. */
static DocOpenResult Open(const QUrl &url, const QString &projectFolder, QUndoGroup *undoGroup,
bool recoverCorruption, MainWindow *parent = nullptr);
~KdenliveDoc() override;
friend class LoadJob;
/** @brief Get current document's producer. */
......@@ -75,6 +109,10 @@ public:
/** @brief Defines whether the document needs to be saved. */
bool isModified() const;
/** @brief Adds a "modified" attribute to the document root so that a backup
* will be created the next time the document is saved.
void requestBackup();
/** @brief Returns the project folder, used to store project temporary files. */
QString projectTempFolder() const;
......@@ -150,6 +188,7 @@ public:
QString getAutoProxyProfile();
/** @brief Returns the number of clips in this project (useful to show loading progress) */
int clipsCount() const;
int updateClipsCount();
/** @brief Returns a list of project tags (color / description) */
QMap <int, QStringList> getProjectTags() const;
/** @brief Returns the number of audio channels for this project */
......@@ -163,6 +202,7 @@ public:
* @return Original decimal point, or an empty string if it was “.” already
QString &modifiedDecimalPoint();
void setModifiedDecimalPoint(const QString &decimalPoint) { m_modifiedDecimalPoint = decimalPoint; }
/** @brief Initialize subtitle model */
void initializeSubtitles(const std::shared_ptr<SubtitleModel> m_subtitle);
/** @brief Returns a path for current document's subtitle file. If final is true, this will be the project filename with ".srt" appended. Otherwise a file in /tmp */
......@@ -176,7 +216,12 @@ public:
void processProxyNodes(QDomNodeList producers, const QString &root, const QMap<QString, QString> &proxies);
QUrl m_url;
/** @brief Create a new KdenliveDoc using the provided QDomDocument (an
* existing project file), used by the Open() named constructor. */
KdenliveDoc(const QUrl &url, QDomDocument& newDom, QString projectFolder, QUndoGroup *undoGroup,
MainWindow *parent = nullptr);
/** @brief Set document default properties using hard-coded values and KdenliveSettings. */
void initializeProperties();
QDomDocument m_document;
int m_clipsCount;
/** @brief MLT's root (base path) that is stripped from urls in saved xml */
......@@ -198,6 +243,8 @@ private:
enum DOCSTATUS { CleanProject, ModifiedProject, UpgradedProject };
DOCSTATUS m_documentOpenStatus;
QUrl m_url;
/** @brief The project folder, used to store project files (titles, effects...). */
QString m_projectFolder;
QList<int> m_undoChunks;
......@@ -228,22 +275,22 @@ public slots:
void slotCreateTextTemplateClip(const QString &group, const QString &groupId, QUrl path);
/** @brief Sets the document as modified or up to date.
* If crash recovery is turned on, a timer calls KdenliveDoc::slotAutoSave() \n
* Emits docModified connected to MainWindow::slotUpdateDocumentState \n
* @param mod (optional) true if the document has to be saved */
void setModified(bool mod = true);
QMap<QString, QString> proxyClipsById(const QStringList &ids, bool proxy, const QMap<QString, QString> &proxyPath = QMap<QString, QString>());
void slotProxyCurrentItem(bool doProxy, QList<std::shared_ptr<ProjectClip>> clipList = QList<std::shared_ptr<ProjectClip>>(), bool force = false,
QUndoCommand *masterCommand = nullptr);
/** @brief Saves the current project at the autosave location.
* The autosave files are in ~/.kde/data/stalefiles/kdenlive/ */
void slotAutoSave(const QString &scene);
/** @brief Groups were changed, save to MLT. */
void groupsChanged(const QString &groups);
void switchProfile(ProfileParam* pf, const QString clipName);
void switchProfile(ProfileParam* pf, const QString &clipName);
private slots:
void slotModified();
......@@ -226,11 +226,10 @@ void ProjectManager::newFile(QString profileName, bool showProjectSettings)
documentMetadata = w->metadata();
delete w;
bool openBackup;
KdenliveDoc *doc = new KdenliveDoc(QUrl(), projectFolder, pCore->window()->m_commandStack, profileName, documentProperties, documentMetadata, projectTracks,
audioChannels, &openBackup, pCore->window());
// TODO: KdenliveDoc constructor
KdenliveDoc *doc = new KdenliveDoc(projectFolder, pCore->window()->m_commandStack, profileName, documentProperties, documentMetadata, projectTracks, audioChannels, pCore->window());
doc->m_autosave = new KAutoSaveFile(startFile, doc);
doc->m_sameProjectFolder = sameProjectFolder;
......@@ -598,7 +597,7 @@ void ProjectManager::openFile(const QUrl &url)
doOpenFile(url, nullptr);
void ProjectManager::doOpenFile(const QUrl &url, KAutoSaveFile *stale)
void ProjectManager::doOpenFile(const QUrl &url, KAutoSaveFile *stale, bool isBackup)
Q_ASSERT(m_project == nullptr);
......@@ -616,19 +615,54 @@ void ProjectManager::doOpenFile(const QUrl &url, KAutoSaveFile *stale)
bool openBackup;
int audioChannels = 2;
if (KdenliveSettings::audio_channels() == 1) {
audioChannels = 4;
} else if (KdenliveSettings::audio_channels() == 2) {
audioChannels = 6;
DocOpenResult openResult = KdenliveDoc::Open(stale ? QUrl::fromLocalFile(stale->fileName()) : url,
QString(), pCore->window()->m_commandStack, false, pCore->window());
KdenliveDoc *doc;
if (!openResult.isSuccessful()) {
if (!isBackup) {
int answer = KMessageBox::warningYesNoCancel(
pCore->window(), i18n("Cannot open the project file. Error:\n%1\nDo you want to open a backup file?", openResult.getError()),
i18n("Error opening file"), KGuiItem(i18n("Open Backup")), KGuiItem(i18n("Recover")));
if (answer == KMessageBox::ButtonCode::Yes) { // Open Backup
} else if (answer == KMessageBox::ButtonCode::No) { // Recover
// if file was broken by Kdenlive 0.9.4, we can try recovering it. If successful, continue through rest of this function.
openResult = KdenliveDoc::Open(stale ? QUrl::fromLocalFile(stale->fileName()) : url,
QString(), pCore->window()->m_commandStack, true, pCore->window());
if (openResult.isSuccessful()) {
doc = openResult.getDocument();
} else {
KMessageBox::error(pCore->window(), "Could not recover corrupted file.");
} else {
KMessageBox::detailedSorry(pCore->window(), "Could not open the backup project file.", openResult.getError());
} else {
doc = openResult.getDocument();
// if we could not open the file, and could not recover (or user declined), stop now
if (!openResult.isSuccessful()) {
delete m_progressDialog;
m_progressDialog = nullptr;
if (openResult.wasUpgraded()) {
pCore->displayMessage(i18n("Your project was upgraded, a backup will be created on next save"),
} else if (openResult.wasModified()) {
pCore->displayMessage(i18n("Your project was modified on opening, a backup will be created on next save"),
pCore->displayMessage(QString(), OperationCompletedMessage);
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<QString, QString>(), QMap<QString, QString>(), {KdenliveSettings::videotracks(), KdenliveSettings::audiotracks()},
audioChannels, &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")));
......@@ -673,9 +707,6 @@ void ProjectManager::doOpenFile(const QUrl &url, KAutoSaveFile *stale)
emit docOpened(m_project);
pCore->displayMessage(QString(), OperationCompletedMessage, 100);
if (openBackup) {
delete m_progressDialog;
m_progressDialog = nullptr;
......@@ -710,7 +741,7 @@ bool ProjectManager::slotOpenBackup(const QUrl &url)
projectFolder = QUrl::fromLocalFile(KdenliveSettings::defaultprojectfolder());
projectFile = url;
} else {
projectFolder = QUrl::fromLocalFile(m_project->projectTempFolder());
projectFolder = QUrl::fromLocalFile(m_project ? m_project->projectTempFolder() : QString());
projectFile = m_project->url();
projectId = m_project->getDocumentProperty(QStringLiteral("documentid"));
......@@ -720,7 +751,7 @@ bool ProjectManager::slotOpenBackup(const QUrl &url)
QString requestedBackup = dia->selectedFile();
doOpenFile(QUrl::fromLocalFile(requestedBackup), nullptr);
doOpenFile(QUrl::fromLocalFile(requestedBackup), nullptr, true);
if (m_project) {
if (!m_project->url().isEmpty()) {
// Only update if restore succeeded
......@@ -51,10 +51,10 @@ public:
/** @brief Store command line args for later opening. */
void init(const QUrl &projectUrl, const QString &clipList);
void doOpenFile(const QUrl &url, KAutoSaveFile *stale);
void doOpenFile(const QUrl &url, KAutoSaveFile *stale, bool isBackup = false);
KRecentFilesAction *recentFilesAction();
void prepareSave();
/** @brief Disable all bin effects in current project
/** @brief Disable all bin effects in current project
* @param disable if true, all project bin effects will be disabled
* @param refreshMonitor if false, monitors will not be refreshed
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment