From 1843ca5bfd5ce533a907f39d0a4fa59b38e3f5db Mon Sep 17 00:00:00 2001 From: Le Tan Date: Sat, 15 May 2021 05:49:29 -0700 Subject: [PATCH] ViewArea session (#1762) --- src/core/buffer/buffer.cpp | 6 +- src/core/buffer/buffer.h | 4 +- src/core/coreconfig.cpp | 13 + src/core/coreconfig.h | 6 + src/core/fileopenparameters.h | 3 + src/core/global.h | 3 + src/core/iconfig.h | 12 +- src/core/sessionconfig.cpp | 15 ++ src/core/sessionconfig.h | 5 + src/data/core/vnotex.json | 3 +- src/widgets/dialogs/settings/generalpage.cpp | 14 ++ src/widgets/dialogs/settings/generalpage.h | 2 + .../dialogs/settings/markdowneditorpage.cpp | 4 +- src/widgets/editors/markdownviewer.cpp | 6 +- src/widgets/markdownviewwindow.cpp | 10 + src/widgets/markdownviewwindow.h | 2 + src/widgets/textviewwindow.cpp | 9 + src/widgets/textviewwindow.h | 2 + src/widgets/viewarea.cpp | 222 +++++++++++++++++- src/widgets/viewarea.h | 21 +- src/widgets/viewareasession.cpp | 101 ++++++++ src/widgets/viewareasession.h | 66 ++++++ src/widgets/viewsplit.cpp | 41 +++- src/widgets/viewsplit.h | 15 +- src/widgets/viewwindow.cpp | 11 + src/widgets/viewwindow.h | 3 + src/widgets/viewwindowsession.cpp | 33 +++ src/widgets/viewwindowsession.h | 26 ++ src/widgets/widgets.pri | 4 + 29 files changed, 626 insertions(+), 36 deletions(-) create mode 100644 src/widgets/viewareasession.cpp create mode 100644 src/widgets/viewareasession.h create mode 100644 src/widgets/viewwindowsession.cpp create mode 100644 src/widgets/viewwindowsession.h diff --git a/src/core/buffer/buffer.cpp b/src/core/buffer/buffer.cpp index 830cd972..fe433c78 100644 --- a/src/core/buffer/buffer.cpp +++ b/src/core/buffer/buffer.cpp @@ -25,7 +25,7 @@ Buffer::Buffer(const BufferParameters &p_parameters, QObject *p_parent) : QObject(p_parent), m_provider(p_parameters.m_provider), - c_id(generateBufferID()), + m_id(generateBufferID()), m_readOnly(m_provider->isReadOnly()) { m_autoSaveTimer = new QTimer(this); @@ -110,9 +110,9 @@ QString Buffer::getResourcePath() const return m_provider->getResourcePath(); } -ID Buffer::getID() const +ID Buffer::getId() const { - return c_id; + return m_id; } const QString &Buffer::getContent() const diff --git a/src/core/buffer/buffer.h b/src/core/buffer/buffer.h index 545f6f89..9c3a6441 100644 --- a/src/core/buffer/buffer.h +++ b/src/core/buffer/buffer.h @@ -87,7 +87,7 @@ namespace vnotex // Return nullptr if not available. QSharedPointer getFile() const; - ID getID() const; + ID getId() const; // Get buffer content. // It may differ from the content on disk. @@ -212,7 +212,7 @@ namespace vnotex bool isBackupFileOfBuffer(const QString &p_file) const; // Will be assigned uniquely once created. - const ID c_id = 0; + const ID m_id = 0; // Revision of contents. int m_revision = 0; diff --git a/src/core/coreconfig.cpp b/src/core/coreconfig.cpp index 7d46b13f..21a9820c 100644 --- a/src/core/coreconfig.cpp +++ b/src/core/coreconfig.cpp @@ -49,6 +49,8 @@ void CoreConfig::init(const QJsonObject &p_app, } loadNoteManagement(appObj, userObj); + + m_recoverLastSessionOnStartEnabled = READBOOL(QStringLiteral("recover_last_session_on_start")); } QJsonObject CoreConfig::toJson() const @@ -58,6 +60,7 @@ QJsonObject CoreConfig::toJson() const obj[QStringLiteral("locale")] = m_locale; obj[QStringLiteral("shortcuts")] = saveShortcuts(); obj[QStringLiteral("toolbar_icon_size")] = m_toolBarIconSize; + obj[QStringLiteral("recover_last_session_on_start")] = m_recoverLastSessionOnStartEnabled; return obj; } @@ -149,3 +152,13 @@ const QStringList &CoreConfig::getExternalNodeExcludePatterns() const { return m_externalNodeExcludePatterns; } + +bool CoreConfig::isRecoverLastSessionOnStartEnabled() const +{ + return m_recoverLastSessionOnStartEnabled; +} + +void CoreConfig::setRecoverLastSessionOnStartEnabled(bool p_enabled) +{ + updateConfig(m_recoverLastSessionOnStartEnabled, p_enabled, this); +} diff --git a/src/core/coreconfig.h b/src/core/coreconfig.h index 31eceb0b..b7f99839 100644 --- a/src/core/coreconfig.h +++ b/src/core/coreconfig.h @@ -63,6 +63,9 @@ namespace vnotex static const QStringList &getAvailableLocales(); + bool isRecoverLastSessionOnStartEnabled() const; + void setRecoverLastSessionOnStartEnabled(bool p_enabled); + private: void loadShortcuts(const QJsonObject &p_app, const QJsonObject &p_user); @@ -84,6 +87,9 @@ namespace vnotex QStringList m_externalNodeExcludePatterns; + // Whether recover last session on start. + bool m_recoverLastSessionOnStartEnabled = true; + static QStringList s_availableLocales; }; } // ns vnotex diff --git a/src/core/fileopenparameters.h b/src/core/fileopenparameters.h index 7dea8b6e..f993b0d4 100644 --- a/src/core/fileopenparameters.h +++ b/src/core/fileopenparameters.h @@ -26,6 +26,9 @@ namespace vnotex // If m_lineNumber > -1, it indicates the line to scroll to after opening the file. // 0-based. int m_lineNumber = -1; + + // Whether always open a new window for file. + bool m_alwaysNewWindow = false; }; } diff --git a/src/core/global.h b/src/core/global.h index bb6d7baf..37701c7d 100644 --- a/src/core/global.h +++ b/src/core/global.h @@ -98,6 +98,9 @@ namespace vnotex FocusPreview, Invalid }; + + enum { InvalidViewSplitId = 0 }; + } // ns vnotex Q_DECLARE_OPERATORS_FOR_FLAGS(vnotex::FindOptions); diff --git a/src/core/iconfig.h b/src/core/iconfig.h index 8557e410..c7f2f773 100644 --- a/src/core/iconfig.h +++ b/src/core/iconfig.h @@ -154,9 +154,7 @@ namespace vnotex } template - static void updateConfig(T &p_cur, - const T &p_new, - IConfig *p_config) + static void updateConfig(T &p_cur, const T &p_new, IConfig *p_config) { if (p_cur == p_new) { return; @@ -167,6 +165,14 @@ namespace vnotex p_config->writeToSettings(); } + template + static void updateConfigWithoutCheck(T &p_cur, const T &p_new, IConfig *p_config) + { + ++p_config->m_revision; + p_cur = p_new; + p_config->writeToSettings(); + } + IConfig *m_topConfig = nullptr; QString m_sessionName; diff --git a/src/core/sessionconfig.cpp b/src/core/sessionconfig.cpp index 034eed1b..dd841a9f 100644 --- a/src/core/sessionconfig.cpp +++ b/src/core/sessionconfig.cpp @@ -64,6 +64,8 @@ void SessionConfig::init() m_exportOption.fromJson(sessionJobj[QStringLiteral("export_option")].toObject()); m_searchOption.fromJson(sessionJobj[QStringLiteral("search_option")].toObject()); + + m_viewAreaSession = readByteArray(sessionJobj, QStringLiteral("viewarea_session")); } void SessionConfig::loadCore(const QJsonObject &p_session) @@ -180,6 +182,7 @@ QJsonObject SessionConfig::toJson() const obj[QStringLiteral("state_geometry")] = saveStateAndGeometry(); obj[QStringLiteral("export_option")] = m_exportOption.toJson(); obj[QStringLiteral("search_option")] = m_searchOption.toJson(); + writeByteArray(obj, QStringLiteral("viewarea_session"), m_viewAreaSession); return obj; } @@ -308,3 +311,15 @@ void SessionConfig::loadStateAndGeometry(const QJsonObject &p_session) m_mainWindowStateGeometry.m_mainState = readByteArray(obj, QStringLiteral("main_window_state")); m_mainWindowStateGeometry.m_mainGeometry = readByteArray(obj, QStringLiteral("main_window_geometry")); } + +QByteArray SessionConfig::getViewAreaSessionAndClear() +{ + QByteArray bytes; + m_viewAreaSession.swap(bytes); + return bytes; +} + +void SessionConfig::setViewAreaSession(const QByteArray &p_bytes) +{ + updateConfigWithoutCheck(m_viewAreaSession, p_bytes, this); +} diff --git a/src/core/sessionconfig.h b/src/core/sessionconfig.h index c468a11b..b7402a9e 100644 --- a/src/core/sessionconfig.h +++ b/src/core/sessionconfig.h @@ -91,6 +91,9 @@ namespace vnotex const SearchOption &getSearchOption() const; void setSearchOption(const SearchOption &p_option); + QByteArray getViewAreaSessionAndClear(); + void setViewAreaSession(const QByteArray &p_obj); + private: void loadCore(const QJsonObject &p_session); @@ -129,6 +132,8 @@ namespace vnotex ExportOption m_exportOption; SearchOption m_searchOption; + + QByteArray m_viewAreaSession; }; } // ns vnotex diff --git a/src/data/core/vnotex.json b/src/data/core/vnotex.json index 047ba745..0bcb819d 100644 --- a/src/data/core/vnotex.json +++ b/src/data/core/vnotex.json @@ -39,7 +39,8 @@ ".git" ] } - } + }, + "recover_last_session_on_start" : true }, "editor" : { "core": { diff --git a/src/widgets/dialogs/settings/generalpage.cpp b/src/widgets/dialogs/settings/generalpage.cpp index 295f46f0..6d91d140 100644 --- a/src/widgets/dialogs/settings/generalpage.cpp +++ b/src/widgets/dialogs/settings/generalpage.cpp @@ -69,6 +69,16 @@ void GeneralPage::setupUI() this, &GeneralPage::pageIsChanged); } #endif + + { + const QString label(tr("Recover last session on start")); + m_recoverLastSessionCheckBox = WidgetsFactory::createCheckBox(label, this); + m_recoverLastSessionCheckBox->setToolTip(tr("Recover last session (like buffers) on start of VNote")); + mainLayout->addRow(m_recoverLastSessionCheckBox); + addSearchItem(label, m_recoverLastSessionCheckBox->toolTip(), m_recoverLastSessionCheckBox); + connect(m_recoverLastSessionCheckBox, &QCheckBox::stateChanged, + this, &GeneralPage::pageIsChanged); + } } void GeneralPage::loadInternal() @@ -92,6 +102,8 @@ void GeneralPage::loadInternal() int toTray = sessionConfig.getMinimizeToSystemTray(); m_systemTrayCheckBox->setChecked(toTray > 0); } + + m_recoverLastSessionCheckBox->setChecked(coreConfig.isRecoverLastSessionOnStartEnabled()); } void GeneralPage::saveInternal() @@ -113,6 +125,8 @@ void GeneralPage::saveInternal() // This will override the -1 state. That is fine. sessionConfig.setMinimizeToSystemTray(m_systemTrayCheckBox->isChecked()); } + + coreConfig.setRecoverLastSessionOnStartEnabled(m_recoverLastSessionCheckBox->isChecked()); } QString GeneralPage::title() const diff --git a/src/widgets/dialogs/settings/generalpage.h b/src/widgets/dialogs/settings/generalpage.h index af4e580e..3a6264e7 100644 --- a/src/widgets/dialogs/settings/generalpage.h +++ b/src/widgets/dialogs/settings/generalpage.h @@ -29,6 +29,8 @@ namespace vnotex QComboBox *m_openGLComboBox = nullptr; QCheckBox *m_systemTrayCheckBox = nullptr; + + QCheckBox *m_recoverLastSessionCheckBox = nullptr; }; } diff --git a/src/widgets/dialogs/settings/markdowneditorpage.cpp b/src/widgets/dialogs/settings/markdowneditorpage.cpp index ace28ffa..00926ddc 100644 --- a/src/widgets/dialogs/settings/markdowneditorpage.cpp +++ b/src/widgets/dialogs/settings/markdowneditorpage.cpp @@ -145,8 +145,8 @@ QGroupBox *MarkdownEditorPage::setupReadGroup() m_zoomFactorSpinBox = WidgetsFactory::createDoubleSpinBox(box); m_zoomFactorSpinBox->setToolTip(tr("Zoom factor in read mode")); - m_zoomFactorSpinBox->setRange(0.25, 10); - m_zoomFactorSpinBox->setSingleStep(0.25); + m_zoomFactorSpinBox->setRange(0.1, 10); + m_zoomFactorSpinBox->setSingleStep(0.1); const QString label(tr("Zoom factor:")); layout->addRow(label, m_zoomFactorSpinBox); diff --git a/src/widgets/editors/markdownviewer.cpp b/src/widgets/editors/markdownviewer.cpp index 835bcc82..2fdb0ff0 100644 --- a/src/widgets/editors/markdownviewer.cpp +++ b/src/widgets/editors/markdownviewer.cpp @@ -360,8 +360,8 @@ void MarkdownViewer::handleWebKeyPress(int p_key, bool p_ctrl, bool p_shift, boo void MarkdownViewer::zoomOut() { qreal factor = zoomFactor(); - if (factor > 0.25) { - factor -= 0.25; + if (factor > 0.1) { + factor -= 0.1; setZoomFactor(factor); emit zoomFactorChanged(factor); } @@ -370,7 +370,7 @@ void MarkdownViewer::zoomOut() void MarkdownViewer::zoomIn() { qreal factor = zoomFactor(); - factor += 0.25; + factor += 0.1; setZoomFactor(factor); emit zoomFactorChanged(factor); } diff --git a/src/widgets/markdownviewwindow.cpp b/src/widgets/markdownviewwindow.cpp index 08cc44dd..736e43c7 100644 --- a/src/widgets/markdownviewwindow.cpp +++ b/src/widgets/markdownviewwindow.cpp @@ -935,3 +935,13 @@ void MarkdownViewWindow::openTwice(const QSharedPointer &p_p Q_ASSERT(!p_paras || !p_paras->m_newFile); handleFileOpenParameters(p_paras); } + +ViewWindowSession MarkdownViewWindow::saveSession() const +{ + auto session = ViewWindow::saveSession(); + if (getBuffer()) { + session.m_lineNumber = isReadMode() ? adapter()->getTopLineNumber() + : m_editor->getCursorPosition().first; + } + return session; +} diff --git a/src/widgets/markdownviewwindow.h b/src/widgets/markdownviewwindow.h index 87270ef1..9f4efd0e 100644 --- a/src/widgets/markdownviewwindow.h +++ b/src/widgets/markdownviewwindow.h @@ -42,6 +42,8 @@ namespace vnotex void openTwice(const QSharedPointer &p_paras) Q_DECL_OVERRIDE; + ViewWindowSession saveSession() const Q_DECL_OVERRIDE; + public slots: void handleEditorConfigChange() Q_DECL_OVERRIDE; diff --git a/src/widgets/textviewwindow.cpp b/src/widgets/textviewwindow.cpp index 1f6686cd..3c258856 100644 --- a/src/widgets/textviewwindow.cpp +++ b/src/widgets/textviewwindow.cpp @@ -244,3 +244,12 @@ void TextViewWindow::handleFileOpenParameters(const QSharedPointerscrollToLine(p_paras->m_lineNumber, true); } } + +ViewWindowSession TextViewWindow::saveSession() const +{ + auto session = ViewWindow::saveSession(); + if (getBuffer()) { + session.m_lineNumber = m_editor->getCursorPosition().first; + } + return session; +} diff --git a/src/widgets/textviewwindow.h b/src/widgets/textviewwindow.h index 6e4ee71a..ad4a4ba9 100644 --- a/src/widgets/textviewwindow.h +++ b/src/widgets/textviewwindow.h @@ -29,6 +29,8 @@ namespace vnotex void openTwice(const QSharedPointer &p_paras) Q_DECL_OVERRIDE; + ViewWindowSession saveSession() const Q_DECL_OVERRIDE; + public slots: void handleEditorConfigChange() Q_DECL_OVERRIDE; diff --git a/src/widgets/viewarea.cpp b/src/widgets/viewarea.cpp index 87804ca9..ecbc0a48 100644 --- a/src/widgets/viewarea.cpp +++ b/src/widgets/viewarea.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include "viewwindow.h" #include "mainwindow.h" @@ -21,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -47,7 +49,9 @@ ViewArea::ViewArea(QWidget *p_parent) return; } - // TODO: save last opened files. + if (ConfigMgr::getInst().getCoreConfig().isRecoverLastSessionOnStartEnabled()) { + saveSession(); + } bool ret = close(false); if (!ret) { @@ -61,6 +65,11 @@ ViewArea::ViewArea(QWidget *p_parent) close(true); }); + if (ConfigMgr::getInst().getCoreConfig().isRecoverLastSessionOnStartEnabled()) { + connect(mainWindow, &MainWindow::mainWindowStarted, + this, &ViewArea::loadSession); + } + connect(&VNoteX::getInst(), &VNoteX::nodeAboutToMove, this, &ViewArea::handleNodeChange); @@ -127,8 +136,6 @@ void ViewArea::setupUI() m_mainLayout = new QVBoxLayout(this); m_mainLayout->setContentsMargins(0, 0, 0, 0); - - showSceneWidget(); } QSize ViewArea::sizeHint() const @@ -145,7 +152,10 @@ QSize ViewArea::sizeHint() const void ViewArea::openBuffer(Buffer *p_buffer, const QSharedPointer &p_paras) { // We allow multiple ViewWindows of the same buffer in different workspaces by default. - auto wins = findBufferInViewSplits(p_buffer); + QVector wins; + if (!p_paras->m_alwaysNewWindow) { + wins = findBufferInViewSplits(p_buffer); + } if (wins.isEmpty()) { if (!m_currentSplit) { addFirstViewSplit(); @@ -193,12 +203,20 @@ QVector ViewArea::findBufferInViewSplits(const Buffer *p_buffer) c return wins; } -ViewSplit *ViewArea::createViewSplit(QWidget *p_parent) +ViewSplit *ViewArea::createViewSplit(QWidget *p_parent, ID p_viewSplitId) { auto workspace = createWorkspace(); m_workspaces.push_back(workspace); - auto split = new ViewSplit(m_workspaces, workspace, p_parent); + ID id = p_viewSplitId; + if (id == InvalidViewSplitId) { + id = m_nextViewSplitId++; + } else { + Q_ASSERT(p_viewSplitId >= m_nextViewSplitId); + m_nextViewSplitId = id + 1; + } + + auto split = new ViewSplit(m_workspaces, workspace, id, p_parent); connect(split, &ViewSplit::viewWindowCloseRequested, this, [this](ViewWindow *p_win) { closeViewWindow(p_win, false, true); @@ -282,7 +300,13 @@ void ViewArea::addFirstViewSplit() hideSceneWidget(); m_mainLayout->addWidget(split); - setCurrentViewSplit(split, false); + postFirstViewSplit(); +} + +void ViewArea::postFirstViewSplit() +{ + Q_ASSERT(!m_splits.isEmpty()); + setCurrentViewSplit(m_splits.first(), false); emit viewSplitsCountChanged(); checkCurrentViewWindowChange(); @@ -446,7 +470,7 @@ QSharedPointer ViewArea::createWorkspace() ID id = 1; QSet usedIds; for (auto ws : m_workspaces) { - usedIds.insert(ws->c_id); + usedIds.insert(ws->m_id); } while (true) { @@ -996,3 +1020,185 @@ QList ViewArea::getAllBuffersInViewSplits() const return bufferSet.values(); } + +void ViewArea::loadSession() +{ + auto &sessionConfig = ConfigMgr::getInst().getSessionConfig(); + auto sessionData = sessionConfig.getViewAreaSessionAndClear(); + + auto session = ViewAreaSession::deserialize(sessionData); + + // Load widgets layout. + if (session.m_root.isEmpty()) { + showSceneWidget(); + } else { + Q_ASSERT(m_splits.isEmpty()); + if (session.m_root.m_type == ViewAreaSession::Node::Type::Splitter) { + // Splitter. + auto splitter = createSplitter(session.m_root.m_orientation, this); + m_mainLayout->addWidget(splitter); + + loadSplitterFromSession(session.m_root, splitter); + } else { + // Just only one ViewSplit. + Q_ASSERT(session.m_root.m_type == ViewAreaSession::Node::Type::ViewSplit); + auto split = createViewSplit(this, session.m_root.m_viewSplitId); + m_splits.push_back(split); + m_mainLayout->addWidget(split); + } + + QHash viewSplitToWorkspace; + + setCurrentViewSplit(m_splits.first(), false); + + // Load invisible workspace. + for (int i = 0; i < session.m_workspaces.size(); ++i) { + const auto &ws = session.m_workspaces[i]; + if (ws.m_viewSplitId != InvalidViewSplitId) { + viewSplitToWorkspace.insert(ws.m_viewSplitId, i); + continue; + } + + for (const auto &winSession : ws.m_viewWindows) { + openViewWindowFromSession(winSession); + } + + // Check if there is any window. + if (m_currentSplit->getViewWindowCount() > 0) { + m_currentSplit->setCurrentViewWindow(ws.m_currentViewWindowIndex); + + // New another workspace. + auto newWs = createWorkspace(); + m_workspaces.push_back(newWs); + m_currentSplit->setWorkspace(newWs); + } + } + + // Load visible workspace. + for (auto split : m_splits) { + setCurrentViewSplit(split, false); + + auto it = viewSplitToWorkspace.find(split->getId()); + Q_ASSERT(it != viewSplitToWorkspace.end()); + + const auto &ws = session.m_workspaces[it.value()]; + + for (const auto &winSession : ws.m_viewWindows) { + openViewWindowFromSession(winSession); + } + + if (m_currentSplit->getViewWindowCount() > 0) { + m_currentSplit->setCurrentViewWindow(ws.m_currentViewWindowIndex); + } + } + + postFirstViewSplit(); + + distributeViewSplits(); + } +} + +void ViewArea::saveSession() const +{ + ViewAreaSession session; + takeSnapshot(session); + + auto &sessionConfig = ConfigMgr::getInst().getSessionConfig(); + sessionConfig.setViewAreaSession(session.serialize()); +} + +static void takeSnapshotOfWidgetNodes(ViewAreaSession::Node &p_node, const QWidget *p_widget, QHash &p_workspaceToViewSplit) +{ + p_node.clear(); + + // Splitter. + auto splitter = dynamic_cast(p_widget); + if (splitter) { + p_node.m_type = ViewAreaSession::Node::Type::Splitter; + p_node.m_orientation = splitter->orientation(); + p_node.m_children.resize(splitter->count()); + + for (int i = 0; i < p_node.m_children.size(); ++i) { + takeSnapshotOfWidgetNodes(p_node.m_children[i], splitter->widget(i), p_workspaceToViewSplit); + } + + return; + } + + // ViewSplit. + auto viewSplit = dynamic_cast(p_widget); + Q_ASSERT(viewSplit); + p_node.m_type = ViewAreaSession::Node::Type::ViewSplit; + p_node.m_viewSplitId = viewSplit->getId(); + + auto ws = viewSplit->getWorkspace(); + if (ws) { + viewSplit->updateStateToWorkspace(); + p_workspaceToViewSplit.insert(ws->m_id, viewSplit->getId()); + } +} + + +void ViewArea::takeSnapshot(ViewAreaSession &p_session) const +{ + QHash workspaceToViewSplit; + + // Widget hirarchy. + p_session.m_root.clear(); + if (!m_splits.isEmpty()) { + auto topWidget = m_mainLayout->itemAt(0)->widget(); + takeSnapshotOfWidgetNodes(p_session.m_root, topWidget, workspaceToViewSplit); + } + + // Workspaces. + p_session.m_workspaces.clear(); + p_session.m_workspaces.reserve(m_workspaces.size()); + for (const auto &ws : m_workspaces) { + p_session.m_workspaces.push_back(ViewAreaSession::Workspace()); + auto &wsSnap = p_session.m_workspaces.last(); + if (ws->m_visible) { + auto it = workspaceToViewSplit.find(ws->m_id); + Q_ASSERT(it != workspaceToViewSplit.end()); + wsSnap.m_viewSplitId = it.value(); + } + wsSnap.m_currentViewWindowIndex = ws->m_currentViewWindowIndex; + for (auto win : ws->m_viewWindows) { + wsSnap.m_viewWindows.push_back(win->saveSession()); + } + } +} + +void ViewArea::loadSplitterFromSession(const ViewAreaSession::Node &p_node, QSplitter *p_splitter) +{ + // @p_splitter is the splitter corresponding to @p_node. + Q_ASSERT(p_node.m_type == ViewAreaSession::Node::Type::Splitter); + + for (const auto &child : p_node.m_children) { + if (child.m_type == ViewAreaSession::Node::Type::Splitter) { + auto childSplitter = createSplitter(child.m_orientation, p_splitter); + p_splitter->addWidget(childSplitter); + + loadSplitterFromSession(child, childSplitter); + } else { + Q_ASSERT(child.m_type == ViewAreaSession::Node::Type::ViewSplit); + auto childSplit = createViewSplit(this, child.m_viewSplitId); + m_splits.push_back(childSplit); + p_splitter->addWidget(childSplit); + } + } +} + +void ViewArea::openViewWindowFromSession(const ViewWindowSession &p_session) +{ + if (p_session.m_bufferPath.isEmpty()) { + return; + } + + auto paras = QSharedPointer::create(); + paras->m_mode = p_session.m_viewWindowMode; + paras->m_readOnly = p_session.m_readOnly; + paras->m_lineNumber = p_session.m_lineNumber; + paras->m_alwaysNewWindow = true; + + emit VNoteX::getInst().openFileRequested(p_session.m_bufferPath, paras); +} diff --git a/src/widgets/viewarea.h b/src/widgets/viewarea.h index 2710c754..02ac46a5 100644 --- a/src/widgets/viewarea.h +++ b/src/widgets/viewarea.h @@ -10,6 +10,7 @@ #include "global.h" #include "navigationmode.h" #include "viewsplit.h" +#include "viewareasession.h" class QLayout; class QSplitter; @@ -29,7 +30,7 @@ namespace vnotex struct ViewWorkspace { explicit ViewWorkspace(ID p_id) - : c_id(p_id) + : m_id(p_id) { } @@ -45,7 +46,7 @@ namespace vnotex m_currentViewWindowIndex = 0; } - const ID c_id = 0; + const ID m_id = 0; // Whether it is displayed by a ViewSplit now. bool m_visible = false; @@ -132,6 +133,10 @@ namespace vnotex void handleNodeChange(Node *p_node, const QSharedPointer &p_event); + void loadSession(); + + void saveSession() const; + private: enum class SplitType { @@ -145,7 +150,7 @@ namespace vnotex // Does not search invisible work spaces. QVector findBufferInViewSplits(const Buffer *p_buffer) const; - ViewSplit *createViewSplit(QWidget *p_parent); + ViewSplit *createViewSplit(QWidget *p_parent, ID p_viewSplitId = InvalidViewSplitId); // A Scene widget will be used when there is no split. // Usually it is used to show some help message. @@ -205,6 +210,14 @@ namespace vnotex QVector getAllViewWindows(ViewSplit *p_split) const; + void takeSnapshot(ViewAreaSession &p_session) const; + + void postFirstViewSplit(); + + void loadSplitterFromSession(const ViewAreaSession::Node &p_node, QSplitter *p_splitter); + + void openViewWindowFromSession(const ViewWindowSession &p_session); + QLayout *m_mainLayout = nullptr; QWidget *m_sceneWidget = nullptr; @@ -226,6 +239,8 @@ namespace vnotex // Timer to check file change outside periodically. QTimer *m_fileCheckTimer = nullptr; + + ID m_nextViewSplitId = InvalidViewSplitId + 1; }; } // ns vnotex diff --git a/src/widgets/viewareasession.cpp b/src/widgets/viewareasession.cpp new file mode 100644 index 00000000..67708405 --- /dev/null +++ b/src/widgets/viewareasession.cpp @@ -0,0 +1,101 @@ +#include "viewareasession.h" + +#include +#include +#include +#include + +using namespace vnotex; + +QDataStream &::vnotex::operator<<(QDataStream &p_ds, const ViewAreaSession::Node &p_node) +{ + p_ds << static_cast(p_node.m_type); + p_ds << static_cast(p_node.m_orientation); + p_ds << p_node.m_viewSplitId; + p_ds << p_node.m_children; + return p_ds; +} + +QDataStream &::vnotex::operator>>(QDataStream &p_ds, ViewAreaSession::Node &p_node) +{ + qint8 tmp = 0; + + p_ds >> tmp; + p_node.m_type = static_cast(tmp); + + p_ds >> tmp; + p_node.m_orientation = static_cast(tmp); + + p_ds >> p_node.m_viewSplitId; + p_ds >> p_node.m_children; + return p_ds; +} + +QDataStream &::vnotex::operator<<(QDataStream &p_ds, const ViewAreaSession::Workspace &p_workspace) +{ + p_ds << p_workspace.m_viewSplitId; + + p_ds << p_workspace.m_viewWindows; + + p_ds << static_cast(p_workspace.m_currentViewWindowIndex); + + return p_ds; +} + +QDataStream &::vnotex::operator>>(QDataStream &p_ds, ViewAreaSession::Workspace &p_workspace) +{ + p_ds >> p_workspace.m_viewSplitId; + + p_ds >> p_workspace.m_viewWindows; + + { + qint32 tmp = 0; + p_ds >> tmp; + p_workspace.m_currentViewWindowIndex = tmp; + } + + return p_ds; +} + +void ViewAreaSession::Node::clear() +{ + m_type = Type::Empty; + m_orientation = Qt::Horizontal; + m_viewSplitId = InvalidViewSplitId; + m_children.clear(); +} + +bool ViewAreaSession::Node::isEmpty() const +{ + return m_type == Type::Empty; +} + +QByteArray ViewAreaSession::serialize() const +{ + QByteArray data; + QDataStream outs(&data, QIODevice::WriteOnly); + outs.setVersion(QDataStream::Qt_5_12); + + outs << m_root; + + outs << m_workspaces; + + return data; +} + +ViewAreaSession ViewAreaSession::deserialize(const QByteArray &p_data) +{ + ViewAreaSession session; + if (p_data.isEmpty()) { + return session; + } + + QDataStream ins(p_data); + ins.setVersion(QDataStream::Qt_5_12); + + ins >> session.m_root; + + ins >> session.m_workspaces; + + return session; +} diff --git a/src/widgets/viewareasession.h b/src/widgets/viewareasession.h new file mode 100644 index 00000000..3a6b8983 --- /dev/null +++ b/src/widgets/viewareasession.h @@ -0,0 +1,66 @@ +#ifndef VIEWAREASESSION_H +#define VIEWAREASESSION_H + +#include +#include +#include + +#include + +#include "viewwindowsession.h" + + +namespace vnotex +{ + struct ViewAreaSession + { + // A node for splitter and ViewSplit hirarchy. + struct Node + { + enum Type + { + Splitter, + ViewSplit, + Empty + }; + + void clear(); + + bool isEmpty() const; + + Type m_type = Type::Empty; + + Qt::Orientation m_orientation = Qt::Horizontal; + + ID m_viewSplitId = InvalidViewSplitId; + + QVector m_children; + }; + + struct Workspace + { + ID m_viewSplitId = InvalidViewSplitId; + + QVector m_viewWindows; + + int m_currentViewWindowIndex = 0; + }; + + QByteArray serialize() const; + + static ViewAreaSession deserialize(const QByteArray &p_data); + + Node m_root; + + QVector m_workspaces; + }; + + + extern QDataStream &operator<<(QDataStream &p_ds, const ViewAreaSession::Node &p_node); + extern QDataStream &operator>>(QDataStream &p_ds, ViewAreaSession::Node &p_node); + + extern QDataStream &operator<<(QDataStream &p_ds, const ViewAreaSession::Workspace &p_workspace); + extern QDataStream &operator>>(QDataStream &p_ds, ViewAreaSession::Workspace &p_workspace); +} + +#endif // VIEWAREASESSION_H diff --git a/src/widgets/viewsplit.cpp b/src/widgets/viewsplit.cpp index 6653f92a..cf1e3c98 100644 --- a/src/widgets/viewsplit.cpp +++ b/src/widgets/viewsplit.cpp @@ -42,8 +42,10 @@ const QString ViewSplit::c_actionButtonForegroundName = "widgets#viewsplit#actio ViewSplit::ViewSplit(const QVector> &p_allWorkspaces, const QSharedPointer &p_workspace, + ID p_id, QWidget *p_parent) : QTabWidget(p_parent), + m_id(p_id), m_allWorkspaces(p_allWorkspaces) { setAcceptDrops(true); @@ -258,17 +260,12 @@ void ViewSplit::setWorkspace(const QSharedPointer &p_workspace) void ViewSplit::updateAndTakeCurrentWorkspace() { if (m_workspace) { - // Store current workspace. - m_workspace->m_currentViewWindowIndex = currentIndex(); + updateStateToWorkspace(); // Take all the view windows out. int cnt = getViewWindowCount(); - m_workspace->m_viewWindows.resize(cnt); for (int i = cnt - 1; i >= 0; --i) { - auto window = getViewWindow(i); - takeViewWindow(window); - - m_workspace->m_viewWindows[i] = window; + takeViewWindow(getViewWindow(i)); } m_workspace->m_visible = false; @@ -279,6 +276,23 @@ void ViewSplit::updateAndTakeCurrentWorkspace() } } +void ViewSplit::updateStateToWorkspace() const +{ + if (!m_workspace) { + return; + } + + Q_ASSERT(m_workspace->m_visible); + + m_workspace->m_currentViewWindowIndex = currentIndex(); + + int cnt = getViewWindowCount(); + m_workspace->m_viewWindows.resize(cnt); + for (int i = cnt - 1; i >= 0; --i) { + m_workspace->m_viewWindows[i] = getViewWindow(i); + } +} + QVector ViewSplit::findBuffer(const Buffer *p_buffer) const { QVector wins; @@ -441,7 +455,7 @@ void ViewSplit::updateMenu(QMenu *p_menu) } for (int i = 0; i < m_allWorkspaces.size(); ++i) { - auto act = new QAction(tr("Workspace %1").arg(m_allWorkspaces[i]->c_id), + auto act = new QAction(tr("Workspace %1").arg(m_allWorkspaces[i]->m_id), m_workspaceActionGroup); act->setData(i); act->setCheckable(true); @@ -735,3 +749,14 @@ void ViewSplit::focus() { focusCurrentViewWindow(); } + +ID ViewSplit::getId() const +{ + return m_id; +} + +void ViewSplit::setCurrentViewWindow(int p_idx) +{ + auto win = getViewWindow(p_idx); + setCurrentViewWindow(win); +} diff --git a/src/widgets/viewsplit.h b/src/widgets/viewsplit.h index 262d5cfd..3e2eaef3 100644 --- a/src/widgets/viewsplit.h +++ b/src/widgets/viewsplit.h @@ -30,9 +30,10 @@ namespace vnotex ViewWindow *m_viewWindow = nullptr; }; - explicit ViewSplit(const QVector> &p_allWorkspaces, - const QSharedPointer &p_workspace, - QWidget *p_parent = nullptr); + ViewSplit(const QVector> &p_allWorkspaces, + const QSharedPointer &p_workspace, + ID p_id, + QWidget *p_parent = nullptr); ~ViewSplit(); @@ -45,6 +46,8 @@ namespace vnotex ViewWindow *getCurrentViewWindow() const; void setCurrentViewWindow(ViewWindow *p_win); + void setCurrentViewWindow(int p_idx); + // @p_win is not deleted. void takeViewWindow(ViewWindow *p_win); @@ -62,6 +65,10 @@ namespace vnotex void focus(); + ID getId() const; + + void updateStateToWorkspace() const; + signals: void viewWindowCloseRequested(ViewWindow *p_win); @@ -122,6 +129,8 @@ namespace vnotex void focusCurrentViewWindow(); + ID m_id = 0; + const QVector> &m_allWorkspaces; QSharedPointer m_workspace; diff --git a/src/widgets/viewwindow.cpp b/src/widgets/viewwindow.cpp index 92046f74..34691b4b 100644 --- a/src/widgets/viewwindow.cpp +++ b/src/widgets/viewwindow.cpp @@ -1079,3 +1079,14 @@ QToolBar *ViewWindow::createToolBar(QWidget *p_parent) toolBar->setProperty(PropertyDefs::c_viewWindowToolBar, true); return toolBar; } + +ViewWindowSession ViewWindow::saveSession() const +{ + ViewWindowSession session; + if (m_buffer) { + session.m_bufferPath = m_buffer->getPath(); + session.m_readOnly = m_buffer->isReadOnly(); + } + session.m_viewWindowMode = getMode(); + return session; +} diff --git a/src/widgets/viewwindow.h b/src/widgets/viewwindow.h index f7360ca6..12cdafb9 100644 --- a/src/widgets/viewwindow.h +++ b/src/widgets/viewwindow.h @@ -9,6 +9,7 @@ #include #include "viewwindowtoolbarhelper.h" +#include "viewwindowsession.h" class QVBoxLayout; class QTimer; @@ -71,6 +72,8 @@ namespace vnotex // Called by upside. void checkFileMissingOrChangedOutsidePeriodically(); + virtual ViewWindowSession saveSession() const; + public slots: virtual void handleEditorConfigChange() = 0; diff --git a/src/widgets/viewwindowsession.cpp b/src/widgets/viewwindowsession.cpp new file mode 100644 index 00000000..20a0b5c5 --- /dev/null +++ b/src/widgets/viewwindowsession.cpp @@ -0,0 +1,33 @@ +#include "viewwindowsession.h" + +using namespace vnotex; + +QDataStream &::vnotex::operator<<(QDataStream &p_ds, const ViewWindowSession &p_session) +{ + p_ds << p_session.m_bufferPath; + p_ds << static_cast(p_session.m_readOnly); + p_ds << static_cast(p_session.m_viewWindowMode); + p_ds << static_cast(p_session.m_lineNumber); + return p_ds; +} + +QDataStream &::vnotex::operator>>(QDataStream &p_ds, ViewWindowSession &p_session) +{ + p_ds >> p_session.m_bufferPath; + + qint8 tmp8 = 0; + + p_ds >> tmp8; + p_session.m_readOnly = tmp8 > 0; + + p_ds >> tmp8; + p_session.m_viewWindowMode = static_cast(tmp8); + + { + qint32 tmp = 0; + p_ds >> tmp; + p_session.m_lineNumber = static_cast(tmp); + } + + return p_ds; +} diff --git a/src/widgets/viewwindowsession.h b/src/widgets/viewwindowsession.h new file mode 100644 index 00000000..9bc70da5 --- /dev/null +++ b/src/widgets/viewwindowsession.h @@ -0,0 +1,26 @@ +#ifndef VIEWWINDOWSESSION_H +#define VIEWWINDOWSESSION_H + +#include + +#include + +namespace vnotex +{ + struct ViewWindowSession + { + QString m_bufferPath; + + bool m_readOnly = false; + + ViewWindowMode m_viewWindowMode = ViewWindowMode::Read; + + // 0-based. + int m_lineNumber = -1; + }; + + extern QDataStream &operator<<(QDataStream &p_ds, const ViewWindowSession &p_session); + extern QDataStream &operator>>(QDataStream &p_ds, ViewWindowSession &p_session); +} + +#endif // VIEWWINDOWSESSION_H diff --git a/src/widgets/widgets.pri b/src/widgets/widgets.pri index 98c11012..46923601 100644 --- a/src/widgets/widgets.pri +++ b/src/widgets/widgets.pri @@ -57,8 +57,10 @@ SOURCES += \ $$PWD/textviewwindow.cpp \ $$PWD/toolbarhelper.cpp \ $$PWD/treeview.cpp \ + $$PWD/viewareasession.cpp \ $$PWD/viewsplit.cpp \ $$PWD/viewwindow.cpp \ + $$PWD/viewwindowsession.cpp \ $$PWD/viewwindowtoolbarhelper.cpp \ $$PWD/webpage.cpp \ $$PWD/webviewer.cpp \ @@ -149,8 +151,10 @@ HEADERS += \ $$PWD/textviewwindowhelper.h \ $$PWD/toolbarhelper.h \ $$PWD/treeview.h \ + $$PWD/viewareasession.h \ $$PWD/viewsplit.h \ $$PWD/viewwindow.h \ + $$PWD/viewwindowsession.h \ $$PWD/viewwindowtoolbarhelper.h \ $$PWD/webpage.h \ $$PWD/webviewer.h \