From cf988e6fa63ff71e73a71e47ce531a0ad3f7a886 Mon Sep 17 00:00:00 2001 From: Le Tan Date: Fri, 23 Jul 2021 20:24:42 +0800 Subject: [PATCH] History: support history --- libs/vtextedit | 2 +- src/core/buffermgr.cpp | 15 +- src/core/buffermgr.h | 3 - src/core/core.pri | 4 + src/core/coreconfig.cpp | 11 + src/core/coreconfig.h | 7 + src/core/historyitem.cpp | 28 +++ src/core/historyitem.h | 32 +++ src/core/historymgr.cpp | 182 ++++++++++++++ src/core/historymgr.h | 81 +++++++ src/core/iconfig.h | 6 + src/core/notebook/bundlenotebook.cpp | 29 ++- src/core/notebook/bundlenotebook.h | 8 + src/core/notebook/bundlenotebookfactory.cpp | 5 +- src/core/notebook/externalnode.h | 1 + src/core/notebook/notebook.h | 7 +- src/core/notebookbackend/inotebookbackend.cpp | 6 + src/core/notebookbackend/inotebookbackend.h | 2 + src/core/notebookconfigmgr/notebookconfig.cpp | 25 +- src/core/notebookconfigmgr/notebookconfig.h | 12 +- src/core/notebookmgr.cpp | 12 + src/core/notebookmgr.h | 4 + src/core/sessionconfig.cpp | 39 +++ src/core/sessionconfig.h | 11 + src/data/core/vnotex.json | 8 +- src/utils/pathutils.cpp | 9 + src/utils/pathutils.h | 2 + src/utils/widgetutils.cpp | 4 + src/widgets/editors/graphhelper.cpp | 17 +- src/widgets/editors/graphhelper.h | 6 + src/widgets/editors/markdownvieweradapter.cpp | 2 + src/widgets/editors/previewhelper.cpp | 2 + src/widgets/historypanel.cpp | 227 ++++++++++++++++++ src/widgets/historypanel.h | 67 ++++++ src/widgets/listwidget.cpp | 12 + src/widgets/listwidget.h | 7 + src/widgets/mainwindow.cpp | 32 ++- src/widgets/mainwindow.h | 8 + src/widgets/snippetpanel.cpp | 6 +- src/widgets/snippetpanel.h | 2 +- src/widgets/viewarea.cpp | 43 ++++ src/widgets/viewarea.h | 2 + src/widgets/widgets.pri | 2 + 43 files changed, 953 insertions(+), 37 deletions(-) create mode 100644 src/core/historyitem.cpp create mode 100644 src/core/historyitem.h create mode 100644 src/core/historymgr.cpp create mode 100644 src/core/historymgr.h create mode 100644 src/widgets/historypanel.cpp create mode 100644 src/widgets/historypanel.h diff --git a/libs/vtextedit b/libs/vtextedit index a15d8591..47902164 160000 --- a/libs/vtextedit +++ b/libs/vtextedit @@ -1 +1 @@ -Subproject commit a15d859141506142a11cbf843a2d93124ea65bfa +Subproject commit 47902164b0e11f5debfb40deb649d451d650660f diff --git a/src/core/buffermgr.cpp b/src/core/buffermgr.cpp index dcd74086..3c59db33 100644 --- a/src/core/buffermgr.cpp +++ b/src/core/buffermgr.cpp @@ -105,7 +105,7 @@ void BufferMgr::open(const QString &p_filePath, const QSharedPointerhasContent()) { open(node.data(), p_paras); @@ -187,16 +187,3 @@ void BufferMgr::addBuffer(Buffer *p_buffer) p_buffer->deleteLater(); }); } - -QSharedPointer BufferMgr::loadNodeByPath(const QString &p_path) -{ - const auto ¬ebooks = VNoteX::getInst().getNotebookMgr().getNotebooks(); - for (const auto &nb : notebooks) { - auto node = nb->loadNodeByPath(p_path); - if (node) { - return node; - } - } - - return nullptr; -} diff --git a/src/core/buffermgr.h b/src/core/buffermgr.h index 0b1ac873..dd2fc312 100644 --- a/src/core/buffermgr.h +++ b/src/core/buffermgr.h @@ -42,9 +42,6 @@ namespace vnotex void addBuffer(Buffer *p_buffer); - // Try to load @p_path as a node if it is within one notebook. - QSharedPointer loadNodeByPath(const QString &p_path); - QSharedPointer> m_bufferServer; // Managed by QObject. diff --git a/src/core/core.pri b/src/core/core.pri index 85112ee8..6c0eb8eb 100644 --- a/src/core/core.pri +++ b/src/core/core.pri @@ -17,6 +17,8 @@ SOURCES += \ $$PWD/editorconfig.cpp \ $$PWD/externalfile.cpp \ $$PWD/file.cpp \ + $$PWD/historyitem.cpp \ + $$PWD/historymgr.cpp \ $$PWD/htmltemplatehelper.cpp \ $$PWD/logger.cpp \ $$PWD/mainconfig.cpp \ @@ -43,6 +45,8 @@ HEADERS += \ $$PWD/file.h \ $$PWD/filelocator.h \ $$PWD/fileopenparameters.h \ + $$PWD/historyitem.h \ + $$PWD/historymgr.h \ $$PWD/htmltemplatehelper.h \ $$PWD/location.h \ $$PWD/logger.h \ diff --git a/src/core/coreconfig.cpp b/src/core/coreconfig.cpp index 21a9820c..ecba94e1 100644 --- a/src/core/coreconfig.cpp +++ b/src/core/coreconfig.cpp @@ -51,6 +51,11 @@ void CoreConfig::init(const QJsonObject &p_app, loadNoteManagement(appObj, userObj); m_recoverLastSessionOnStartEnabled = READBOOL(QStringLiteral("recover_last_session_on_start")); + + m_historyMaxCount = READINT(QStringLiteral("history_max_count")); + if (m_historyMaxCount < 0) { + m_historyMaxCount = 100; + } } QJsonObject CoreConfig::toJson() const @@ -61,6 +66,7 @@ QJsonObject CoreConfig::toJson() const obj[QStringLiteral("shortcuts")] = saveShortcuts(); obj[QStringLiteral("toolbar_icon_size")] = m_toolBarIconSize; obj[QStringLiteral("recover_last_session_on_start")] = m_recoverLastSessionOnStartEnabled; + obj[QStringLiteral("history_max_count")] = m_historyMaxCount; return obj; } @@ -162,3 +168,8 @@ void CoreConfig::setRecoverLastSessionOnStartEnabled(bool p_enabled) { updateConfig(m_recoverLastSessionOnStartEnabled, p_enabled, this); } + +int CoreConfig::getHistoryMaxCount() const +{ + return m_historyMaxCount; +} diff --git a/src/core/coreconfig.h b/src/core/coreconfig.h index 85cbbca7..41e81161 100644 --- a/src/core/coreconfig.h +++ b/src/core/coreconfig.h @@ -26,6 +26,7 @@ namespace vnotex SearchDock, SnippetDock, LocationListDock, + HistoryDock, Search, NavigationMode, LocateNode, @@ -61,6 +62,7 @@ namespace vnotex MoveOneSplitDown, MoveOneSplitUp, MoveOneSplitRight, + OpenLastClosedFile, MaxShortcut }; Q_ENUM(Shortcut) @@ -92,6 +94,8 @@ namespace vnotex bool isRecoverLastSessionOnStartEnabled() const; void setRecoverLastSessionOnStartEnabled(bool p_enabled); + int getHistoryMaxCount() const; + private: friend class MainConfig; @@ -118,6 +122,9 @@ namespace vnotex // Whether recover last session on start. bool m_recoverLastSessionOnStartEnabled = true; + // Max count of the history items for each notebook and session config. + int m_historyMaxCount = 100; + static QStringList s_availableLocales; }; } // ns vnotex diff --git a/src/core/historyitem.cpp b/src/core/historyitem.cpp new file mode 100644 index 00000000..4ac68cd2 --- /dev/null +++ b/src/core/historyitem.cpp @@ -0,0 +1,28 @@ +#include "historyitem.h" + +#include + +using namespace vnotex; + +HistoryItem::HistoryItem(const QString &p_path, int p_lineNumber, const QDateTime &p_lastAccessedTimeUtc) + : m_path(p_path), + m_lineNumber(p_lineNumber), + m_lastAccessedTimeUtc(p_lastAccessedTimeUtc) +{ +} + +QJsonObject HistoryItem::toJson() const +{ + QJsonObject jobj; + jobj[QStringLiteral("path")] = m_path; + jobj[QStringLiteral("line_number")] = m_lineNumber; + jobj[QStringLiteral("last_accessed_time")] = Utils::dateTimeStringUniform(m_lastAccessedTimeUtc); + return jobj; +} + +void HistoryItem::fromJson(const QJsonObject &p_jobj) +{ + m_path = p_jobj[QStringLiteral("path")].toString(); + m_lineNumber = p_jobj[QStringLiteral("line_number")].toInt(); + m_lastAccessedTimeUtc = Utils::dateTimeFromStringUniform(p_jobj[QStringLiteral("last_accessed_time")].toString()); +} diff --git a/src/core/historyitem.h b/src/core/historyitem.h new file mode 100644 index 00000000..5044c73d --- /dev/null +++ b/src/core/historyitem.h @@ -0,0 +1,32 @@ +#ifndef HISTORYITEM_H +#define HISTORYITEM_H + +#include +#include +#include + +namespace vnotex +{ + struct HistoryItem + { + HistoryItem() = default; + + HistoryItem(const QString &p_path, + int p_lineNumber, + const QDateTime &p_lastAccessedTimeUtc); + + QJsonObject toJson() const; + + void fromJson(const QJsonObject &p_jobj); + + // Relative path if it is a node within a notebook. + QString m_path; + + // 0-based. + int m_lineNumber = -1; + + QDateTime m_lastAccessedTimeUtc; + }; +} + +#endif // HISTORYITEM_H diff --git a/src/core/historymgr.cpp b/src/core/historymgr.cpp new file mode 100644 index 00000000..7ead6db0 --- /dev/null +++ b/src/core/historymgr.cpp @@ -0,0 +1,182 @@ +#include "historymgr.h" + +#include + +#include "configmgr.h" +#include "sessionconfig.h" +#include "coreconfig.h" +#include "vnotex.h" +#include "notebookmgr.h" +#include + +using namespace vnotex; + +bool HistoryItemFull::operator<(const HistoryItemFull &p_other) const +{ + if (m_item.m_lastAccessedTimeUtc < p_other.m_item.m_lastAccessedTimeUtc) { + return true; + } else if (m_item.m_lastAccessedTimeUtc > p_other.m_item.m_lastAccessedTimeUtc) { + return false; + } else { + return m_item.m_path < p_other.m_item.m_path; + } +} + + +int HistoryMgr::s_maxHistoryCount = 100; + +HistoryMgr::HistoryMgr() +{ + s_maxHistoryCount = ConfigMgr::getInst().getCoreConfig().getHistoryMaxCount(); + + connect(&VNoteX::getInst().getNotebookMgr(), &NotebookMgr::notebooksUpdated, + this, &HistoryMgr::loadHistory); + + loadHistory(); +} + +static bool historyPtrCmp(const QSharedPointer &p_a, const QSharedPointer &p_b) +{ + return *p_a < *p_b; +} + +void HistoryMgr::loadHistory() +{ + m_history.clear(); + + // Load from session. + { + const auto &history = ConfigMgr::getInst().getSessionConfig().getHistory(); + for (const auto &item : history) { + auto fullItem = QSharedPointer::create(); + fullItem->m_item = item; + m_history.push_back(fullItem); + } + } + + // Load from notebooks. + { + const auto ¬ebooks = VNoteX::getInst().getNotebookMgr().getNotebooks(); + for (const auto &nb : notebooks) { + const auto &history = nb->getHistory(); + for (const auto &item : history) { + auto fullItem = QSharedPointer::create(); + fullItem->m_item = item; + fullItem->m_notebookName = nb->getName(); + m_history.push_back(fullItem); + } + } + } + + std::sort(m_history.begin(), m_history.end(), historyPtrCmp); + + qDebug() << "loaded" << m_history.size() << "history items"; + + emit historyUpdated(); +} + +const QVector> &HistoryMgr::getHistory() const +{ + return m_history; +} + +void HistoryMgr::add(const QString &p_path, + int p_lineNumber, + ViewWindowMode p_mode, + bool p_readOnly, + Notebook *p_notebook) +{ + if (p_path.isEmpty() || s_maxHistoryCount == 0) { + return; + } + + HistoryItem item(p_path, p_lineNumber, QDateTime::currentDateTimeUtc()); + + if (p_notebook) { + p_notebook->addHistory(item); + } else { + auto &sessionConfig = ConfigMgr::getInst().getSessionConfig(); + sessionConfig.addHistory(item); + } + + // Maintain the combined queue. + { + for (int i = m_history.size() - 1; i >= 0; --i) { + if (m_history[i]->m_item.m_path == item.m_path) { + // Erase it. + m_history.remove(i); + break; + } + } + + auto fullItem = QSharedPointer::create(); + fullItem->m_item = item; + if (p_notebook) { + fullItem->m_notebookName = p_notebook->getName(); + } + m_history.append(fullItem); + } + + // Update m_lastClosedFiles. + { + for (int i = m_lastClosedFiles.size() - 1; i >= 0; --i) { + if (m_lastClosedFiles[i].m_path == p_path) { + m_lastClosedFiles.remove(i); + break; + } + } + + m_lastClosedFiles.append(LastClosedFile()); + auto &file = m_lastClosedFiles.back(); + file.m_path = p_path; + file.m_lineNumber = p_lineNumber; + file.m_mode = p_mode; + file.m_readOnly = p_readOnly; + + if (m_lastClosedFiles.size() > 100) { + m_lastClosedFiles.remove(0, m_lastClosedFiles.size() - 100); + } + } + + emit historyUpdated(); +} + +void HistoryMgr::insertHistoryItem(QVector &p_history, const HistoryItem &p_item) +{ + for (int i = p_history.size() - 1; i >= 0; --i) { + if (p_history[i].m_path == p_item.m_path) { + // Erase it. + p_history.remove(i); + break; + } + } + + p_history.append(p_item); + + if (p_history.size() > s_maxHistoryCount) { + p_history.remove(0, p_history.size() - s_maxHistoryCount); + } +} + +void HistoryMgr::clear() +{ + ConfigMgr::getInst().getSessionConfig().clearHistory(); + + const auto ¬ebooks = VNoteX::getInst().getNotebookMgr().getNotebooks(); + for (const auto &nb : notebooks) { + nb->clearHistory(); + } + + loadHistory(); +} + +HistoryMgr::LastClosedFile HistoryMgr::popLastClosedFile() +{ + if (m_lastClosedFiles.isEmpty()) { + return LastClosedFile(); + } + + auto file = m_lastClosedFiles.back(); + m_lastClosedFiles.pop_back(); + return file; +} diff --git a/src/core/historymgr.h b/src/core/historymgr.h new file mode 100644 index 00000000..73e810d5 --- /dev/null +++ b/src/core/historymgr.h @@ -0,0 +1,81 @@ +#ifndef HISTORYMGR_H +#define HISTORYMGR_H + +#include +#include +#include + +#include "noncopyable.h" +#include "historyitem.h" +#include "global.h" + +namespace vnotex +{ + class Notebook; + + struct HistoryItemFull + { + bool operator<(const HistoryItemFull &p_other) const; + + HistoryItem m_item; + + QString m_notebookName; + }; + + // Combine the history from all notebooks and from SessionConfig. + // SessionConfig will store history about external files. + // Also provide stack of files accessed during current session, which could be re-opened + // via Ctrl+Shit+T. + class HistoryMgr : public QObject, private Noncopyable + { + Q_OBJECT + public: + struct LastClosedFile + { + QString m_path; + + int m_lineNumber = 0; + + ViewWindowMode m_mode = ViewWindowMode::Read; + + bool m_readOnly = false; + }; + + static HistoryMgr &getInst() + { + static HistoryMgr inst; + return inst; + } + + const QVector> &getHistory() const; + + void add(const QString &p_path, + int p_lineNumber, + ViewWindowMode p_mode, + bool p_readOnly, + Notebook *p_notebook); + + void clear(); + + LastClosedFile popLastClosedFile(); + + static void insertHistoryItem(QVector &p_history, const HistoryItem &p_item); + + signals: + void historyUpdated(); + + private: + HistoryMgr(); + + void loadHistory(); + + // Sorted by last accessed time ascendingly. + QVector> m_history; + + QVector m_lastClosedFiles; + + static int s_maxHistoryCount; + }; +} + +#endif // HISTORYMGR_H diff --git a/src/core/iconfig.h b/src/core/iconfig.h index dc4fe2d4..a8cee1ff 100644 --- a/src/core/iconfig.h +++ b/src/core/iconfig.h @@ -57,6 +57,12 @@ namespace vnotex return m_revision; } + void update() + { + ++m_revision; + writeToSettings(); + } + protected: ConfigMgr *getMgr() const { diff --git a/src/core/notebook/bundlenotebook.cpp b/src/core/notebook/bundlenotebook.cpp index c4929eab..81bbb32e 100644 --- a/src/core/notebook/bundlenotebook.cpp +++ b/src/core/notebook/bundlenotebook.cpp @@ -5,16 +5,18 @@ #include #include #include +#include +#include using namespace vnotex; BundleNotebook::BundleNotebook(const NotebookParameters &p_paras, + const QSharedPointer &p_notebookConfig, QObject *p_parent) : Notebook(p_paras, p_parent) { - auto configMgr = getBundleNotebookConfigMgr(); - auto config = configMgr->readNotebookConfig(); - m_nextNodeId = config->m_nextNodeId; + m_nextNodeId = p_notebookConfig->m_nextNodeId; + m_history = p_notebookConfig->m_history; } BundleNotebookConfigMgr *BundleNotebook::getBundleNotebookConfigMgr() const @@ -58,3 +60,24 @@ void BundleNotebook::remove() .arg(getRootFolderAbsolutePath()); } } + +const QVector &BundleNotebook::getHistory() const +{ + return m_history; +} + +void BundleNotebook::addHistory(const HistoryItem &p_item) +{ + HistoryItem item(p_item); + item.m_path = getBackend()->getRelativePath(item.m_path); + HistoryMgr::insertHistoryItem(m_history, p_item); + + updateNotebookConfig(); +} + +void BundleNotebook::clearHistory() +{ + m_history.clear(); + + updateNotebookConfig(); +} diff --git a/src/core/notebook/bundlenotebook.h b/src/core/notebook/bundlenotebook.h index 7ec9aa01..6776b965 100644 --- a/src/core/notebook/bundlenotebook.h +++ b/src/core/notebook/bundlenotebook.h @@ -7,12 +7,14 @@ namespace vnotex { class BundleNotebookConfigMgr; + class NotebookConfig; class BundleNotebook : public Notebook { Q_OBJECT public: BundleNotebook(const NotebookParameters &p_paras, + const QSharedPointer &p_notebookConfig, QObject *p_parent = nullptr); ID getNextNodeId() const Q_DECL_OVERRIDE; @@ -25,10 +27,16 @@ namespace vnotex void remove() Q_DECL_OVERRIDE; + const QVector &getHistory() const Q_DECL_OVERRIDE; + void addHistory(const HistoryItem &p_item) Q_DECL_OVERRIDE; + void clearHistory() Q_DECL_OVERRIDE; + private: BundleNotebookConfigMgr *getBundleNotebookConfigMgr() const; ID m_nextNodeId = 1; + + QVector m_history; }; } // ns vnotex diff --git a/src/core/notebook/bundlenotebookfactory.cpp b/src/core/notebook/bundlenotebookfactory.cpp index 9e9d1345..2a0dbbc0 100644 --- a/src/core/notebook/bundlenotebookfactory.cpp +++ b/src/core/notebook/bundlenotebookfactory.cpp @@ -55,7 +55,8 @@ QSharedPointer BundleNotebookFactory::newNotebook(const NotebookParame p_paras.m_notebookConfigMgr->createEmptySkeleton(p_paras); - auto notebook = QSharedPointer::create(p_paras); + auto nbConfig = BundleNotebookConfigMgr::readNotebookConfig(p_paras.m_notebookBackend); + auto notebook = QSharedPointer::create(p_paras, nbConfig); return notebook; } @@ -78,7 +79,7 @@ QSharedPointer BundleNotebookFactory::createNotebook(const NotebookMgr nbConfig->m_versionController, nbConfig->m_notebookConfigMgr); checkParameters(*paras); - auto notebook = QSharedPointer::create(*paras); + auto notebook = QSharedPointer::create(*paras, nbConfig); return notebook; } diff --git a/src/core/notebook/externalnode.h b/src/core/notebook/externalnode.h index a8d033e8..5084abdb 100644 --- a/src/core/notebook/externalnode.h +++ b/src/core/notebook/externalnode.h @@ -19,6 +19,7 @@ namespace vnotex ExternalNode(Node *p_parent, const QString &p_name, Type p_type); + // Get parent node. Node *getNode() const; const QString &getName() const; diff --git a/src/core/notebook/notebook.h b/src/core/notebook/notebook.h index 94b46d85..704076c4 100644 --- a/src/core/notebook/notebook.h +++ b/src/core/notebook/notebook.h @@ -6,8 +6,9 @@ #include #include "notebookparameters.h" -#include "../global.h" +#include #include "node.h" +#include namespace vnotex { @@ -130,6 +131,10 @@ namespace vnotex void reloadNodes(); + virtual const QVector &getHistory() const = 0; + virtual void addHistory(const HistoryItem &p_item) = 0; + virtual void clearHistory() = 0; + static const QString c_defaultAttachmentFolder; static const QString c_defaultImageFolder; diff --git a/src/core/notebookbackend/inotebookbackend.cpp b/src/core/notebookbackend/inotebookbackend.cpp index 80ef1049..c7825e9a 100644 --- a/src/core/notebookbackend/inotebookbackend.cpp +++ b/src/core/notebookbackend/inotebookbackend.cpp @@ -21,3 +21,9 @@ QString INotebookBackend::getFullPath(const QString &p_path) const constrainPath(p_path); return QDir(m_rootPath).filePath(p_path); } + +QString INotebookBackend::getRelativePath(const QString &p_path) const +{ + constrainPath(p_path); + return PathUtils::relativePath(m_rootPath, p_path); +} diff --git a/src/core/notebookbackend/inotebookbackend.h b/src/core/notebookbackend/inotebookbackend.h index a2f1199f..5ba0f7a8 100644 --- a/src/core/notebookbackend/inotebookbackend.h +++ b/src/core/notebookbackend/inotebookbackend.h @@ -65,6 +65,8 @@ namespace vnotex QString getFullPath(const QString &p_path) const; + QString getRelativePath(const QString &p_path) const; + virtual bool exists(const QString &p_path) const = 0; virtual bool existsFile(const QString &p_path) const = 0; diff --git a/src/core/notebookconfigmgr/notebookconfig.cpp b/src/core/notebookconfigmgr/notebookconfig.cpp index 44ca9798..f80b4470 100644 --- a/src/core/notebookconfigmgr/notebookconfig.cpp +++ b/src/core/notebookconfigmgr/notebookconfig.cpp @@ -58,6 +58,8 @@ QJsonObject NotebookConfig::toJson() const jobj[NotebookConfig::c_configMgr] = m_notebookConfigMgr; jobj[NotebookConfig::c_nextNodeId] = QString::number(m_nextNodeId); + jobj[QStringLiteral("history")] = saveHistory(); + return jobj; } @@ -69,7 +71,7 @@ void NotebookConfig::fromJson(const QJsonObject &p_jobj) || !p_jobj.contains(NotebookConfig::c_versionController) || !p_jobj.contains(NotebookConfig::c_configMgr)) { Exception::throwOne(Exception::Type::InvalidArgument, - QString("fail to read notebook configuration from JSON (%1)").arg(QJsonObjectToString(p_jobj))); + QString("failed to read notebook configuration from JSON (%1)").arg(QJsonObjectToString(p_jobj))); return; } @@ -90,6 +92,8 @@ void NotebookConfig::fromJson(const QJsonObject &p_jobj) m_nextNodeId = BundleNotebookConfigMgr::RootNodeId; } } + + loadHistory(p_jobj); } QSharedPointer NotebookConfig::fromNotebook(const QString &p_version, @@ -106,6 +110,25 @@ QSharedPointer NotebookConfig::fromNotebook(const QString &p_ver config->m_versionController = p_notebook->getVersionController()->getName(); config->m_notebookConfigMgr = p_notebook->getConfigMgr()->getName(); config->m_nextNodeId = p_notebook->getNextNodeId(); + config->m_history = p_notebook->getHistory(); return config; } + +QJsonArray NotebookConfig::saveHistory() const +{ + QJsonArray arr; + for (const auto &item : m_history) { + arr.append(item.toJson()); + } + return arr; +} + +void NotebookConfig::loadHistory(const QJsonObject &p_jobj) +{ + auto arr = p_jobj[QStringLiteral("history")].toArray(); + m_history.resize(arr.size()); + for (int i = 0; i < arr.size(); ++i) { + m_history[i].fromJson(arr[i].toObject()); + } +} diff --git a/src/core/notebookconfigmgr/notebookconfig.h b/src/core/notebookconfigmgr/notebookconfig.h index f7bdfbfd..c54bcb6b 100644 --- a/src/core/notebookconfigmgr/notebookconfig.h +++ b/src/core/notebookconfigmgr/notebookconfig.h @@ -4,9 +4,12 @@ #include #include #include +#include +#include #include "bundlenotebookconfigmgr.h" -#include "global.h" +#include +#include namespace vnotex { @@ -45,6 +48,13 @@ namespace vnotex ID m_nextNodeId = BundleNotebookConfigMgr::RootNodeId + 1; + QVector m_history; + + private: + QJsonArray saveHistory() const; + + void loadHistory(const QJsonObject &p_jobj); + static const QString c_version; static const QString c_name; diff --git a/src/core/notebookmgr.cpp b/src/core/notebookmgr.cpp index b489a8ff..1c97d6f0 100644 --- a/src/core/notebookmgr.cpp +++ b/src/core/notebookmgr.cpp @@ -374,3 +374,15 @@ void NotebookMgr::addNotebook(const QSharedPointer &p_notebook) emit notebookUpdated(notebook); }); } + +QSharedPointer NotebookMgr::loadNodeByPath(const QString &p_path) +{ + for (const auto &nb : m_notebooks) { + auto node = nb->loadNodeByPath(p_path); + if (node) { + return node; + } + } + + return nullptr; +} diff --git a/src/core/notebookmgr.h b/src/core/notebookmgr.h index e0957033..46ecf7df 100644 --- a/src/core/notebookmgr.h +++ b/src/core/notebookmgr.h @@ -21,6 +21,7 @@ namespace vnotex class INotebookBackendFactory; class INotebookFactory; class NotebookParameters; + class Node; class NotebookMgr : public QObject { @@ -67,6 +68,9 @@ namespace vnotex void removeNotebook(ID p_id); + // Try to load @p_path as a node if it is within one notebook. + QSharedPointer loadNodeByPath(const QString &p_path); + public slots: void setCurrentNotebook(ID p_notebookId); diff --git a/src/core/sessionconfig.cpp b/src/core/sessionconfig.cpp index c72a4de9..09a0bf6b 100644 --- a/src/core/sessionconfig.cpp +++ b/src/core/sessionconfig.cpp @@ -9,6 +9,7 @@ #include "configmgr.h" #include "mainconfig.h" +#include "historymgr.h" using namespace vnotex; @@ -87,6 +88,8 @@ void SessionConfig::init() loadNotebooks(sessionJobj); + loadHistory(sessionJobj); + if (MainConfig::isVersionChanged()) { doVersionSpecificOverride(); } @@ -208,6 +211,7 @@ QJsonObject SessionConfig::toJson() const writeByteArray(obj, QStringLiteral("viewarea_session"), m_viewAreaSession); writeByteArray(obj, QStringLiteral("notebook_explorer_session"), m_notebookExplorerSession); obj[QStringLiteral("external_programs")] = saveExternalPrograms(); + obj[QStringLiteral("history")] = saveHistory(); return obj; } @@ -405,3 +409,38 @@ const QVector &SessionConfig::getExternalProgram { return m_externalPrograms; } + +const QVector &SessionConfig::getHistory() const +{ + return m_history; +} + +void SessionConfig::addHistory(const HistoryItem &p_item) +{ + HistoryMgr::insertHistoryItem(m_history, p_item); + update(); +} + +void SessionConfig::clearHistory() +{ + m_history.clear(); + update(); +} + +void SessionConfig::loadHistory(const QJsonObject &p_session) +{ + auto arr = p_session[QStringLiteral("history")].toArray(); + m_history.resize(arr.size()); + for (int i = 0; i < arr.size(); ++i) { + m_history[i].fromJson(arr[i].toObject()); + } +} + +QJsonArray SessionConfig::saveHistory() const +{ + QJsonArray arr; + for (const auto &item : m_history) { + arr.append(item.toJson()); + } + return arr; +} diff --git a/src/core/sessionconfig.h b/src/core/sessionconfig.h index 5ac62956..a57c41c4 100644 --- a/src/core/sessionconfig.h +++ b/src/core/sessionconfig.h @@ -8,6 +8,7 @@ #include #include +#include "historyitem.h" namespace vnotex { @@ -123,6 +124,10 @@ namespace vnotex const QVector &getExternalPrograms() const; + const QVector &getHistory() const; + void addHistory(const HistoryItem &p_item); + void clearHistory(); + private: void loadCore(const QJsonObject &p_session); @@ -142,6 +147,10 @@ namespace vnotex void doVersionSpecificOverride(); + void loadHistory(const QJsonObject &p_session); + + QJsonArray saveHistory() const; + QString m_newNotebookDefaultRootFolderPath; // Use root folder to identify a notebook uniquely. @@ -175,6 +184,8 @@ namespace vnotex QStringList m_quickAccessFiles; QVector m_externalPrograms; + + QVector m_history; }; } // ns vnotex diff --git a/src/data/core/vnotex.json b/src/data/core/vnotex.json index 65a4e2dd..3f23932c 100644 --- a/src/data/core/vnotex.json +++ b/src/data/core/vnotex.json @@ -20,6 +20,7 @@ "SearchDock" : "", "SnippetDock" : "Ctrl+G, S", "LocationListDock" : "Ctrl+G, C", + "HistoryDock" : "", "Search" : "Ctrl+Alt+F", "NavigationMode" : "Ctrl+G, W", "LocateNode" : "Ctrl+G, D", @@ -54,7 +55,8 @@ "MoveOneSplitLeft" : "Ctrl+G, Shift+H", "MoveOneSplitDown" : "Ctrl+G, Shift+J", "MoveOneSplitUp" : "Ctrl+G, Shift+K", - "MoveOneSplitRight" : "Ctrl+G, Shift+L" + "MoveOneSplitRight" : "Ctrl+G, Shift+L", + "OpenLastClosedFile" : "Ctrl+Shift+T" }, "toolbar_icon_size" : 16, "note_management" : { @@ -66,7 +68,9 @@ ] } }, - "recover_last_session_on_start" : true + "recover_last_session_on_start" : true, + "//comment" : "Max count of the history items for each notebook and session config", + "history_max_count" : 100 }, "editor" : { "core": { diff --git a/src/utils/pathutils.cpp b/src/utils/pathutils.cpp index f3beb4d3..d163338f 100644 --- a/src/utils/pathutils.cpp +++ b/src/utils/pathutils.cpp @@ -72,6 +72,15 @@ QString PathUtils::fileName(const QString &p_path) return fi.fileName(); } +QString PathUtils::fileNameCheap(const QString &p_path) +{ + int idx = p_path.lastIndexOf(QRegularExpression("[\\\\/]")); + if (idx == -1) { + return p_path; + } + return p_path.mid(idx + 1); +} + QString PathUtils::normalizePath(const QString &p_path) { auto absPath = QDir::cleanPath(QDir(p_path).absolutePath()); diff --git a/src/utils/pathutils.h b/src/utils/pathutils.h index 8677cae4..93a1794e 100644 --- a/src/utils/pathutils.h +++ b/src/utils/pathutils.h @@ -38,6 +38,8 @@ namespace vnotex // Get file name of @p_path file/directory. static QString fileName(const QString &p_path); + static QString fileNameCheap(const QString &p_path); + static QString absolutePath(const QString &p_path) { return QDir(p_path).absolutePath(); diff --git a/src/utils/widgetutils.cpp b/src/utils/widgetutils.cpp index 9e219ec7..4597b726 100644 --- a/src/utils/widgetutils.cpp +++ b/src/utils/widgetutils.cpp @@ -296,6 +296,10 @@ QShortcut *WidgetUtils::createShortcut(const QString &p_shortcut, } auto shortcut = new QShortcut(kseq, p_widget, nullptr, nullptr, p_context); + if (shortcut->key().isEmpty()) { + delete shortcut; + return nullptr; + } return shortcut; } diff --git a/src/widgets/editors/graphhelper.cpp b/src/widgets/editors/graphhelper.cpp index 9a68870b..a817ff0d 100644 --- a/src/widgets/editors/graphhelper.cpp +++ b/src/widgets/editors/graphhelper.cpp @@ -42,6 +42,7 @@ void GraphHelper::process(quint64 p_id, TimeStamp p_timeStamp, const QString &p_format, const QString &p_text, + QObject *p_owner, const ResultCallback &p_callback) { Task task; @@ -49,6 +50,7 @@ void GraphHelper::process(quint64 p_id, task.m_timeStamp = p_timeStamp; task.m_format = p_format; task.m_text = p_text; + task.m_owner = p_owner; task.m_callback = p_callback; m_tasks.enqueue(task); @@ -126,10 +128,10 @@ void GraphHelper::finishOneTask(QProcess *p_process, int p_exitCode, QProcess::E QString data; if (task.m_format == QStringLiteral("svg")) { data = QString::fromLocal8Bit(outBa); - task.m_callback(id, timeStamp, task.m_format, data); + callbackOneTask(task, id, timeStamp, task.m_format, data); } else { data = QString::fromLocal8Bit(outBa.toBase64()); - task.m_callback(id, timeStamp, task.m_format, data); + callbackOneTask(task, id, timeStamp, task.m_format, data); } CacheItem item; @@ -152,7 +154,7 @@ void GraphHelper::finishOneTask(QProcess *p_process, int p_exitCode, QProcess::E } if (failed) { - task.m_callback(id, task.m_timeStamp, task.m_format, QString()); + callbackOneTask(task, id, task.m_timeStamp, task.m_format, QString()); } p_process->deleteLater(); @@ -169,7 +171,7 @@ void GraphHelper::finishOneTask(const QString &p_data) qDebug() << "Graph task" << task.m_id << task.m_timeStamp << "finished by cache" << p_data.size(); - task.m_callback(task.m_id, task.m_timeStamp, task.m_format, p_data); + callbackOneTask(task, task.m_id, task.m_timeStamp, task.m_format, p_data); m_taskOngoing = false; processOneTask(); @@ -199,3 +201,10 @@ void GraphHelper::checkValidProgram() } } } + +void GraphHelper::callbackOneTask(const Task &p_task, quint64 p_id, TimeStamp p_timeStamp, const QString &p_format, const QString &p_data) const +{ + if (p_task.m_owner) { + p_task.m_callback(p_id, p_timeStamp, p_format, p_data); + } +} diff --git a/src/widgets/editors/graphhelper.h b/src/widgets/editors/graphhelper.h index 6ebf7b06..f957f858 100644 --- a/src/widgets/editors/graphhelper.h +++ b/src/widgets/editors/graphhelper.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -23,6 +24,7 @@ namespace vnotex TimeStamp p_timeStamp, const QString &p_format, const QString &p_text, + QObject *p_owner, const ResultCallback &p_callback); protected: @@ -55,6 +57,8 @@ namespace vnotex QString m_text; + QPointer m_owner; + ResultCallback m_callback; }; @@ -76,6 +80,8 @@ namespace vnotex void finishOneTask(const QString &p_data); + void callbackOneTask(const Task &p_task, quint64 p_id, TimeStamp p_timeStamp, const QString &p_format, const QString &p_data) const; + QQueue m_tasks; bool m_taskOngoing = false; diff --git a/src/widgets/editors/markdownvieweradapter.cpp b/src/widgets/editors/markdownvieweradapter.cpp index 44a75b0f..0fc5cf2c 100644 --- a/src/widgets/editors/markdownvieweradapter.cpp +++ b/src/widgets/editors/markdownvieweradapter.cpp @@ -390,6 +390,7 @@ void MarkdownViewerAdapter::renderGraph(quint64 p_id, p_index, p_format, p_text, + this, [this](quint64 id, TimeStamp timeStamp, const QString &format, const QString &data) { emit graphRenderDataReady(id, timeStamp, format, data); }); @@ -398,6 +399,7 @@ void MarkdownViewerAdapter::renderGraph(quint64 p_id, p_index, p_format, p_text, + this, [this](quint64 id, TimeStamp timeStamp, const QString &format, const QString &data) { emit graphRenderDataReady(id, timeStamp, format, data); }); diff --git a/src/widgets/editors/previewhelper.cpp b/src/widgets/editors/previewhelper.cpp index 2b4a3074..9428724e 100644 --- a/src/widgets/editors/previewhelper.cpp +++ b/src/widgets/editors/previewhelper.cpp @@ -262,6 +262,7 @@ void PreviewHelper::inplacePreviewCodeBlock(int p_blockPreviewIdx) m_codeBlockTimeStamp, QStringLiteral("svg"), vte::TextUtils::removeCodeBlockFence(blockData.m_text), + this, [this](quint64 id, TimeStamp timeStamp, const QString &format, const QString &data) { handleLocalData(id, timeStamp, format, data, true); }); @@ -274,6 +275,7 @@ void PreviewHelper::inplacePreviewCodeBlock(int p_blockPreviewIdx) m_codeBlockTimeStamp, QStringLiteral("svg"), vte::TextUtils::removeCodeBlockFence(blockData.m_text), + this, [this](quint64 id, TimeStamp timeStamp, const QString &format, const QString &data) { handleLocalData(id, timeStamp, format, data, false); }); diff --git a/src/widgets/historypanel.cpp b/src/widgets/historypanel.cpp new file mode 100644 index 00000000..c3918093 --- /dev/null +++ b/src/widgets/historypanel.cpp @@ -0,0 +1,227 @@ +#include "historypanel.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "titlebar.h" +#include "listwidget.h" +#include "mainwindow.h" +#include "messageboxhelper.h" + +using namespace vnotex; + +HistoryPanel::HistoryPanel(QWidget *p_parent) + : QFrame(p_parent) +{ + setupUI(); + + updateSeparators(); +} + +void HistoryPanel::setupUI() +{ + auto mainLayout = new QVBoxLayout(this); + WidgetUtils::setContentsMargins(mainLayout); + + { + setupTitleBar(QString(), this); + mainLayout->addWidget(m_titleBar); + } + + m_historyList = new ListWidget(this); + m_historyList->setContextMenuPolicy(Qt::CustomContextMenu); + m_historyList->setSelectionMode(QAbstractItemView::ExtendedSelection); + connect(m_historyList, &QListWidget::customContextMenuRequested, + this, &HistoryPanel::handleContextMenuRequested); + connect(m_historyList, &QListWidget::itemActivated, + this, &HistoryPanel::openItem); + mainLayout->addWidget(m_historyList); + + setFocusProxy(m_historyList); +} + +void HistoryPanel::setupTitleBar(const QString &p_title, QWidget *p_parent) +{ + m_titleBar = new TitleBar(p_title, false, TitleBar::Action::None, p_parent); + m_titleBar->setActionButtonsAlwaysShown(true); + + { + auto clearBtn = m_titleBar->addActionButton(QStringLiteral("clear.svg"), tr("Clear")); + connect(clearBtn, &QToolButton::triggered, + this, &HistoryPanel::clearHistory); + } +} + +void HistoryPanel::handleContextMenuRequested(const QPoint &p_pos) +{ + auto item = m_historyList->itemAt(p_pos); + if (!isValidItem(item)) { + return; + } + + QMenu menu(this); + + const int selectedCount = m_historyList->selectedItems().size(); + + menu.addAction(tr("&Open"), + this, + [this]() { + const auto selectedItems = m_historyList->selectedItems(); + for (const auto &selectedItem : selectedItems) { + openItem(selectedItem); + } + }); + + if (selectedCount == 1) { + menu.addAction(tr("&Locate Node"), + &menu, + [this]() { + auto item = m_historyList->currentItem(); + if (!isValidItem(item)) { + return; + } + + auto node = VNoteX::getInst().getNotebookMgr().loadNodeByPath(getPath(item)); + if (node) { + emit VNoteX::getInst().locateNodeRequested(node.data()); + } + }); + } + + menu.exec(m_historyList->mapToGlobal(p_pos)); +} + +void HistoryPanel::openItem(const QListWidgetItem *p_item) +{ + if (!isValidItem(p_item)) { + return; + } + + emit VNoteX::getInst().openFileRequested(getPath(p_item), QSharedPointer::create()); +} + +void HistoryPanel::clearHistory() +{ + int ret = MessageBoxHelper::questionOkCancel(MessageBoxHelper::Warning, + tr("Clear all the history?"), + QString(), + QString(), + VNoteX::getInst().getMainWindow()); + if (ret != QMessageBox::Ok) { + return; + } + + HistoryMgr::getInst().clear(); +} + +bool HistoryPanel::isValidItem(const QListWidgetItem *p_item) const +{ + return p_item && !ListWidget::isSeparatorItem(p_item); +} + +void HistoryPanel::updateHistoryList() +{ + m_pendingUpdate = false; + + m_historyList->clear(); + + const auto &history = HistoryMgr::getInst().getHistory(); + int itemIdx = history.size() - 1; + for (int sepIdx = 0; sepIdx < m_separators.size() && itemIdx >= 0; ++sepIdx) { + const auto &separator = m_separators[sepIdx]; + if (separator.m_dateUtc <= history[itemIdx]->m_item.m_lastAccessedTimeUtc) { + // Insert this separator. + auto sepItem = ListWidget::createSeparatorItem(separator.m_text); + m_historyList->addItem(sepItem); + + addItem(*history[itemIdx]); + --itemIdx; + + // Insert all qualified items. + for (; + itemIdx >= 0 && separator.m_dateUtc <= history[itemIdx]->m_item.m_lastAccessedTimeUtc; + --itemIdx) { + addItem(*history[itemIdx]); + } + } + } + + if (itemIdx >= 0) { + // Older. + auto sepItem = ListWidget::createSeparatorItem(tr(">>> Older")); + m_historyList->addItem(sepItem); + + for (; itemIdx >= 0; --itemIdx) { + addItem(*history[itemIdx]); + } + } +} + +void HistoryPanel::showEvent(QShowEvent *p_event) +{ + QFrame::showEvent(p_event); + + if (!m_initialized) { + m_initialized = true; + connect(&HistoryMgr::getInst(), &HistoryMgr::historyUpdated, + this, &HistoryPanel::updateHistoryListIfProper); + } + + if (m_pendingUpdate) { + updateHistoryList(); + } +} + +void HistoryPanel::updateSeparators() +{ + m_separators.resize(3); + + // Mid-night of today. + auto curDateTime = QDateTime::currentDateTime(); + curDateTime.setTime(QTime()); + + m_separators[0].m_text = tr(">>> Today"); + m_separators[0].m_dateUtc = curDateTime.toUTC(); + m_separators[1].m_text = tr(">>> Yesterday"); + m_separators[1].m_dateUtc = curDateTime.addDays(-1).toUTC(); + m_separators[2].m_text = tr(">>> Last 7 Days"); + m_separators[2].m_dateUtc = curDateTime.addDays(-7).toUTC(); +} + +void HistoryPanel::updateHistoryListIfProper() +{ + if (isVisible()) { + updateHistoryList(); + } else { + m_pendingUpdate = true; + } +} + +void HistoryPanel::addItem(const HistoryItemFull &p_hisItem) +{ + auto item = new QListWidgetItem(m_historyList); + + item->setText(PathUtils::fileNameCheap(p_hisItem.m_item.m_path)); + item->setData(Qt::UserRole, p_hisItem.m_item.m_path); + if (p_hisItem.m_notebookName.isEmpty()) { + item->setToolTip(p_hisItem.m_item.m_path); + } else { + item->setToolTip(tr("[%1] %2").arg(p_hisItem.m_notebookName, p_hisItem.m_item.m_path)); + } +} + +QString HistoryPanel::getPath(const QListWidgetItem *p_item) const +{ + return p_item->data(Qt::UserRole).toString(); +} diff --git a/src/widgets/historypanel.h b/src/widgets/historypanel.h new file mode 100644 index 00000000..fdb07127 --- /dev/null +++ b/src/widgets/historypanel.h @@ -0,0 +1,67 @@ +#ifndef HISTORYPANEL_H +#define HISTORYPANEL_H + +#include +#include + +class QListWidget; +class QListWidgetItem; + +namespace vnotex +{ + class TitleBar; + struct HistoryItemFull; + + class HistoryPanel : public QFrame + { + Q_OBJECT + public: + explicit HistoryPanel(QWidget *p_parent = nullptr); + + protected: + void showEvent(QShowEvent *p_event) Q_DECL_OVERRIDE; + + private slots: + void handleContextMenuRequested(const QPoint &p_pos); + + void openItem(const QListWidgetItem *p_item); + + void clearHistory(); + + private: + struct SeparatorData + { + QString m_text; + + QDateTime m_dateUtc; + }; + + void setupUI(); + + void setupTitleBar(const QString &p_title, QWidget *p_parent = nullptr); + + void updateHistoryList(); + + void updateHistoryListIfProper(); + + void updateSeparators(); + + void addItem(const HistoryItemFull &p_hisItem); + + QString getPath(const QListWidgetItem *p_item) const; + + bool isValidItem(const QListWidgetItem *p_item) const; + + TitleBar *m_titleBar = nullptr; + + QListWidget *m_historyList = nullptr; + + bool m_initialized = false; + + bool m_pendingUpdate = true; + + QVector m_separators; + }; +} + +#endif // HISTORYPANEL_H diff --git a/src/widgets/listwidget.cpp b/src/widgets/listwidget.cpp index e4f7a302..4f768006 100644 --- a/src/widgets/listwidget.cpp +++ b/src/widgets/listwidget.cpp @@ -41,3 +41,15 @@ QVector ListWidget::getVisibleItems(const QListWidget *p_widg return items; } + +QListWidgetItem *ListWidget::createSeparatorItem(const QString &p_text) +{ + QListWidgetItem *item = new QListWidgetItem(p_text, nullptr, c_separatorType); + item->setFlags(Qt::NoItemFlags); + return item; +} + +bool ListWidget::isSeparatorItem(const QListWidgetItem *p_item) +{ + return p_item->type() == c_separatorType; +} diff --git a/src/widgets/listwidget.h b/src/widgets/listwidget.h index a153cee2..0a143794 100644 --- a/src/widgets/listwidget.h +++ b/src/widgets/listwidget.h @@ -14,8 +14,15 @@ namespace vnotex static QVector getVisibleItems(const QListWidget *p_widget); + static QListWidgetItem *createSeparatorItem(const QString &p_text); + + static bool isSeparatorItem(const QListWidgetItem *p_item); + protected: void keyPressEvent(QKeyEvent *p_event) Q_DECL_OVERRIDE; + + private: + static const int c_separatorType = 2000; }; } diff --git a/src/widgets/mainwindow.cpp b/src/widgets/mainwindow.cpp index 81ce263b..d3b7ce84 100644 --- a/src/widgets/mainwindow.cpp +++ b/src/widgets/mainwindow.cpp @@ -43,6 +43,7 @@ #include "locationlist.h" #include "searchpanel.h" #include "snippetpanel.h" +#include "historypanel.h" #include #include "searchinfoprovider.h" #include @@ -217,6 +218,8 @@ void MainWindow::setupDocks() setupOutlineDock(); + setupHistoryDock(); + setupSearchDock(); setupSnippetDock(); @@ -225,6 +228,7 @@ void MainWindow::setupDocks() tabifyDockWidget(m_docks[i - 1], m_docks[i]); } + // Following are non-tabfieid docks. setupLocationListDock(); for (auto dock : m_docks) { @@ -338,6 +342,26 @@ void MainWindow::setupSnippetPanel() }); } +void MainWindow::setupHistoryDock() +{ + auto dock = new QDockWidget(tr("History"), this); + m_docks.push_back(dock); + + dock->setObjectName(QStringLiteral("HistoryDock.vnotex")); + dock->setAllowedAreas(Qt::AllDockWidgetAreas); + + setupHistoryPanel(); + dock->setWidget(m_historyPanel); + dock->setFocusProxy(m_historyPanel); + addDockWidget(Qt::LeftDockWidgetArea, dock); +} + +void MainWindow::setupHistoryPanel() +{ + m_historyPanel = new HistoryPanel(this); + m_historyPanel->setObjectName("HistoryPanel.vnotex"); +} + void MainWindow::setupLocationListDock() { auto dock = new QDockWidget(tr("Location List"), this); @@ -381,7 +405,10 @@ void MainWindow::setupNotebookExplorer(QWidget *p_parent) connect(&VNoteX::getInst(), &VNoteX::importLegacyNotebookRequested, m_notebookExplorer, &NotebookExplorer::importLegacyNotebook); connect(&VNoteX::getInst(), &VNoteX::locateNodeRequested, - m_notebookExplorer, &NotebookExplorer::locateNode); + this, [this](Node *p_node) { + activateDock(m_docks[DockIndex::NavigationDock]); + m_notebookExplorer->locateNode(p_node); + }); auto notebookMgr = &VNoteX::getInst().getNotebookMgr(); connect(notebookMgr, &NotebookMgr::notebooksUpdated, @@ -646,6 +673,9 @@ void MainWindow::setupShortcuts() setupDockActivateShortcut(m_docks[DockIndex::OutlineDock], coreConfig.getShortcut(CoreConfig::Shortcut::OutlineDock)); + setupDockActivateShortcut(m_docks[DockIndex::HistoryDock], + coreConfig.getShortcut(CoreConfig::Shortcut::HistoryDock)); + setupDockActivateShortcut(m_docks[DockIndex::SearchDock], coreConfig.getShortcut(CoreConfig::Shortcut::SearchDock)); // Extra shortcut for SearchDock. diff --git a/src/widgets/mainwindow.h b/src/widgets/mainwindow.h index 3256cc0a..ac68a1ac 100644 --- a/src/widgets/mainwindow.h +++ b/src/widgets/mainwindow.h @@ -23,6 +23,7 @@ namespace vnotex class LocationList; class SearchPanel; class SnippetPanel; + class HistoryPanel; enum { RESTART_EXIT_CODE = 1000 }; @@ -96,6 +97,7 @@ namespace vnotex { NavigationDock = 0, OutlineDock, + HistoryDock, SearchDock, SnippetDock, LocationListDock @@ -123,6 +125,10 @@ namespace vnotex void setupSnippetPanel(); + void setupHistoryDock(); + + void setupHistoryPanel(); + void setupNotebookExplorer(QWidget *p_parent = nullptr); void setupDocks(); @@ -176,6 +182,8 @@ namespace vnotex SnippetPanel *m_snippetPanel = nullptr; + HistoryPanel *m_historyPanel = nullptr; + QVector m_docks; bool m_layoutReset = false; diff --git a/src/widgets/snippetpanel.cpp b/src/widgets/snippetpanel.cpp index 9c75272a..880c4c60 100644 --- a/src/widgets/snippetpanel.cpp +++ b/src/widgets/snippetpanel.cpp @@ -135,15 +135,15 @@ void SnippetPanel::showEvent(QShowEvent *p_event) } } -void SnippetPanel::handleContextMenuRequested(QPoint p_pos) +void SnippetPanel::handleContextMenuRequested(const QPoint &p_pos) { - QMenu menu(this); - auto item = m_snippetList->itemAt(p_pos); if (!item) { return; } + QMenu menu(this); + const int selectedCount = m_snippetList->selectedItems().size(); if (selectedCount == 1) { menu.addAction(tr("&Apply"), diff --git a/src/widgets/snippetpanel.h b/src/widgets/snippetpanel.h index 43d038e5..c0487c2d 100644 --- a/src/widgets/snippetpanel.h +++ b/src/widgets/snippetpanel.h @@ -25,7 +25,7 @@ namespace vnotex private slots: void newSnippet(); - void handleContextMenuRequested(QPoint p_pos); + void handleContextMenuRequested(const QPoint &p_pos); void removeSelectedSnippets(); diff --git a/src/widgets/viewarea.cpp b/src/widgets/viewarea.cpp index c23417b1..6f12bc76 100644 --- a/src/widgets/viewarea.cpp +++ b/src/widgets/viewarea.cpp @@ -32,6 +32,7 @@ #include #include "editors/plantumlhelper.h" #include "editors/graphvizhelper.h" +#include using namespace vnotex; @@ -464,10 +465,23 @@ bool ViewArea::closeViewWindow(ViewWindow *p_win, bool p_force, bool p_removeSpl // Make it current ViewWindow. setCurrentViewWindow(p_win); + // Get info before close. + const auto session = p_win->saveSession(); + Notebook *notebook = nullptr; + if (p_win->getBuffer()) { + auto node = p_win->getBuffer()->getNode(); + if (node) { + notebook = node->getNotebook(); + } + } + if (!p_win->aboutToClose(p_force)) { return false; } + // Update history. + updateHistory(session, notebook); + // Remove the status widget. if (m_currentStatusWidget && p_win == getCurrentViewWindow()) { Q_ASSERT(m_currentStatusWidget == p_win->statusWidget()); @@ -890,6 +904,26 @@ void ViewArea::setupShortcuts() }); } } + + // OpenLastClosedFile. + { + auto shortcut = WidgetUtils::createShortcut(coreConfig.getShortcut(CoreConfig::OpenLastClosedFile), this); + if (shortcut) { + connect(shortcut, &QShortcut::activated, + this, [this]() { + auto file = HistoryMgr::getInst().popLastClosedFile(); + if (file.m_path.isEmpty()) { + VNoteX::getInst().showStatusMessageShort(tr("No recently closed file")); + return; + } + auto paras = QSharedPointer::create(); + paras->m_lineNumber = file.m_lineNumber; + paras->m_mode = file.m_mode; + paras->m_readOnly = file.m_readOnly; + emit VNoteX::getInst().openFileRequested(file.m_path, paras); + }); + } + } } bool ViewArea::close(Node *p_node, bool p_force) @@ -1441,3 +1475,12 @@ ViewArea::SplitType ViewArea::splitTypeOfDirection(Direction p_direction) return SplitType::Vertical; } } + +void ViewArea::updateHistory(const ViewWindowSession &p_session, Notebook *p_notebook) const +{ + HistoryMgr::getInst().add(p_session.m_bufferPath, + p_session.m_lineNumber, + p_session.m_viewWindowMode, + p_session.m_readOnly, + p_notebook); +} diff --git a/src/widgets/viewarea.h b/src/widgets/viewarea.h index 9a96d632..2a2d7fbc 100644 --- a/src/widgets/viewarea.h +++ b/src/widgets/viewarea.h @@ -228,6 +228,8 @@ namespace vnotex void flashViewSplit(ViewSplit *p_split); + void updateHistory(const ViewWindowSession &p_session, Notebook *p_notebook) const; + static SplitType splitTypeOfDirection(Direction p_direction); QLayout *m_mainLayout = nullptr; diff --git a/src/widgets/widgets.pri b/src/widgets/widgets.pri index 3d1eb410..73443900 100644 --- a/src/widgets/widgets.pri +++ b/src/widgets/widgets.pri @@ -49,6 +49,7 @@ SOURCES += \ $$PWD/findandreplacewidget.cpp \ $$PWD/floatingwidget.cpp \ $$PWD/fullscreentoggleaction.cpp \ + $$PWD/historypanel.cpp \ $$PWD/lineedit.cpp \ $$PWD/lineeditdelegate.cpp \ $$PWD/lineeditwithsnippet.cpp \ @@ -155,6 +156,7 @@ HEADERS += \ $$PWD/findandreplacewidget.h \ $$PWD/floatingwidget.h \ $$PWD/fullscreentoggleaction.h \ + $$PWD/historypanel.h \ $$PWD/lineedit.h \ $$PWD/lineeditdelegate.h \ $$PWD/lineeditwithsnippet.h \