History: support history

This commit is contained in:
Le Tan 2021-07-23 20:24:42 +08:00
parent ef69ee435f
commit cf988e6fa6
43 changed files with 953 additions and 37 deletions

@ -1 +1 @@
Subproject commit a15d859141506142a11cbf843a2d93124ea65bfa
Subproject commit 47902164b0e11f5debfb40deb649d451d650660f

View File

@ -105,7 +105,7 @@ void BufferMgr::open(const QString &p_filePath, const QSharedPointer<FileOpenPar
}
// Check if it is an internal node or not.
auto node = loadNodeByPath(p_filePath);
auto node = VNoteX::getInst().getNotebookMgr().loadNodeByPath(p_filePath);
if (node) {
if (node->hasContent()) {
open(node.data(), p_paras);
@ -187,16 +187,3 @@ void BufferMgr::addBuffer(Buffer *p_buffer)
p_buffer->deleteLater();
});
}
QSharedPointer<Node> BufferMgr::loadNodeByPath(const QString &p_path)
{
const auto &notebooks = VNoteX::getInst().getNotebookMgr().getNotebooks();
for (const auto &nb : notebooks) {
auto node = nb->loadNodeByPath(p_path);
if (node) {
return node;
}
}
return nullptr;
}

View File

@ -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<Node> loadNodeByPath(const QString &p_path);
QSharedPointer<NameBasedServer<IBufferFactory>> m_bufferServer;
// Managed by QObject.

View File

@ -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 \

View File

@ -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;
}

View File

@ -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

28
src/core/historyitem.cpp Normal file
View File

@ -0,0 +1,28 @@
#include "historyitem.h"
#include <utils/utils.h>
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());
}

32
src/core/historyitem.h Normal file
View File

@ -0,0 +1,32 @@
#ifndef HISTORYITEM_H
#define HISTORYITEM_H
#include <QString>
#include <QJsonObject>
#include <QDateTime>
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

182
src/core/historymgr.cpp Normal file
View File

@ -0,0 +1,182 @@
#include "historymgr.h"
#include <QDebug>
#include "configmgr.h"
#include "sessionconfig.h"
#include "coreconfig.h"
#include "vnotex.h"
#include "notebookmgr.h"
#include <notebook/notebook.h>
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<HistoryItemFull> &p_a, const QSharedPointer<HistoryItemFull> &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<HistoryItemFull>::create();
fullItem->m_item = item;
m_history.push_back(fullItem);
}
}
// Load from notebooks.
{
const auto &notebooks = VNoteX::getInst().getNotebookMgr().getNotebooks();
for (const auto &nb : notebooks) {
const auto &history = nb->getHistory();
for (const auto &item : history) {
auto fullItem = QSharedPointer<HistoryItemFull>::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<QSharedPointer<HistoryItemFull>> &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<HistoryItemFull>::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<HistoryItem> &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 &notebooks = 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;
}

81
src/core/historymgr.h Normal file
View File

@ -0,0 +1,81 @@
#ifndef HISTORYMGR_H
#define HISTORYMGR_H
#include <QObject>
#include <QVector>
#include <QSharedPointer>
#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<QSharedPointer<HistoryItemFull>> &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<HistoryItem> &p_history, const HistoryItem &p_item);
signals:
void historyUpdated();
private:
HistoryMgr();
void loadHistory();
// Sorted by last accessed time ascendingly.
QVector<QSharedPointer<HistoryItemFull>> m_history;
QVector<LastClosedFile> m_lastClosedFiles;
static int s_maxHistoryCount;
};
}
#endif // HISTORYMGR_H

View File

@ -57,6 +57,12 @@ namespace vnotex
return m_revision;
}
void update()
{
++m_revision;
writeToSettings();
}
protected:
ConfigMgr *getMgr() const
{

View File

@ -5,16 +5,18 @@
#include <notebookconfigmgr/bundlenotebookconfigmgr.h>
#include <notebookconfigmgr/notebookconfig.h>
#include <utils/fileutils.h>
#include <core/historymgr.h>
#include <notebookbackend/inotebookbackend.h>
using namespace vnotex;
BundleNotebook::BundleNotebook(const NotebookParameters &p_paras,
const QSharedPointer<NotebookConfig> &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<HistoryItem> &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();
}

View File

@ -7,12 +7,14 @@
namespace vnotex
{
class BundleNotebookConfigMgr;
class NotebookConfig;
class BundleNotebook : public Notebook
{
Q_OBJECT
public:
BundleNotebook(const NotebookParameters &p_paras,
const QSharedPointer<NotebookConfig> &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<HistoryItem> &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<HistoryItem> m_history;
};
} // ns vnotex

View File

@ -55,7 +55,8 @@ QSharedPointer<Notebook> BundleNotebookFactory::newNotebook(const NotebookParame
p_paras.m_notebookConfigMgr->createEmptySkeleton(p_paras);
auto notebook = QSharedPointer<BundleNotebook>::create(p_paras);
auto nbConfig = BundleNotebookConfigMgr::readNotebookConfig(p_paras.m_notebookBackend);
auto notebook = QSharedPointer<BundleNotebook>::create(p_paras, nbConfig);
return notebook;
}
@ -78,7 +79,7 @@ QSharedPointer<Notebook> BundleNotebookFactory::createNotebook(const NotebookMgr
nbConfig->m_versionController,
nbConfig->m_notebookConfigMgr);
checkParameters(*paras);
auto notebook = QSharedPointer<BundleNotebook>::create(*paras);
auto notebook = QSharedPointer<BundleNotebook>::create(*paras, nbConfig);
return notebook;
}

View File

@ -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;

View File

@ -6,8 +6,9 @@
#include <QSharedPointer>
#include "notebookparameters.h"
#include "../global.h"
#include <core/global.h>
#include "node.h"
#include <core/historyitem.h>
namespace vnotex
{
@ -130,6 +131,10 @@ namespace vnotex
void reloadNodes();
virtual const QVector<HistoryItem> &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;

View File

@ -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);
}

