diff --git a/app/common/rational.cpp b/app/common/rational.cpp index a8b4ba49f..36952ca11 100644 --- a/app/common/rational.cpp +++ b/app/common/rational.cpp @@ -15,6 +15,20 @@ rational rational::fromDouble(const double &flt) return av_d2q(flt, INT_MAX); } +rational rational::fromString(const QString &str) +{ + QStringList elements = str.split('/'); + + switch (elements.size()) { + case 0: + return rational(); + case 1: + return rational(elements.first().toLongLong()); + default: + return rational(elements.at(0).toLongLong(), elements.at(1).toLongLong()); + } +} + //Function: print number to cout void rational::print(ostream &out) const diff --git a/app/common/rational.h b/app/common/rational.h index a037e7c0e..0244e989a 100644 --- a/app/common/rational.h +++ b/app/common/rational.h @@ -57,6 +57,8 @@ public: static rational fromDouble(const double& flt); + static rational fromString(const QString& str); + //Assignment Operators const rational& operator=(const rational &rhs); const rational& operator+=(const rational &rhs); diff --git a/app/common/xmlreadloop.h b/app/common/xmlreadloop.h new file mode 100644 index 000000000..80e101c5f --- /dev/null +++ b/app/common/xmlreadloop.h @@ -0,0 +1,11 @@ +#ifndef XMLREADLOOP_H +#define XMLREADLOOP_H + +#define XMLReadLoop(reader, section) \ + while (!reader->atEnd() && !(reader->name() == section && reader->isEndElement()) && reader->readNext()) + +#define XMLAttributeLoop(reader, item) \ + QXmlStreamAttributes __attributes = reader->attributes(); \ + foreach (const QXmlStreamAttribute& item, __attributes) + +#endif // XMLREADLOOP_H diff --git a/app/core.cpp b/app/core.cpp index d99af6539..40efbb141 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -41,6 +41,7 @@ #include "panel/panelmanager.h" #include "panel/project/project.h" #include "panel/viewer/viewer.h" +#include "project/projectloadmanager.h" #include "project/projectsavemanager.h" #include "project/item/footage/footage.h" #include "project/item/sequence/sequence.h" @@ -116,9 +117,13 @@ void Core::Start() StartGUI(parser.isSet(fullscreen_option)); - // Create new project on startup - // FIXME: Load project from startup_project_ instead if not empty - AddOpenProject(std::make_shared()); + // Load startup project + if (startup_project_.isEmpty()) { + // If no load project is set, create a new one on open + AddOpenProject(std::make_shared()); + } else { + + } } void Core::Stop() @@ -408,35 +413,10 @@ void Core::StartGUI(bool full_screen) void Core::SaveProjectInternal(Project *project) { - // Create save dialog - LoadSaveDialog* lsd = new LoadSaveDialog(tr("Saving '%1'").arg(project->filename()), tr("Save Project"), main_window_); - lsd->open(); - // Create save manager ProjectSaveManager* psm = new ProjectSaveManager(project); - // Create a separate thread to save in - QThread* save_thread = new QThread(); - save_thread->start(); - - // Move the save manager to this thread - psm->moveToThread(save_thread); - - // Connect the save manager progress signal to the progress bar update on the dialog - connect(psm, &ProjectSaveManager::ProgressChanged, lsd, &LoadSaveDialog::SetProgress, Qt::QueuedConnection); - - // Connect cancel signal (must be a direct connection or it'll be queued after the save is already finished) - connect(lsd, &LoadSaveDialog::Cancelled, psm, &ProjectSaveManager::Cancel, Qt::DirectConnection); - - // Connect cleanup functions (ensure everything new'd in this function is deleteLater'd) - connect(psm, &ProjectSaveManager::Finished, lsd, &LoadSaveDialog::accept, Qt::QueuedConnection); - connect(psm, &ProjectSaveManager::Finished, lsd, &LoadSaveDialog::deleteLater, Qt::QueuedConnection); - connect(psm, &ProjectSaveManager::Finished, psm, &ProjectSaveManager::deleteLater, Qt::QueuedConnection); - connect(psm, &ProjectSaveManager::Finished, save_thread, &QThread::quit, Qt::QueuedConnection); - connect(psm, &ProjectSaveManager::Finished, save_thread, &QThread::deleteLater, Qt::QueuedConnection); - - // Start the save process - QMetaObject::invokeMethod(psm, "Start", Qt::QueuedConnection); + InitiateOpenSaveProcess(psm, tr("Saving '%1'").arg(project->filename()), tr("Save Project")); } void Core::SaveAutorecovery() @@ -500,7 +480,7 @@ void Core::SaveActiveProjectAs() QString fn = QFileDialog::getSaveFileName(main_window_, tr("Save Project As"), QString(), - tr("Olive Project (*.ove)")); + GetProjectFilter()); if (!fn.isEmpty()) { active_project->set_filename(fn); @@ -584,3 +564,61 @@ QString Core::ChannelLayoutToString(const uint64_t &layout) return tr("Unknown (0x%1)").arg(layout, 1, 16); } } + +QString Core::GetProjectFilter() const +{ + return QStringLiteral("%1 (*.ove)").arg("Olive Project"); +} + +void Core::OpenProjectInternal(const QString &filename) +{ + ProjectLoadManager* plm = new ProjectLoadManager(filename); + + // We use a blocking queued connection here because we want to ensure we have this project instance before the + // ProjectLoadManager is destroyed + connect(plm, &ProjectLoadManager::ProjectLoaded, this, &Core::AddOpenProject, Qt::BlockingQueuedConnection); + + InitiateOpenSaveProcess(plm, tr("Loading '%1'").arg(filename), tr("Load Project")); +} + +void Core::InitiateOpenSaveProcess(ProjectFileManagerBase *manager, const QString& dialog_text, const QString& dialog_title) +{ + // Create save dialog + LoadSaveDialog* lsd = new LoadSaveDialog(dialog_text, dialog_title, main_window_); + lsd->open(); + + // Create a separate thread to save in + QThread* save_thread = new QThread(); + save_thread->start(); + + // Move the save manager to this thread + manager->moveToThread(save_thread); + + // Connect the save manager progress signal to the progress bar update on the dialog + connect(manager, &ProjectFileManagerBase::ProgressChanged, lsd, &LoadSaveDialog::SetProgress, Qt::QueuedConnection); + + // Connect cancel signal (must be a direct connection or it'll be queued after the save is already finished) + connect(lsd, &LoadSaveDialog::Cancelled, manager, &ProjectFileManagerBase::Cancel, Qt::DirectConnection); + + // Connect cleanup functions (ensure everything new'd in this function is deleteLater'd) + connect(manager, &ProjectFileManagerBase::Finished, lsd, &LoadSaveDialog::accept, Qt::QueuedConnection); + connect(manager, &ProjectFileManagerBase::Finished, lsd, &LoadSaveDialog::deleteLater, Qt::QueuedConnection); + connect(manager, &ProjectFileManagerBase::Finished, manager, &ProjectFileManagerBase::deleteLater, Qt::QueuedConnection); + connect(manager, &ProjectFileManagerBase::Finished, save_thread, &QThread::quit, Qt::QueuedConnection); + connect(manager, &ProjectFileManagerBase::Finished, save_thread, &QThread::deleteLater, Qt::QueuedConnection); + + // Start the save process + QMetaObject::invokeMethod(manager, "Start", Qt::QueuedConnection); +} + +void Core::OpenProject() +{ + QString file = QFileDialog::getOpenFileName(main_window_, + tr("Open Project"), + QString(), + GetProjectFilter()); + + if (!file.isEmpty()) { + OpenProjectInternal(file); + } +} diff --git a/app/core.h b/app/core.h index 076ff02cf..23500718d 100644 --- a/app/core.h +++ b/app/core.h @@ -26,6 +26,7 @@ #include "common/rational.h" #include "project/project.h" +#include "project/projectfilemanagerbase.h" #include "project/projectviewmodel.h" #include "task/task.h" #include "tool/tool.h" @@ -174,6 +175,11 @@ public: static QString ChannelLayoutToString(const uint64_t &layout); public slots: + /** + * @brief Starts an open file dialog to load a project from file + */ + void OpenProject(); + /** * @brief Save the currently active project * @@ -255,9 +261,22 @@ signals: private: /** - * @brief Creates an empty project and adds it to the "open projects" + * @brief Get the file filter than can be used with QFileDialog to open and save compatible projects */ - void AddOpenProject(ProjectPtr p); + QString GetProjectFilter() const; + + /** + * @brief Internal project open + */ + void OpenProjectInternal(const QString& filename); + + /** + * @brief Initiate a project load or save + * + * The load and save process are largely similar, both OpenProjectInternal() and SaveProjectInternal() can run + * this function with some minor setup differences. + */ + void InitiateOpenSaveProcess(ProjectFileManagerBase* manager, const QString &dialog_text, const QString &dialog_title); /** * @brief Declare custom types/classes for Qt's signal/slot system @@ -334,6 +353,11 @@ private: private slots: void SaveAutorecovery(); + /** + * @brief Adds a project to the "open projects" list + */ + void AddOpenProject(ProjectPtr p); + }; #endif // CORE_H diff --git a/app/dialog/projectproperties/projectproperties.cpp b/app/dialog/projectproperties/projectproperties.cpp index 3e0a9cde9..94bae0707 100644 --- a/app/dialog/projectproperties/projectproperties.cpp +++ b/app/dialog/projectproperties/projectproperties.cpp @@ -40,7 +40,7 @@ ProjectPropertiesDialog::ProjectPropertiesDialog(QWidget *parent) : { QVBoxLayout* layout = new QVBoxLayout(this); - setWindowTitle(tr("Project Properties")); + setWindowTitle(tr("Project Properties for '%1'").arg(working_project_->name())); QGroupBox* color_group = new QGroupBox(); color_group->setTitle(tr("Color Management")); diff --git a/app/node/input.cpp b/app/node/input.cpp index 8dcd5e2f2..58ffa56c9 100644 --- a/app/node/input.cpp +++ b/app/node/input.cpp @@ -26,6 +26,7 @@ #include "common/bezier.h" #include "common/lerp.h" +#include "common/xmlreadloop.h" #include "node.h" #include "output.h" #include "inputarray.h" @@ -84,6 +85,85 @@ QString NodeInput::name() return NodeParam::name(); } +void NodeInput::Load(QXmlStreamReader *reader) +{ + XMLAttributeLoop(reader, attr) { + if (attr.name() == "keyframing") { + set_is_keyframing(attr.value() == "1"); + } + } + + XMLReadLoop(reader, "input") { + if (reader->isStartElement()) { + if (reader->name() == "standard") { + // Load standard value + int val_index = 0; + + XMLReadLoop(reader, "standard") { + if (reader->name() == "value") { + reader->readNext(); + + QString value_text = reader->text().toString(); + + if (value_text.isEmpty()) { + standard_value_.replace(val_index, QVariant()); + } else { + standard_value_.replace(val_index, value_text); + } + + val_index++; + } + } + } else if (reader->name() == "keyframes") { + int track = 0; + + XMLReadLoop(reader, "keyframes") { + if (reader->name() == "track") { + XMLReadLoop(reader, "track") { + if (reader->name() == "key") { + rational key_time; + NodeKeyframe::Type key_type; + QVariant key_value; + QPointF key_in_handle; + QPointF key_out_handle; + + XMLAttributeLoop(reader, attr) { + if (attr.name() == "time") { + key_time = rational::fromString(attr.value().toString()); + } else if (attr.name() == "type") { + key_type = static_cast(attr.value().toInt()); + } else if (attr.name() == "inhandlex") { + key_in_handle.setX(attr.value().toDouble()); + } else if (attr.name() == "inhandley") { + key_in_handle.setY(attr.value().toDouble()); + } else if (attr.name() == "outhandlex") { + key_out_handle.setX(attr.value().toDouble()); + } else if (attr.name() == "outhandley") { + key_out_handle.setY(attr.value().toDouble()); + } + } + + reader->readNext(); + + key_value = reader->text().toString(); + + NodeKeyframePtr key = NodeKeyframe::Create(key_time, key_value, key_type, track); + key->set_bezier_control_in(key_in_handle); + key->set_bezier_control_out(key_out_handle); + keyframe_tracks_[track].append(key); + } + } + + track++; + } + } + } else { + + } + } + } +} + void NodeInput::Save(QXmlStreamWriter *writer) const { writer->writeStartElement("input"); @@ -114,6 +194,10 @@ void NodeInput::Save(QXmlStreamWriter *writer) const writer->writeAttribute("time", key->time().toString()); writer->writeAttribute("type", QString::number(key->type())); + writer->writeAttribute("inhandlex", QString::number(key->bezier_control_in().x())); + writer->writeAttribute("inhandley", QString::number(key->bezier_control_in().y())); + writer->writeAttribute("outhandlex", QString::number(key->bezier_control_out().x())); + writer->writeAttribute("outhandley", QString::number(key->bezier_control_out().y())); writer->writeCharacters(key->value().toString()); @@ -137,6 +221,10 @@ const NodeParam::DataType &NodeInput::data_type() const return data_type_; } +void NodeInput::LoadInternal(QXmlStreamReader *reader) +{ +} + void NodeInput::SaveInternal(QXmlStreamWriter *writer) const { } diff --git a/app/node/input.h b/app/node/input.h index 9e2802724..b320cf2ce 100644 --- a/app/node/input.h +++ b/app/node/input.h @@ -54,6 +54,8 @@ public: virtual QString name() override; + virtual void Load(QXmlStreamReader* reader) override; + virtual void Save(QXmlStreamWriter* writer) const override; /** @@ -241,6 +243,8 @@ signals: void KeyframeRemoved(NodeKeyframePtr key); protected: + virtual void LoadInternal(QXmlStreamReader* reader); + virtual void SaveInternal(QXmlStreamWriter* writer) const; private: diff --git a/app/node/inputarray.cpp b/app/node/inputarray.cpp index 9fcfbc934..ba32c8727 100644 --- a/app/node/inputarray.cpp +++ b/app/node/inputarray.cpp @@ -1,5 +1,6 @@ #include "inputarray.h" +#include "common/xmlreadloop.h" #include "node.h" NodeInputArray::NodeInputArray(const QString &id, const DataType &type, const QVariant &default_value) : @@ -167,6 +168,18 @@ void NodeInputArray::RemoveAt(int index) RemoveLast(); } +void NodeInputArray::LoadInternal(QXmlStreamReader *reader) +{ + if (reader->name() == "subparameters") { + XMLReadLoop(reader, "subparameters") { + if (reader->name() == "input") { + Append(); + At(GetSize() - 1)->Load(reader); + } + } + } +} + void NodeInputArray::SaveInternal(QXmlStreamWriter *writer) const { writer->writeStartElement("subparameters"); diff --git a/app/node/inputarray.h b/app/node/inputarray.h index 4f2df806e..b6aaf4550 100644 --- a/app/node/inputarray.h +++ b/app/node/inputarray.h @@ -33,6 +33,8 @@ signals: void SizeChanged(int size); protected: + virtual void LoadInternal(QXmlStreamReader* reader) override; + virtual void SaveInternal(QXmlStreamWriter* writer) const override; private: diff --git a/app/node/node.cpp b/app/node/node.cpp index 2fef04065..1ed039ffc 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -23,6 +23,8 @@ #include #include +#include "common/xmlreadloop.h" + Node::Node() : can_be_deleted_(true) { @@ -47,6 +49,39 @@ Node::~Node() } } +void Node::Load(QXmlStreamReader *reader) +{ + XMLReadLoop(reader, "node") { + if (reader->isStartElement()) { + if (reader->name() == "input" || reader->name() == "output") { + QString param_id; + + XMLAttributeLoop(reader, attr) { + if (attr.name() == "id") { + param_id = attr.value().toString(); + + break; + } + } + + if (param_id.isEmpty()) { + qDebug() << "Found parameter with no ID"; + continue; + } + + NodeParam* param = GetParameterWithID(param_id); + + if (!param) { + qDebug() << "No parameter in" << id() << "with parameter" << param_id; + continue; + } + + param->Load(reader); + } + } + } +} + void Node::Save(QXmlStreamWriter *writer) const { writer->writeStartElement("node"); diff --git a/app/node/node.h b/app/node/node.h index b49768cc9..0b5cb317c 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -65,6 +65,11 @@ public: */ virtual Node* copy() const = 0; + /** + * @brief Clear current node variables and replace them with + */ + void Load(QXmlStreamReader* reader); + /** * @brief Save this node into a text/XML format */ diff --git a/app/node/output.cpp b/app/node/output.cpp index 4f4b2bbfd..a04226437 100644 --- a/app/node/output.cpp +++ b/app/node/output.cpp @@ -41,6 +41,10 @@ QString NodeOutput::name() return NodeParam::name(); } +void NodeOutput::Load(QXmlStreamReader *reader) +{ +} + void NodeOutput::Save(QXmlStreamWriter *writer) const { writer->writeStartElement("output"); diff --git a/app/node/output.h b/app/node/output.h index 1b6e66941..479ed3f96 100644 --- a/app/node/output.h +++ b/app/node/output.h @@ -42,6 +42,8 @@ public: virtual QString name() override; + virtual void Load(QXmlStreamReader *reader) override; + virtual void Save(QXmlStreamWriter* writer) const override; private: diff --git a/app/node/param.h b/app/node/param.h index 55d0a8d4b..22d2e42dc 100644 --- a/app/node/param.h +++ b/app/node/param.h @@ -230,6 +230,11 @@ public: virtual ~NodeParam() override; + /** + * @brief Load function + */ + virtual void Load(QXmlStreamReader* reader) = 0; + /** * @brief Save function */ diff --git a/app/project/CMakeLists.txt b/app/project/CMakeLists.txt index a39e09ff3..6ef2b1a6e 100644 --- a/app/project/CMakeLists.txt +++ b/app/project/CMakeLists.txt @@ -20,6 +20,10 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} project/project.h project/project.cpp + project/projectfilemanagerbase.h + project/projectfilemanagerbase.cpp + project/projectloadmanager.h + project/projectloadmanager.cpp project/projectsavemanager.h project/projectsavemanager.cpp project/projectviewmodel.h diff --git a/app/project/item/folder/folder.cpp b/app/project/item/folder/folder.cpp index c3f1b6cf2..2d4ee1eaf 100644 --- a/app/project/item/folder/folder.cpp +++ b/app/project/item/folder/folder.cpp @@ -20,6 +20,9 @@ #include "folder.h" +#include "common/xmlreadloop.h" +#include "project/item/footage/footage.h" +#include "project/item/sequence/sequence.h" #include "ui/icons/icons.h" Folder::Folder() @@ -41,6 +44,34 @@ QIcon Folder::icon() return icon::Folder; } +void Folder::Load(QXmlStreamReader *reader) +{ + XMLAttributeLoop(reader, attr) { + if (attr.name() == "name") { + set_name(attr.value().toString()); + } + } + + XMLReadLoop(reader, "folder") { + if (reader->isStartElement()) { + ItemPtr child = nullptr; + + if (reader->name() == "folder") { + child = std::make_shared(); + } else if (reader->name() == "footage") { + child = std::make_shared(); + } else if (reader->name() == "sequence") { + child = std::make_shared(); + } + + if (child) { + child->Load(reader); + add_child(child); + } + } + } +} + void Folder::Save(QXmlStreamWriter *writer) const { writer->writeStartElement("folder"); diff --git a/app/project/item/folder/folder.h b/app/project/item/folder/folder.h index 340228188..037359df1 100644 --- a/app/project/item/folder/folder.h +++ b/app/project/item/folder/folder.h @@ -40,6 +40,8 @@ public: virtual QIcon icon() override; + virtual void Load(QXmlStreamReader* reader) override; + virtual void Save(QXmlStreamWriter* writer) const override; private: diff --git a/app/project/item/footage/footage.cpp b/app/project/item/footage/footage.cpp index 941e87e5e..fe36eeb25 100644 --- a/app/project/item/footage/footage.cpp +++ b/app/project/item/footage/footage.cpp @@ -23,6 +23,7 @@ #include #include "common/timecodefunctions.h" +#include "common/xmlreadloop.h" #include "ui/icons/icons.h" Footage::Footage() @@ -35,6 +36,29 @@ Footage::~Footage() ClearStreams(); } +void Footage::Load(QXmlStreamReader *reader) +{ + QXmlStreamAttributes attributes = reader->attributes(); + + foreach (const QXmlStreamAttribute& attr, attributes) { + if (attr.name() == "name") { + set_name(attr.value().toString()); + } else if (attr.name() == "filename") { + set_filename(attr.value().toString()); + } + } + + // FIXME: Probe here + + XMLReadLoop(reader, "footage") { + if (reader->isStartElement()) { + if (reader->name() == "stream") { + // FIXME: Load stream custom options here + } + } + } +} + void Footage::Save(QXmlStreamWriter *writer) const { writer->writeStartElement("footage"); diff --git a/app/project/item/footage/footage.h b/app/project/item/footage/footage.h index 6630a7193..8829f1b07 100644 --- a/app/project/item/footage/footage.h +++ b/app/project/item/footage/footage.h @@ -60,6 +60,11 @@ public: */ virtual ~Footage() override; + /** + * @brief Load function + */ + virtual void Load(QXmlStreamReader* reader) override; + /** * @brief Save function */ diff --git a/app/project/item/item.h b/app/project/item/item.h index 87e0bb6a3..b999f3b86 100644 --- a/app/project/item/item.h +++ b/app/project/item/item.h @@ -63,6 +63,8 @@ public: DISABLE_COPY_MOVE(Item) + virtual void Load(QXmlStreamReader* reader) = 0; + virtual void Save(QXmlStreamWriter* writer) const = 0; virtual Type type() const = 0; diff --git a/app/project/item/sequence/sequence.cpp b/app/project/item/sequence/sequence.cpp index 6384094b9..f983c7d68 100644 --- a/app/project/item/sequence/sequence.cpp +++ b/app/project/item/sequence/sequence.cpp @@ -25,6 +25,8 @@ #include "config/config.h" #include "common/channellayout.h" #include "common/timecodefunctions.h" +#include "common/xmlreadloop.h" +#include "node/factory.h" #include "panel/panelmanager.h" #include "panel/node/node.h" #include "panel/curve/curve.h" @@ -38,6 +40,48 @@ Sequence::Sequence() : { } +void Sequence::Load(QXmlStreamReader *reader) +{ + XMLAttributeLoop(reader, attr) { + if (attr.name() == "name") { + set_name(attr.value().toString()); + } + } + + XMLReadLoop(reader, "sequence") { + if (reader->isStartElement()) { + if (reader->name() == "node") { + QString node_id; + + XMLAttributeLoop(reader, attr) { + if (attr.name() == "id") { + node_id = attr.value().toString(); + + // Currently the only thing we need + break; + } + } + + if (node_id.isEmpty()) { + qDebug() << "Found node with no ID"; + continue; + } + + Node* node = NodeFactory::CreateFromID(node_id); + + if (!node) { + qDebug() << "Failed to load" << node_id << "- no node with that ID is installed"; + continue; + } + + node->Load(reader); + + AddNode(node); + } + } + } +} + void Sequence::Save(QXmlStreamWriter *writer) const { writer->writeStartElement("sequence"); diff --git a/app/project/item/sequence/sequence.h b/app/project/item/sequence/sequence.h index 71aa31f6e..6d271b80a 100644 --- a/app/project/item/sequence/sequence.h +++ b/app/project/item/sequence/sequence.h @@ -38,6 +38,11 @@ class Sequence : public Item, public NodeGraph public: Sequence(); + /** + * @brief Load function + */ + virtual void Load(QXmlStreamReader* reader) override; + /** * @brief Save function */ diff --git a/app/project/project.cpp b/app/project/project.cpp index bee92d4a0..fe737490b 100644 --- a/app/project/project.cpp +++ b/app/project/project.cpp @@ -22,11 +22,25 @@ #include +#include "common/xmlreadloop.h" + Project::Project() { root_.set_project(this); } +void Project::Load(QXmlStreamReader *reader) +{ + XMLReadLoop(reader, "project") { + if (reader->isStartElement()) { + if (reader->name() == "folder") { + // Assume this folder is our root + root_.Load(reader); + } + } + } +} + void Project::Save(QXmlStreamWriter *writer) const { writer->writeStartElement("project"); @@ -35,7 +49,7 @@ void Project::Save(QXmlStreamWriter *writer) const root_.Save(writer); - + writer->writeTextElement("ocio", ocio_config_); writer->writeEndElement(); // project } diff --git a/app/project/project.h b/app/project/project.h index 7f3b83d4c..2c32068c0 100644 --- a/app/project/project.h +++ b/app/project/project.h @@ -44,6 +44,8 @@ class Project : public QObject public: Project(); + void Load(QXmlStreamReader* reader); + void Save(QXmlStreamWriter* writer) const; Folder* root(); diff --git a/app/project/projectfilemanagerbase.cpp b/app/project/projectfilemanagerbase.cpp new file mode 100644 index 000000000..5a7ae97ee --- /dev/null +++ b/app/project/projectfilemanagerbase.cpp @@ -0,0 +1,12 @@ +#include "projectfilemanagerbase.h" + +ProjectFileManagerBase::ProjectFileManagerBase() : + cancelled_(false) +{ + +} + +void ProjectFileManagerBase::Cancel() +{ + cancelled_ = true; +} diff --git a/app/project/projectfilemanagerbase.h b/app/project/projectfilemanagerbase.h new file mode 100644 index 000000000..ff227dc6b --- /dev/null +++ b/app/project/projectfilemanagerbase.h @@ -0,0 +1,41 @@ +#ifndef PROJECTFILEMANAGERBASE_H +#define PROJECTFILEMANAGERBASE_H + +#include + +#include "project.h" + +class ProjectFileManagerBase : public QObject +{ + Q_OBJECT +public: + ProjectFileManagerBase(); + +public slots: + /** + * @brief Start the save process + * + * It's recommended to invoke this through Qt signals/slots/QueuedConnection after moving this object to a separate + * thread. + */ + virtual void Start() = 0; + + /** + * @brief Cancel the current save + * + * Always connect to this with a DirectConnection. Otherwise, it'll be queued AFTER the save function is already + * complete. + */ + void Cancel(); + +signals: + void ProgressChanged(int); + + void Finished(); + +private: + QAtomicInt cancelled_; + +}; + +#endif // PROJECTFILEMANAGERBASE_H diff --git a/app/project/projectloadmanager.cpp b/app/project/projectloadmanager.cpp new file mode 100644 index 000000000..0d138629c --- /dev/null +++ b/app/project/projectloadmanager.cpp @@ -0,0 +1,46 @@ +#include "projectloadmanager.h" + +#include +#include + +ProjectLoadManager::ProjectLoadManager(const QString &filename) : + filename_(filename) +{ +} + +void ProjectLoadManager::Start() +{ + QFile project_file(filename_); + + if (project_file.open(QFile::ReadOnly | QFile::Text)) { + QXmlStreamReader reader(&project_file); + + while (!reader.atEnd()) { + reader.readNext(); + + if (reader.isStartElement()) { + if (reader.name() == "version") { + reader.readNext(); + + qDebug() << "Project version:" << reader.text(); + } else if (reader.name() == "project") { + ProjectPtr project = std::make_shared(); + + project->set_filename(filename_); + + project->Load(&reader); + + emit ProjectLoaded(project); + } + } + } + + if (reader.hasError()) { + qDebug() << "Found XML error:" << reader.errorString(); + } + + project_file.close(); + } + + emit Finished(); +} diff --git a/app/project/projectloadmanager.h b/app/project/projectloadmanager.h new file mode 100644 index 000000000..22613162f --- /dev/null +++ b/app/project/projectloadmanager.h @@ -0,0 +1,29 @@ +#ifndef PROJECTLOADMANAGER_H +#define PROJECTLOADMANAGER_H + +#include "projectfilemanagerbase.h" + +class ProjectLoadManager : public ProjectFileManagerBase +{ + Q_OBJECT +public: + ProjectLoadManager(const QString& filename); + +public slots: + /** + * @brief Start the load process + * + * It's recommended to invoke this through Qt signals/slots/QueuedConnection after moving this object to a separate + * thread. + */ + virtual void Start() override; + +signals: + void ProjectLoaded(ProjectPtr project); + +private: + QString filename_; + +}; + +#endif // PROJECTLOADMANAGER_H diff --git a/app/project/projectsavemanager.cpp b/app/project/projectsavemanager.cpp index c53b8df9c..3fa2eb9fa 100644 --- a/app/project/projectsavemanager.cpp +++ b/app/project/projectsavemanager.cpp @@ -1,10 +1,10 @@ #include "projectsavemanager.h" #include +#include ProjectSaveManager::ProjectSaveManager(Project *project) : - project_(project), - cancelled_(false) + project_(project) { } @@ -19,10 +19,14 @@ void ProjectSaveManager::Start() writer.writeStartDocument(); + writer.writeStartElement("olive"); + writer.writeTextElement("version", "0.2.0"); project_->Save(&writer); + writer.writeEndElement(); // olive + writer.writeEndDocument(); project_file.close(); @@ -30,8 +34,3 @@ void ProjectSaveManager::Start() emit Finished(); } - -void ProjectSaveManager::Cancel() -{ - cancelled_ = true; -} diff --git a/app/project/projectsavemanager.h b/app/project/projectsavemanager.h index b49788b6f..f374e58ca 100644 --- a/app/project/projectsavemanager.h +++ b/app/project/projectsavemanager.h @@ -1,12 +1,9 @@ #ifndef PROJECTSAVEMANAGER_H #define PROJECTSAVEMANAGER_H -#include -#include +#include "projectfilemanagerbase.h" -#include "project.h" - -class ProjectSaveManager : public QObject +class ProjectSaveManager : public ProjectFileManagerBase { Q_OBJECT public: @@ -19,26 +16,11 @@ public slots: * It's recommended to invoke this through Qt signals/slots/QueuedConnection after moving this object to a separate * thread. */ - void Start(); - - /** - * @brief Cancel the current save - * - * Always connect to this with a DirectConnection. Otherwise, it'll be queued AFTER the save function is already - * complete. - */ - void Cancel(); - -signals: - void ProgressChanged(int); - - void Finished(); + virtual void Start() override; private: Project* project_; - QAtomicInt cancelled_; - }; #endif // PROJECTSAVEMANAGER_H diff --git a/app/window/mainwindow/mainmenu.cpp b/app/window/mainwindow/mainmenu.cpp index bc5d43df8..41892358f 100644 --- a/app/window/mainwindow/mainmenu.cpp +++ b/app/window/mainwindow/mainmenu.cpp @@ -41,7 +41,7 @@ MainMenu::MainMenu(QMainWindow *parent) : file_menu_ = new Menu(this, this, SLOT(FileMenuAboutToShow())); file_new_menu_ = new Menu(file_menu_); MenuShared::instance()->AddItemsForNewMenu(file_new_menu_); - file_open_item_ = file_menu_->AddItem("openproj", nullptr, nullptr, "Ctrl+O"); + file_open_item_ = file_menu_->AddItem("openproj", Core::instance(), SLOT(OpenProject()), "Ctrl+O"); file_open_recent_menu_ = new Menu(file_menu_); file_open_recent_clear_item_ = file_open_recent_menu_->AddItem("clearopenrecent", nullptr, nullptr); file_save_item_ = file_menu_->AddItem("saveproj", Core::instance(), SLOT(SaveActiveProject()), "Ctrl+S");