From 29b2e5743d492eef2ba56419cad7bb961780541c Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 30 Jan 2022 21:55:19 +0100 Subject: [PATCH] Improve data flow (#27) * Add an editor auto save timer and make the document class a bit cleaner * Fix QTextDocument init and don't save empty docs * Add memory cleanup and rename some private variables --- .../e709ba3f-3141-4b4b-95df-4a8d3e91a8ba.json | 6 +- sample/project/project.json | 4 +- src/editor/doceditor.cpp | 48 ++++++++++-- src/editor/doceditor.h | 3 + src/editor/textedit.cpp | 55 +++++++++++--- src/editor/textedit.h | 12 +++ src/project/document.cpp | 75 ++++++++++--------- src/project/document.h | 29 +++---- src/project/project.cpp | 27 +++---- src/project/project.h | 7 +- src/settings.cpp | 26 +++++-- src/settings.h | 6 ++ 12 files changed, 208 insertions(+), 90 deletions(-) diff --git a/sample/content/e709ba3f-3141-4b4b-95df-4a8d3e91a8ba.json b/sample/content/e709ba3f-3141-4b4b-95df-4a8d3e91a8ba.json index db7530b..7536394 100644 --- a/sample/content/e709ba3f-3141-4b4b-95df-4a8d3e91a8ba.json +++ b/sample/content/e709ba3f-3141-4b4b-95df-4a8d3e91a8ba.json @@ -1,10 +1,14 @@ { "m:created": "2022-01-30T18:54:49", - "m:updated": "2022-01-30T18:58:53", + "m:updated": "2022-01-30T21:17:06", "x:content": [ { "u:fmt": "p:ac", "u:txt": "t|My Novel" + }, + { + "u:fmt": "p:ac", + "u:txt": "t|" } ] } diff --git a/sample/project/project.json b/sample/project/project.json index 6cb74de..561f649 100644 --- a/sample/project/project.json +++ b/sample/project/project.json @@ -1,10 +1,10 @@ { "c:meta": { "m:created": "2021-12-14T22:24:25", - "m:updated": "2022-01-30T18:58:56" + "m:updated": "2022-01-30T21:17:06" }, "c:project": { - "s:last-doc-main": "7e5a1a98-d1a3-44a1-ab4e-2b5d21d92201", + "s:last-doc-main": "e709ba3f-3141-4b4b-95df-4a8d3e91a8ba", "u:project-name": "Sample Project" }, "c:settings": { diff --git a/src/editor/doceditor.cpp b/src/editor/doceditor.cpp index f797fac..2770ddf 100644 --- a/src/editor/doceditor.cpp +++ b/src/editor/doceditor.cpp @@ -20,6 +20,7 @@ */ #include "collett.h" +#include "settings.h" #include "doceditor.h" #include "textedit.h" #include "document.h" @@ -28,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -44,6 +46,8 @@ GuiDocEditor::GuiDocEditor(QWidget *parent) m_document = nullptr; m_textArea = new GuiTextEdit(this); + m_textArea->setReadOnly(true); + m_editToolBar = new GuiEditToolBar(this); QVBoxLayout *outerBox = new QVBoxLayout; @@ -54,6 +58,12 @@ GuiDocEditor::GuiDocEditor(QWidget *parent) this->setLayout(outerBox); + CollettSettings *settings = CollettSettings::instance(); + + // Timers + m_autoSave = new QTimer(this); + m_autoSave->setInterval(settings->editorAutoSave() * 1000); + // Connections connect(m_editToolBar, SIGNAL(documentAction(DocAction)), @@ -62,6 +72,8 @@ GuiDocEditor::GuiDocEditor(QWidget *parent) this, SLOT(editorCharFormatChanged(const QTextCharFormat&))); connect(m_textArea, SIGNAL(currentBlockChanged(const QTextBlock&)), this, SLOT(editorBlockChanged(const QTextBlock&))); + connect(m_autoSave, SIGNAL(timeout()), + this, SLOT(flushEditorData())); } /** @@ -80,6 +92,9 @@ bool GuiDocEditor::openDocument(const QUuid &uuid) { m_document = m_data->project()->document(uuid); m_textArea->setJsonContent(m_document->content()); + m_autoSave->start(); + m_textArea->setReadOnly(false); + return true; } @@ -89,19 +104,21 @@ bool GuiDocEditor::saveDocument() { qWarning() << "No project loaded"; return false; } - if (m_docUuid.isNull()) { + if (!hasDocument()) { qWarning() << "No document to save"; return false; } - QTime startTime = QTime::currentTime(); - m_document->save(m_textArea->toJsonContent()); - QTime endTime = QTime::currentTime(); - qDebug() << "Save file took (ms):" << startTime.msecsTo(endTime); + + m_document->setLocked(true); + m_document->setContent(m_textArea->toJsonContent()); + m_document->setLocked(false); return true; } void GuiDocEditor::closeDocument() { + m_autoSave->stop(); + m_textArea->setReadOnly(true); m_textArea->clear(); m_docUuid = QUuid(); m_document = nullptr; @@ -117,7 +134,7 @@ QUuid GuiDocEditor::currentDocument() const { } bool GuiDocEditor::hasDocument() const { - return m_document != nullptr; + return m_document != nullptr && !m_docUuid.isNull(); } /** @@ -141,4 +158,23 @@ void GuiDocEditor::editorBlockChanged(const QTextBlock &block) { m_editToolBar->m_textIndent->setChecked(blockFormat.textIndent() > 0.0); } +void GuiDocEditor::flushEditorData() { + + qDebug() << "Ding!"; + + if (!m_data->hasProject() || !hasDocument()) { + return; + } + + if (m_textArea->isModified()) { + qDebug() << "Autosaving editor content"; + m_document->setLocked(true); + m_document->setContent(m_textArea->toJsonContent()); + m_document->setLocked(false); + if (m_document->write()) { + m_textArea->setModified(false); + } + } +} + } // namespace Collett diff --git a/src/editor/doceditor.h b/src/editor/doceditor.h index 562c81b..2c00130 100644 --- a/src/editor/doceditor.h +++ b/src/editor/doceditor.h @@ -29,6 +29,7 @@ #include "edittoolbar.h" #include +#include #include #include #include @@ -61,6 +62,7 @@ class GuiDocEditor : public QWidget private: GuiTextEdit *m_textArea; GuiEditToolBar *m_editToolBar; + QTimer *m_autoSave; CollettData *m_data; Document *m_document; @@ -69,6 +71,7 @@ class GuiDocEditor : public QWidget private slots: void editorCharFormatChanged(const QTextCharFormat &fmt); void editorBlockChanged(const QTextBlock &block); + void flushEditorData(); }; } // namespace Collett diff --git a/src/editor/textedit.cpp b/src/editor/textedit.cpp index 0b00738..ff3f64f 100644 --- a/src/editor/textedit.cpp +++ b/src/editor/textedit.cpp @@ -46,12 +46,7 @@ GuiTextEdit::GuiTextEdit(QWidget *parent) { // Settings setAcceptRichText(true); - - // Text Options - QTextOption opts; - opts.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); - document()->setDefaultTextOption(opts); - document()->setDocumentMargin(40); + initDocument(this->document()); CollettSettings *settings = CollettSettings::instance(); m_format = settings->textFormat(); @@ -61,14 +56,37 @@ GuiTextEdit::GuiTextEdit(QWidget *parent) } /** - * Methods - * ======= + * Class Setters + * ============= + */ + +void GuiTextEdit::setModified(bool state) { + this->document()->setModified(state); +} + +/** + * Class Getters + * ============= + */ + +bool GuiTextEdit::isModified() const { + return this->document()->isModified(); +} + +/** + * Class Methods + * ============= */ QJsonArray GuiTextEdit::toJsonContent() { QJsonArray json; + if (this->document()->blockCount() == 1 && this->document()->firstBlock().text().trimmed().isEmpty()) { + // No text content + return json; + } + QTextBlock block = this->document()->firstBlock(); while(block.isValid()) { QJsonObject jsonBlock; @@ -148,12 +166,13 @@ QJsonArray GuiTextEdit::toJsonContent() { void GuiTextEdit::setJsonContent(const QJsonArray &json) { - QTextDocument *doc = this->document(); + QTextDocument *doc = new QTextDocument(this); QTextCursor cursor = QTextCursor(doc); bool isFirst = true; doc->setUndoRedoEnabled(false); doc->clear(); + initDocument(doc); for (const QJsonValue &jsonBlockValue : json) { @@ -264,6 +283,24 @@ void GuiTextEdit::setJsonContent(const QJsonArray &json) { } doc->setUndoRedoEnabled(true); + doc->setModified(false); + + this->setDocument(doc); +} + +/** + * Internal Functions + * ================== + */ + +void GuiTextEdit::initDocument(QTextDocument *doc) { + + // Text Options + QTextOption opts; + opts.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); + doc->setDefaultTextOption(opts); + doc->setDocumentMargin(40); + } /** diff --git a/src/editor/textedit.h b/src/editor/textedit.h index 54ce014..fda7d70 100644 --- a/src/editor/textedit.h +++ b/src/editor/textedit.h @@ -44,6 +44,16 @@ class GuiTextEdit : public QTextEdit GuiTextEdit(QWidget *parent=nullptr); ~GuiTextEdit() {}; + // Setters + + void setModified(bool state); + + // Getters + + bool isModified() const; + + // Methods + QJsonArray toJsonContent(); void setJsonContent(const QJsonArray &json); @@ -52,6 +62,8 @@ class GuiTextEdit : public QTextEdit int m_currentBlockNo = -1; + void initDocument(QTextDocument *doc); + signals: void currentBlockChanged(const QTextBlock &block); diff --git a/src/project/document.cpp b/src/project/document.cpp index 0ec1c79..2cba8ae 100644 --- a/src/project/document.cpp +++ b/src/project/document.cpp @@ -29,32 +29,32 @@ namespace Collett { -Document::Document(Storage *store, const QUuid uuid, Document::Mode mode) +Document::Document(Storage *store, const QUuid uuid) : m_store(store), m_handle(uuid) { - m_empty = true; - m_existing = false; + m_locked = false; m_unsaved = true; - m_mode = mode; m_created = QDateTime::currentDateTime().toString(Qt::ISODate); } /** - * Class Getters + * Class Setters * ============= */ -bool Document::isEmpty() const { - return m_empty; +void Document::setContent(const QJsonArray &content) { + m_content = content; + m_unsaved = true; } -bool Document::isExisting() const { - return m_existing; +void Document::setLocked(bool locked) { + m_locked = locked; } -bool Document::isUnsaved() const { - return m_unsaved; -} +/** + * Class Getters + * ============= + */ QJsonArray Document::content() const { return m_content; @@ -64,6 +64,19 @@ QUuid Document::handle() const { return m_handle; } +/** + * Class State + * =========== + */ + +bool Document::isEmpty() const { + return m_content.isEmpty(); +} + +bool Document::isUnsaved() const { + return m_unsaved; +} + /** * Class Methods * ============= @@ -71,16 +84,14 @@ QUuid Document::handle() const { /**! * @brief Open the document and read the content into the data buffer. - * + * * @param mode either ReadOnly or ReadWrite. * @return true if successful, otherwise false. */ -bool Document::open(const Document::Mode mode) { +bool Document::read() { QJsonObject json; - m_mode = mode; - if (!m_store->loadFile(m_handle, json)) { return false; } @@ -96,34 +107,24 @@ bool Document::open(const Document::Mode mode) { m_content = QJsonArray(); } - m_empty = false; - m_existing = true; - return true; } -/**! - * @brief Update content and save data. - * - * This is a convenience function. - * - * @param content the updated document content. - * @return true if successful, otherwise false. - */ -bool Document::save(const QJsonArray &content) { - m_content = content; - m_empty = false; - return save(); -} - /**! * @brief Save the content in the data buffer to the document file. - * + * * @return true if successful, otherwise false. */ -bool Document::save() { +bool Document::write() { + + if (isEmpty()) { + // Nothing to do + m_unsaved = false; + return true; + } - if (m_mode != Document::ReadWrite || m_empty) { + if (m_locked) { + qInfo() << "Document is locked"; return false; } @@ -136,8 +137,8 @@ bool Document::save() { return false; } - m_existing = true; m_unsaved = false; + return true; } diff --git a/src/project/document.h b/src/project/document.h index 33a3a86..0ac645e 100644 --- a/src/project/document.h +++ b/src/project/document.h @@ -36,32 +36,35 @@ class Document : public QObject Q_OBJECT public: - enum Mode {ReadOnly, ReadWrite}; - - explicit Document(Storage *store, const QUuid uuid, Mode mode=Mode::ReadOnly); + explicit Document(Storage *store, const QUuid uuid); ~Document() {}; + // Setters + + void setContent(const QJsonArray &content); + void setLocked(bool locked); + // Getters - bool isEmpty() const; - bool isExisting() const; - bool isUnsaved() const; QJsonArray content() const; QUuid handle() const; + // Check State + + bool isEmpty() const; + bool isUnsaved() const; + // Methods - bool open(const Mode mode); - bool save(const QJsonArray &content); - bool save(); + bool read(); + bool write(); private: Storage *m_store; QUuid m_handle; - bool m_empty; - bool m_existing; - bool m_unsaved; - Mode m_mode; + + bool m_locked; + bool m_unsaved; // Data Variables diff --git a/src/project/project.cpp b/src/project/project.cpp index bc878ef..8f1433d 100644 --- a/src/project/project.cpp +++ b/src/project/project.cpp @@ -64,6 +64,7 @@ Project::Project(const QString &path) { Project::~Project() { qDebug() << "Destructor: Project"; delete m_storyModel; + qDeleteAll(m_documents.begin(), m_documents.end()); } /** @@ -83,7 +84,7 @@ bool Project::openProject() { if (main) { bool settings = loadSettingsFile(); bool story = loadStoryFile(); - loadContent(); + loadDocuments(); m_isValid = settings && story; } else { m_isValid = false; @@ -103,7 +104,7 @@ bool Project::saveProject() { bool main = m_store->saveProjectFile(); bool settings = saveSettingsFile(); bool story = saveStoryFile(); - saveContent(); + saveDocuments(); return main && settings && story; } @@ -147,12 +148,12 @@ Storage *Project::store() { } Document *Project::document(const QUuid &uuid) { - if (m_content.contains(uuid)) { - return m_content.value(uuid); + if (m_documents.contains(uuid)) { + return m_documents.value(uuid); } else { qDebug() << "Created new document entry for" << uuid.toString(QUuid::WithoutBraces); - Document *doc = new Document(m_store, uuid, Document::ReadWrite); - m_content.insert(uuid, doc); + Document *doc = new Document(m_store, uuid); + m_documents.insert(uuid, doc); return doc; } } @@ -243,9 +244,9 @@ bool Project::saveStoryFile() { return true; } -void Project::loadContent() { +void Project::loadDocuments() { - if (!m_content.isEmpty()) { + if (!m_documents.isEmpty()) { qWarning() << "Project content already loaded"; return; } @@ -254,18 +255,18 @@ void Project::loadContent() { for (const QUuid &uuid : contentList) { qDebug() << "Loading content:" << uuid.toString(QUuid::WithoutBraces); Document *doc = new Document(m_store, uuid); - if (doc->open(Document::ReadWrite)) { - m_content.insert(uuid, doc); + if (doc->read()) { + m_documents.insert(uuid, doc); } } } -void Project::saveContent() { +void Project::saveDocuments() { - for (Document *doc : m_content) { + for (Document *doc : m_documents) { if (doc->isUnsaved()) { qDebug() << "Saving content:" << doc->handle().toString(QUuid::WithoutBraces); - doc->save(); + doc->write(); } } } diff --git a/src/project/project.h b/src/project/project.h index 8ccbb3b..0498d41 100644 --- a/src/project/project.h +++ b/src/project/project.h @@ -91,17 +91,18 @@ class Project : public QObject // Content StoryModel *m_storyModel; - QHash m_content; + QHash m_documents; // File Load & Save bool loadSettingsFile(); bool saveSettingsFile(); + bool loadStoryFile(); bool saveStoryFile(); - void loadContent(); - void saveContent(); + void loadDocuments(); + void saveDocuments(); }; } // namespace Collett diff --git a/src/settings.cpp b/src/settings.cpp index 02cbb69..e357ba3 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -25,8 +25,12 @@ #define CNF_MAIN_WINDOW_SIZE "GuiMain/windowSize" #define CNF_MAIN_SPLIT_SIZES "GuiMain/mainSplitSizes" +#define CNF_EDITOR_AUTO_SAVE "Editor/autoSave" + #define CNF_TEXT_FONT_SIZE "TextFormat/fontSize" +#include + #include #include #include @@ -99,15 +103,15 @@ CollettSettings::CollettSettings() { m_mainWindowSize.setHeight(300); } + // Editor Settings + // --------------- + + m_editorAutoSave = std::max(settings.value(CNF_EDITOR_AUTO_SAVE, 30).toInt(), 5); + // Text Format // ----------- - m_textFontSize = settings.value(CNF_TEXT_FONT_SIZE, (qreal)13.0).toReal(); - - // Check Values - if (m_textFontSize < 5.0) { - m_textFontSize = 5.0; - } + m_textFontSize = std::max(settings.value(CNF_TEXT_FONT_SIZE, (qreal)13.0).toReal(), 5.0); recalculateTextFormats(); } @@ -128,6 +132,8 @@ void CollettSettings::flushSettings() { settings.setValue(CNF_MAIN_WINDOW_SIZE, m_mainWindowSize); settings.setValue(CNF_MAIN_SPLIT_SIZES, intListToVariant(m_mainSplitSizes)); + settings.setValue(CNF_EDITOR_AUTO_SAVE, m_editorAutoSave); + settings.setValue(CNF_TEXT_FONT_SIZE, m_textFontSize); qDebug() << "CollettSettings values saved"; @@ -148,6 +154,10 @@ void CollettSettings::setMainSplitSizes(const QList &sizes) { m_mainSplitSizes = sizes; } +void CollettSettings::setEditorAutoSave(const int interval) { + m_editorAutoSave = interval; +} + void CollettSettings::setTextFontSize(const qreal size) { m_textFontSize = size; recalculateTextFormats(); @@ -166,6 +176,10 @@ QList CollettSettings::mainSplitSizes() const { return m_mainSplitSizes; } +int CollettSettings::editorAutoSave() const { + return m_editorAutoSave; +} + CollettSettings::TextFormat CollettSettings::textFormat() const { return m_textFormat; } diff --git a/src/settings.h b/src/settings.h index 397d490..fae5262 100644 --- a/src/settings.h +++ b/src/settings.h @@ -68,12 +68,14 @@ class CollettSettings : public QObject void setMainWindowSize(const QSize size); void setMainSplitSizes(const QList &sizes); + void setEditorAutoSave(const int interval); void setTextFontSize(const qreal size); // Getters QSize mainWindowSize() const; QList mainSplitSizes() const; + int editorAutoSave() const; TextFormat textFormat() const; private: @@ -84,6 +86,10 @@ class CollettSettings : public QObject QSize m_mainWindowSize; QList m_mainSplitSizes; + // Editor + + int m_editorAutoSave; + // Text Format qreal m_textFontSize;