View File

@ -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;

View File

@ -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> NotebookConfig::fromNotebook(const QString &p_version,
@ -106,6 +110,25 @@ QSharedPointer<NotebookConfig> 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());
}
}

View File

@ -4,9 +4,12 @@
#include <QJsonObject>
#include <QSharedPointer>
#include <QDateTime>
#include <QVector>
#include <QJsonArray>
#include "bundlenotebookconfigmgr.h"
#include "global.h"
#include <core/global.h>
#include <core/historyitem.h>
namespace vnotex
{
@ -45,6 +48,13 @@ namespace vnotex
ID m_nextNodeId = BundleNotebookConfigMgr::RootNodeId + 1;
QVector<HistoryItem> m_history;
private:
QJsonArray saveHistory() const;
void loadHistory(const QJsonObject &p_jobj);
static const QString c_version;
static const QString c_name;

View File

@ -374,3 +374,15 @@ void NotebookMgr::addNotebook(const QSharedPointer<Notebook> &p_notebook)
emit notebookUpdated(notebook);
});
}
QSharedPointer<Node> NotebookMgr::loadNodeByPath(const QString &p_path)
{
for (const auto &nb : m_notebooks) {
auto node = nb->loadNodeByPath(p_path);
if (node) {
return node;
}
}
return nullptr;
}

View File

@ -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<Node> loadNodeByPath(const QString &p_path);
public slots:
void setCurrentNotebook(ID p_notebookId);

View File

@ -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::ExternalProgram> &SessionConfig::getExternalProgram
{
return m_externalPrograms;
}
const QVector<HistoryItem> &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;
}

View File

@ -8,6 +8,7 @@
#include <export/exportdata.h>
#include <search/searchdata.h>
#include "historyitem.h"
namespace vnotex
{
@ -123,6 +124,10 @@ namespace vnotex
const QVector<ExternalProgram> &getExternalPrograms() const;
const QVector<HistoryItem> &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<ExternalProgram> m_externalPrograms;
QVector<HistoryItem> m_history;
};
} // ns vnotex

View File

@ -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": {

View File

@ -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());

View File

@ -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();

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -5,6 +5,7 @@
#include <QStringList>
#include <QPair>
#include <QQueue>
#include <QPointer>
#include <core/noncopyable.h>
#include <core/global.h>
@ -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<QObject> 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<Task> m_tasks;
bool m_taskOngoing = false;

View File

@ -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);
});

View File

@ -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);
});

View File

@ -0,0 +1,227 @@
#include "historypanel.h"
#include <QVBoxLayout>
#include <QToolButton>
#include <QListWidgetItem>
#include <utils/widgetutils.h>
#include <utils/pathutils.h>
#include <core/vnotex.h>
#include <core/exception.h>
#include <core/configmgr.h>
#include <core/historymgr.h>
#include <core/notebookmgr.h>
#include <core/fileopenparameters.h>
#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<FileOpenParameters>::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();
}

View File

@ -0,0 +1,67 @@
#ifndef HISTORYPANEL_H
#define HISTORYPANEL_H
#include <QFrame>
#include <QDateTime>
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<SeparatorData> m_separators;
};
}
#endif // HISTORYPANEL_H

View File

@ -41,3 +41,15 @@ QVector<QListWidgetItem *> 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;
}

View File

@ -14,8 +14,15 @@ namespace vnotex
static QVector<QListWidgetItem *> 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;
};
}

View File

@ -43,6 +43,7 @@
#include "locationlist.h"
#include "searchpanel.h"
#include "snippetpanel.h"
#include "historypanel.h"
#include <notebook/notebook.h>
#include "searchinfoprovider.h"
#include <vtextedit/spellchecker.h>
@ -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.

View File

@ -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<QDockWidget *> m_docks;
bool m_layoutReset = false;

View File

@ -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"),

View File

@ -25,7 +25,7 @@ namespace vnotex
private slots:
void newSnippet();
void handleContextMenuRequested(QPoint p_pos);
void handleContextMenuRequested(const QPoint &p_pos);
void removeSelectedSnippets();

View File

@ -32,6 +32,7 @@
#include <notebook/notebook.h>
#include "editors/plantumlhelper.h"
#include "editors/graphvizhelper.h"
#include <core/historymgr.h>
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<FileOpenParameters>::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);
}

View File

@ -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;

View File

@ -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 \