support tags
@ -526,6 +526,11 @@ bool Buffer::isAttachment(const QString &p_path) const
|
||||
return PathUtils::pathContains(getAttachmentFolderPath(), p_path);
|
||||
}
|
||||
|
||||
bool Buffer::isTagSupported() const
|
||||
{
|
||||
return m_provider->isTagSupported();
|
||||
}
|
||||
|
||||
Buffer::ProviderType Buffer::getProviderType() const
|
||||
{
|
||||
return m_provider->getType();
|
||||
|
@ -165,6 +165,8 @@ namespace vnotex
|
||||
// Judge whether file @p_path is attachment.
|
||||
bool isAttachment(const QString &p_path) const;
|
||||
|
||||
bool isTagSupported() const;
|
||||
|
||||
ProviderType getProviderType() const;
|
||||
|
||||
bool checkFileExistsOnDisk();
|
||||
|
@ -68,6 +68,8 @@ namespace vnotex
|
||||
|
||||
virtual bool isAttachmentSupported() const = 0;
|
||||
|
||||
virtual bool isTagSupported() const = 0;
|
||||
|
||||
virtual bool checkFileExistsOnDisk() const;
|
||||
|
||||
virtual bool checkFileChangedOutside() const;
|
||||
|
@ -169,6 +169,11 @@ bool FileBufferProvider::isAttachmentSupported() const
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FileBufferProvider::isTagSupported() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Node *FileBufferProvider::getNode() const
|
||||
{
|
||||
return c_nodeAttachedTo;
|
||||
|
@ -63,6 +63,8 @@ namespace vnotex
|
||||
|
||||
bool isAttachmentSupported() const Q_DECL_OVERRIDE;
|
||||
|
||||
bool isTagSupported() const Q_DECL_OVERRIDE;
|
||||
|
||||
bool isReadOnly() const Q_DECL_OVERRIDE;
|
||||
|
||||
QSharedPointer<File> getFile() const Q_DECL_OVERRIDE;
|
||||
|
@ -3,6 +3,7 @@
|
||||
#include <QFileInfo>
|
||||
|
||||
#include <notebook/node.h>
|
||||
#include <notebook/notebook.h>
|
||||
#include <utils/pathutils.h>
|
||||
#include <core/file.h>
|
||||
|
||||
@ -148,6 +149,11 @@ bool NodeBufferProvider::isAttachmentSupported() const
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NodeBufferProvider::isTagSupported() const
|
||||
{
|
||||
return m_node->getNotebook()->tag() != nullptr;
|
||||
}
|
||||
|
||||
Node *NodeBufferProvider::getNode() const
|
||||
{
|
||||
return m_node.data();
|
||||
|
@ -65,6 +65,8 @@ namespace vnotex
|
||||
|
||||
bool isAttachmentSupported() const Q_DECL_OVERRIDE;
|
||||
|
||||
bool isTagSupported() const Q_DECL_OVERRIDE;
|
||||
|
||||
bool isReadOnly() const Q_DECL_OVERRIDE;
|
||||
|
||||
QSharedPointer<File> getFile() const Q_DECL_OVERRIDE;
|
||||
|
@ -29,6 +29,7 @@ namespace vnotex
|
||||
SnippetDock,
|
||||
LocationListDock,
|
||||
HistoryDock,
|
||||
TagDock,
|
||||
Search,
|
||||
NavigationMode,
|
||||
LocateNode,
|
||||
|
@ -58,6 +58,7 @@ namespace vnotex
|
||||
FindNext,
|
||||
FindPrevious,
|
||||
ApplySnippet,
|
||||
Tag,
|
||||
MaxShortcut
|
||||
};
|
||||
Q_ENUM(Shortcut)
|
||||
|
@ -38,6 +38,9 @@ namespace vnotex
|
||||
|
||||
// If not empty, use this token to do a search text highlight.
|
||||
QSharedPointer<SearchToken> m_searchToken;
|
||||
|
||||
// Whether should save this file into session.
|
||||
bool m_sessionEnabled = true;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "vnotex.h"
|
||||
#include "notebookmgr.h"
|
||||
#include <notebook/notebook.h>
|
||||
#include <notebook/historyi.h>
|
||||
#include <notebookbackend/inotebookbackend.h>
|
||||
#include "exception.h"
|
||||
|
||||
@ -57,7 +58,11 @@ void HistoryMgr::loadHistory()
|
||||
if (m_perNotebookHistoryEnabled) {
|
||||
const auto ¬ebooks = VNoteX::getInst().getNotebookMgr().getNotebooks();
|
||||
for (const auto &nb : notebooks) {
|
||||
const auto &history = nb->getHistory();
|
||||
auto historyI = nb->history();
|
||||
if (!historyI) {
|
||||
continue;
|
||||
}
|
||||
const auto &history = historyI->getHistory();
|
||||
const auto &backend = nb->getBackend();
|
||||
for (const auto &item : history) {
|
||||
auto fullItem = QSharedPointer<HistoryItemFull>::create();
|
||||
@ -102,8 +107,8 @@ void HistoryMgr::add(const QString &p_path,
|
||||
|
||||
HistoryItem item(p_path, p_lineNumber, QDateTime::currentDateTimeUtc());
|
||||
|
||||
if (p_notebook && m_perNotebookHistoryEnabled) {
|
||||
p_notebook->addHistory(item);
|
||||
if (p_notebook && m_perNotebookHistoryEnabled && p_notebook->history()) {
|
||||
p_notebook->history()->addHistory(item);
|
||||
} else {
|
||||
auto &sessionConfig = ConfigMgr::getInst().getSessionConfig();
|
||||
sessionConfig.addHistory(item);
|
||||
@ -176,7 +181,9 @@ void HistoryMgr::clear()
|
||||
if (m_perNotebookHistoryEnabled) {
|
||||
const auto ¬ebooks = VNoteX::getInst().getNotebookMgr().getNotebooks();
|
||||
for (const auto &nb : notebooks) {
|
||||
nb->clearHistory();
|
||||
if (auto historyI = nb->history()) {
|
||||
historyI->clearHistory();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include <notebookbackend/inotebookbackend.h>
|
||||
|
||||
#include "notebookdatabaseaccess.h"
|
||||
#include "notebooktagmgr.h"
|
||||
|
||||
using namespace vnotex;
|
||||
|
||||
@ -20,6 +21,7 @@ BundleNotebook::BundleNotebook(const NotebookParameters &p_paras,
|
||||
: Notebook(p_paras, p_parent),
|
||||
m_configVersion(p_notebookConfig->m_version),
|
||||
m_history(p_notebookConfig->m_history),
|
||||
m_tagGraph(p_notebookConfig->m_tagGraph),
|
||||
m_extraConfigs(p_notebookConfig->m_extraConfigs)
|
||||
{
|
||||
setupDatabase();
|
||||
@ -59,6 +61,15 @@ void BundleNotebook::initDatabase()
|
||||
int cnt = 0;
|
||||
fillNodeTableFromConfig(getRootNode().data(), m_configVersion < 2, cnt);
|
||||
qDebug() << "fillNodeTableFromConfig nodes count" << cnt;
|
||||
|
||||
fillTagTableFromTagGraph();
|
||||
|
||||
cnt = 0;
|
||||
fillTagTableFromConfig(getRootNode().data(), cnt);
|
||||
}
|
||||
|
||||
if (m_tagMgr) {
|
||||
m_tagMgr->update();
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,6 +98,11 @@ void BundleNotebook::remove()
|
||||
}
|
||||
}
|
||||
|
||||
HistoryI *BundleNotebook::history()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
const QVector<HistoryItem> &BundleNotebook::getHistory() const
|
||||
{
|
||||
return m_history;
|
||||
@ -166,5 +182,75 @@ bool BundleNotebook::rebuildDatabase()
|
||||
|
||||
setupDatabase();
|
||||
initDatabase();
|
||||
|
||||
emit tagsUpdated();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const QString &BundleNotebook::getTagGraph() const
|
||||
{
|
||||
return m_tagGraph;
|
||||
}
|
||||
|
||||
void BundleNotebook::updateTagGraph(const QString &p_tagGraph)
|
||||
{
|
||||
if (m_tagGraph == p_tagGraph) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_tagGraph = p_tagGraph;
|
||||
updateNotebookConfig();
|
||||
}
|
||||
|
||||
void BundleNotebook::fillTagTableFromTagGraph()
|
||||
{
|
||||
auto tagGraph = NotebookTagMgr::stringToTagGraph(m_tagGraph);
|
||||
for (const auto &tagPair : tagGraph) {
|
||||
if (!m_dbAccess->addTag(tagPair.m_parent)) {
|
||||
qWarning() << "failed to add tag to DB" << tagPair.m_parent;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!m_dbAccess->addTag(tagPair.m_child, tagPair.m_parent)) {
|
||||
qWarning() << "failed to add tag to DB" << tagPair.m_child;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
QCoreApplication::processEvents();
|
||||
}
|
||||
|
||||
void BundleNotebook::fillTagTableFromConfig(Node *p_node, int &p_totalCnt)
|
||||
{
|
||||
// @p_node must already exists in node table.
|
||||
bool ret = m_dbAccess->updateNodeTags(p_node);
|
||||
if (!ret) {
|
||||
qWarning() << "failed to add tags of node to DB" << p_node->getName() << p_node->getTags();
|
||||
return;
|
||||
}
|
||||
|
||||
if (++p_totalCnt % 10) {
|
||||
QCoreApplication::processEvents();
|
||||
}
|
||||
|
||||
const auto &children = p_node->getChildrenRef();
|
||||
for (const auto &child : children) {
|
||||
fillTagTableFromConfig(child.data(), p_totalCnt);
|
||||
}
|
||||
}
|
||||
|
||||
NotebookTagMgr *BundleNotebook::getTagMgr() const
|
||||
{
|
||||
if (!m_tagMgr) {
|
||||
auto th = const_cast<BundleNotebook *>(this);
|
||||
th->m_tagMgr = new NotebookTagMgr(th);
|
||||
}
|
||||
|
||||
return m_tagMgr;
|
||||
}
|
||||
|
||||
TagI *BundleNotebook::tag()
|
||||
{
|
||||
return getTagMgr();
|
||||
}
|
||||
|
@ -3,14 +3,17 @@
|
||||
|
||||
#include "notebook.h"
|
||||
#include "global.h"
|
||||
#include "historyi.h"
|
||||
|
||||
namespace vnotex
|
||||
{
|
||||
class BundleNotebookConfigMgr;
|
||||
class NotebookConfig;
|
||||
class NotebookDatabaseAccess;
|
||||
class NotebookTagMgr;
|
||||
|
||||
class BundleNotebook : public Notebook
|
||||
class BundleNotebook : public Notebook,
|
||||
public HistoryI
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
@ -26,9 +29,8 @@ 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;
|
||||
const QString &getTagGraph() const;
|
||||
void updateTagGraph(const QString &p_tagGraph);
|
||||
|
||||
const QJsonObject &getExtraConfigs() const Q_DECL_OVERRIDE;
|
||||
void setExtraConfig(const QString &p_key, const QJsonObject &p_obj) Q_DECL_OVERRIDE;
|
||||
@ -37,6 +39,18 @@ namespace vnotex
|
||||
|
||||
NotebookDatabaseAccess *getDatabaseAccess() const;
|
||||
|
||||
TagI *tag() Q_DECL_OVERRIDE;
|
||||
|
||||
// HistoryI.
|
||||
public:
|
||||
HistoryI *history() 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;
|
||||
|
||||
protected:
|
||||
void initializeInternal() Q_DECL_OVERRIDE;
|
||||
|
||||
@ -49,14 +63,25 @@ namespace vnotex
|
||||
|
||||
void initDatabase();
|
||||
|
||||
void fillTagTableFromTagGraph();
|
||||
|
||||
void fillTagTableFromConfig(Node *p_node, int &p_totalCnt);
|
||||
|
||||
NotebookTagMgr *getTagMgr() const;
|
||||
|
||||
const int m_configVersion;
|
||||
|
||||
QVector<HistoryItem> m_history;
|
||||
|
||||
QString m_tagGraph;
|
||||
|
||||
QJsonObject m_extraConfigs;
|
||||
|
||||
// Managed by QObject.
|
||||
NotebookDatabaseAccess *m_dbAccess = nullptr;
|
||||
|
||||
// Managed by QObject.
|
||||
NotebookTagMgr *m_tagMgr = nullptr;
|
||||
};
|
||||
} // ns vnotex
|
||||
|
||||
|
23
src/core/notebook/historyi.h
Normal file
@ -0,0 +1,23 @@
|
||||
#ifndef HISTORYI_H
|
||||
#define HISTORYI_H
|
||||
|
||||
#include <QVector>
|
||||
|
||||
#include <core/historyitem.h>
|
||||
|
||||
namespace vnotex
|
||||
{
|
||||
// History interface for notebook.
|
||||
class HistoryI
|
||||
{
|
||||
public:
|
||||
virtual ~HistoryI() = default;
|
||||
|
||||
virtual const QVector<HistoryItem> &getHistory() const = 0;
|
||||
|
||||
virtual void addHistory(const HistoryItem &p_item) = 0;
|
||||
|
||||
virtual void clearHistory() = 0;
|
||||
};
|
||||
}
|
||||
#endif // HISTORYI_H
|
@ -245,6 +245,17 @@ const QStringList &Node::getTags() const
|
||||
return m_tags;
|
||||
}
|
||||
|
||||
void Node::updateTags(const QStringList &p_tags)
|
||||
{
|
||||
if (p_tags == m_tags) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_tags = p_tags;
|
||||
save();
|
||||
emit m_notebook->nodeUpdated(this);
|
||||
}
|
||||
|
||||
bool Node::isReadOnly() const
|
||||
{
|
||||
return m_flags & Flag::ReadOnly;
|
||||
|
@ -132,6 +132,7 @@ namespace vnotex
|
||||
virtual void save();
|
||||
|
||||
const QStringList &getTags() const;
|
||||
void updateTags(const QStringList &p_tags);
|
||||
|
||||
const QString &getAttachmentFolder() const;
|
||||
void setAttachmentFolder(const QString &p_attachmentFolder);
|
||||
|
@ -406,3 +406,13 @@ bool Notebook::rebuildDatabase()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
HistoryI *Notebook::history()
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TagI *Notebook::tag()
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -8,7 +8,6 @@
|
||||
#include "notebookparameters.h"
|
||||
#include <core/global.h>
|
||||
#include "node.h"
|
||||
#include <core/historyitem.h>
|
||||
|
||||
namespace vnotex
|
||||
{
|
||||
@ -17,6 +16,8 @@ namespace vnotex
|
||||
class INotebookConfigMgr;
|
||||
class NodeParameters;
|
||||
class File;
|
||||
class HistoryI;
|
||||
class TagI;
|
||||
|
||||
// Base class of notebook.
|
||||
class Notebook : public QObject
|
||||
@ -133,10 +134,6 @@ namespace vnotex
|
||||
|
||||
void reloadNodes();
|
||||
|
||||
virtual const QVector<HistoryItem> &getHistory() const = 0;
|
||||
virtual void addHistory(const HistoryItem &p_item) = 0;
|
||||
virtual void clearHistory() = 0;
|
||||
|
||||
// Hold extra 3rd party configs.
|
||||
virtual const QJsonObject &getExtraConfigs() const = 0;
|
||||
QJsonObject getExtraConfig(const QString &p_key) const;
|
||||
@ -153,11 +150,20 @@ namespace vnotex
|
||||
|
||||
static const QString c_defaultImageFolder;
|
||||
|
||||
public:
|
||||
// Return null if history is not suported.
|
||||
virtual HistoryI *history();
|
||||
|
||||
// Return null if tag is not suported.
|
||||
virtual TagI *tag();
|
||||
|
||||
signals:
|
||||
void updated();
|
||||
|
||||
void nodeUpdated(const Node *p_node);
|
||||
|
||||
void tagsUpdated();
|
||||
|
||||
protected:
|
||||
virtual void initializeInternal() = 0;
|
||||
|
||||
|
@ -7,11 +7,14 @@ SOURCES += \
|
||||
$$PWD/notebookparameters.cpp \
|
||||
$$PWD/bundlenotebook.cpp \
|
||||
$$PWD/node.cpp \
|
||||
$$PWD/notebooktagmgr.cpp \
|
||||
$$PWD/tag.cpp \
|
||||
$$PWD/vxnode.cpp \
|
||||
$$PWD/vxnodefile.cpp
|
||||
|
||||
HEADERS += \
|
||||
$$PWD/externalnode.h \
|
||||
$$PWD/historyi.h \
|
||||
$$PWD/nodeparameters.h \
|
||||
$$PWD/notebook.h \
|
||||
$$PWD/inotebookfactory.h \
|
||||
@ -20,5 +23,8 @@ HEADERS += \
|
||||
$$PWD/notebookparameters.h \
|
||||
$$PWD/bundlenotebook.h \
|
||||
$$PWD/node.h \
|
||||
$$PWD/notebooktagmgr.h \
|
||||
$$PWD/tag.h \
|
||||
$$PWD/tagi.h \
|
||||
$$PWD/vxnode.h \
|
||||
$$PWD/vxnodefile.h
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
#include <QtSql>
|
||||
#include <QDebug>
|
||||
#include <QSet>
|
||||
|
||||
#include <core/exception.h>
|
||||
|
||||
@ -12,6 +13,10 @@ using namespace vnotex;
|
||||
|
||||
static QString c_nodeTableName = "node";
|
||||
|
||||
static QString c_tagTableName = "tag";
|
||||
|
||||
static QString c_nodeTagTableName = "tag_node";
|
||||
|
||||
NotebookDatabaseAccess::NotebookDatabaseAccess(Notebook *p_notebook, const QString &p_databaseFile, QObject *p_parent)
|
||||
: QObject(p_parent),
|
||||
m_notebook(p_notebook),
|
||||
@ -64,18 +69,40 @@ void NotebookDatabaseAccess::setupTables(QSqlDatabase &p_db, int p_configVersion
|
||||
|
||||
QSqlQuery query(p_db);
|
||||
|
||||
// Node.
|
||||
if (m_fresh) {
|
||||
// Node.
|
||||
bool ret = query.exec(QString("CREATE TABLE %1 (\n"
|
||||
" id INTEGER PRIMARY KEY,\n"
|
||||
" name text NOT NULL,\n"
|
||||
" name TEXT NOT NULL,\n"
|
||||
" signature INTEGER NOT NULL,\n"
|
||||
" parent_id INTEGER NULL REFERENCES %1(id) ON DELETE CASCADE)\n").arg(c_nodeTableName));
|
||||
" parent_id INTEGER NULL REFERENCES %1(id) ON DELETE CASCADE ON UPDATE CASCADE)\n").arg(c_nodeTableName));
|
||||
if (!ret) {
|
||||
qWarning() << QString("failed to create database table (%1) (%2)").arg(c_nodeTableName, query.lastError().text());
|
||||
m_valid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Tag.
|
||||
ret = query.exec(QString("CREATE TABLE %1 (\n"
|
||||
" name TEXT PRIMARY KEY,\n"
|
||||
" parent_name TEXT NULL REFERENCES %1(name) ON DELETE CASCADE ON UPDATE CASCADE) WITHOUT ROWID\n").arg(c_tagTableName));
|
||||
if (!ret) {
|
||||
qWarning() << QString("failed to create database table (%1) (%2)").arg(c_tagTableName, query.lastError().text());
|
||||
m_valid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Node_Tag.
|
||||
ret = query.exec(QString("CREATE TABLE %1 (\n"
|
||||
" node_id INTEGER REFERENCES %2(id) ON DELETE CASCADE ON UPDATE CASCADE,\n"
|
||||
" tag_name TEXT REFERENCES %3(name) ON DELETE CASCADE ON UPDATE CASCADE)\n").arg(c_nodeTagTableName,
|
||||
c_nodeTableName,
|
||||
c_tagTableName));
|
||||
if (!ret) {
|
||||
qWarning() << QString("failed to create database table (%1) (%2)").arg(c_nodeTagTableName, query.lastError().text());
|
||||
m_valid = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -113,7 +140,7 @@ bool NotebookDatabaseAccess::addNode(Node *p_node, bool p_ignoreId)
|
||||
if (p_node->getId() != InvalidId) {
|
||||
auto nodeRec = queryNode(p_node->getId());
|
||||
if (nodeRec) {
|
||||
auto nodePath = queryNodePath(p_node->getId());
|
||||
auto nodePath = queryNodeParentPath(p_node->getId());
|
||||
if (existsNode(p_node, nodeRec.data(), nodePath)) {
|
||||
return true;
|
||||
}
|
||||
@ -156,7 +183,7 @@ bool NotebookDatabaseAccess::addNode(Node *p_node, bool p_ignoreId)
|
||||
}
|
||||
|
||||
if (!query.exec()) {
|
||||
qWarning() << "failed to add node by query" << query.executedQuery() << query.lastError().text();
|
||||
qWarning() << "failed to add node" << query.executedQuery() << query.lastError().text();
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -164,13 +191,7 @@ bool NotebookDatabaseAccess::addNode(Node *p_node, bool p_ignoreId)
|
||||
const ID preId = p_node->getId();
|
||||
p_node->updateId(id);
|
||||
|
||||
qDebug("added node id %llu preId %llu ignoreId %d sig %llu name %s parentId %llu",
|
||||
id,
|
||||
preId,
|
||||
p_ignoreId,
|
||||
p_node->getSignature(),
|
||||
p_node->getName().toStdString(),
|
||||
p_node->getParent() ? p_node->getParent()->getId() : Node::InvalidId);
|
||||
qDebug() << "added node id" << id << p_node->getName();
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -178,7 +199,7 @@ QSharedPointer<NotebookDatabaseAccess::NodeRecord> NotebookDatabaseAccess::query
|
||||
{
|
||||
auto db = getDatabase();
|
||||
QSqlQuery query(db);
|
||||
query.prepare(QString("SELECT id, name, signature, parent_id from %1 where id = :id").arg(c_nodeTableName));
|
||||
query.prepare(QString("SELECT id, name, signature, parent_id FROM %1 WHERE id = :id").arg(c_nodeTableName));
|
||||
query.bindValue(":id", p_id);
|
||||
if (!query.exec()) {
|
||||
qWarning() << "failed to query node" << query.executedQuery() << query.lastError().text();
|
||||
@ -210,7 +231,7 @@ bool NotebookDatabaseAccess::existsNode(const Node *p_node)
|
||||
|
||||
return existsNode(p_node,
|
||||
queryNode(p_node->getId()).data(),
|
||||
queryNodePath(p_node->getId()));
|
||||
queryNodeParentPath(p_node->getId()));
|
||||
}
|
||||
|
||||
bool NotebookDatabaseAccess::existsNode(const Node *p_node, const NodeRecord *p_rec, const QStringList &p_nodePath)
|
||||
@ -226,7 +247,7 @@ bool NotebookDatabaseAccess::existsNode(const Node *p_node, const NodeRecord *p_
|
||||
return checkNodePath(p_node, p_nodePath);
|
||||
}
|
||||
|
||||
QStringList NotebookDatabaseAccess::queryNodePath(ID p_id)
|
||||
QStringList NotebookDatabaseAccess::queryNodeParentPath(ID p_id)
|
||||
{
|
||||
auto db = getDatabase();
|
||||
QSqlQuery query(db);
|
||||
@ -239,7 +260,7 @@ QStringList NotebookDatabaseAccess::queryNodePath(ID p_id)
|
||||
" FROM %1 node\n"
|
||||
" JOIN cte_parents cte ON node.id = cte.parent_id\n"
|
||||
" LIMIT 5000)\n"
|
||||
"SELECT * FROM cte_parents").arg(c_nodeTableName));
|
||||
"SELECT id, name, parent_id FROM cte_parents").arg(c_nodeTableName));
|
||||
query.bindValue(":id", p_id);
|
||||
if (!query.exec()) {
|
||||
qWarning() << "failed to query node's path" << query.executedQuery() << query.lastError().text();
|
||||
@ -259,6 +280,22 @@ QStringList NotebookDatabaseAccess::queryNodePath(ID p_id)
|
||||
return ret;
|
||||
}
|
||||
|
||||
QString NotebookDatabaseAccess::queryNodePath(ID p_id)
|
||||
{
|
||||
auto parentPath = queryNodeParentPath(p_id);
|
||||
if (parentPath.isEmpty()) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
if (parentPath.size() == 1) {
|
||||
return parentPath.first();
|
||||
}
|
||||
|
||||
QString relativePath = parentPath.join(QLatin1Char('/'));
|
||||
Q_ASSERT(relativePath[0] == QLatin1Char('/'));
|
||||
return relativePath.mid(1);
|
||||
}
|
||||
|
||||
bool NotebookDatabaseAccess::updateNode(const Node *p_node)
|
||||
{
|
||||
Q_ASSERT(p_node->getParent());
|
||||
@ -379,3 +416,350 @@ bool NotebookDatabaseAccess::checkNodePath(const Node *p_node, const QStringList
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NotebookDatabaseAccess::addTag(const QString &p_name, const QString &p_parentName)
|
||||
{
|
||||
return addTag(p_name, p_parentName, true);
|
||||
}
|
||||
|
||||
bool NotebookDatabaseAccess::addTag(const QString &p_name)
|
||||
{
|
||||
return addTag(p_name, QString(), false);
|
||||
}
|
||||
|
||||
bool NotebookDatabaseAccess::addTag(const QString &p_name, const QString &p_parentName, bool p_updateOnExists)
|
||||
{
|
||||
{
|
||||
auto tagRec = queryTag(p_name);
|
||||
if (tagRec) {
|
||||
if (!p_updateOnExists || tagRec->m_parentName == p_parentName) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return updateTagParent(p_name, p_parentName);
|
||||
}
|
||||
}
|
||||
|
||||
auto db = getDatabase();
|
||||
QSqlQuery query(db);
|
||||
query.prepare(QString("INSERT INTO %1 (name, parent_name)\n"
|
||||
" VALUES (:name, :parent_name)").arg(c_tagTableName));
|
||||
query.bindValue(":name", p_name);
|
||||
query.bindValue(":parent_name", p_parentName.isEmpty() ? QVariant() : p_parentName);
|
||||
|
||||
if (!query.exec()) {
|
||||
qWarning() << "failed to add tag" << query.executedQuery() << query.lastError().text();
|
||||
return false;
|
||||
}
|
||||
|
||||
qDebug() << "added tag" << p_name << "parentName" << p_parentName;
|
||||
return true;
|
||||
}
|
||||
|
||||
QSharedPointer<NotebookDatabaseAccess::TagRecord> NotebookDatabaseAccess::queryTag(const QString &p_name)
|
||||
{
|
||||
auto db = getDatabase();
|
||||
QSqlQuery query(db);
|
||||
query.prepare(QString("SELECT name, parent_name FROM %1 WHERE name = :name").arg(c_tagTableName));
|
||||
query.bindValue(":name", p_name);
|
||||
if (!query.exec()) {
|
||||
qWarning() << "failed to query tag" << query.executedQuery() << query.lastError().text();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (query.next()) {
|
||||
auto tagRec = QSharedPointer<TagRecord>::create();
|
||||
tagRec->m_name = query.value(0).toString();
|
||||
tagRec->m_parentName = query.value(1).toString();
|
||||
return tagRec;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool NotebookDatabaseAccess::updateTagParent(const QString &p_name, const QString &p_parentName)
|
||||
{
|
||||
auto db = getDatabase();
|
||||
QSqlQuery query(db);
|
||||
query.prepare(QString("UPDATE %1\n"
|
||||
"SET parent_name = :parent_name\n"
|
||||
"WHERE name = :name").arg(c_tagTableName));
|
||||
query.bindValue(":name", p_name);
|
||||
query.bindValue(":parent_name", p_parentName.isEmpty() ? QVariant() : p_parentName);
|
||||
if (!query.exec()) {
|
||||
qWarning() << "failed to update tag" << query.executedQuery() << query.lastError().text();
|
||||
return false;
|
||||
}
|
||||
|
||||
qDebug() << "updated tag parent" << p_name << p_parentName;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NotebookDatabaseAccess::renameTag(const QString &p_name, const QString &p_newName)
|
||||
{
|
||||
Q_ASSERT(!p_newName.isEmpty());
|
||||
if (p_name == p_newName) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto db = getDatabase();
|
||||
QSqlQuery query(db);
|
||||
query.prepare(QString("UPDATE %1\n"
|
||||
"SET name = :new_name\n"
|
||||
"WHERE name = :name").arg(c_tagTableName));
|
||||
query.bindValue(":name", p_name);
|
||||
query.bindValue(":new_name", p_newName);
|
||||
if (!query.exec()) {
|
||||
qWarning() << "failed to update tag" << query.executedQuery() << query.lastError().text();
|
||||
return false;
|
||||
}
|
||||
|
||||
qDebug() << "updated tag name" << p_name << p_newName;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NotebookDatabaseAccess::removeTag(const QString &p_name)
|
||||
{
|
||||
auto db = getDatabase();
|
||||
QSqlQuery query(db);
|
||||
query.prepare(QString("DELETE FROM %1\n"
|
||||
"WHERE name = :name").arg(c_tagTableName));
|
||||
query.bindValue(":name", p_name);
|
||||
if (!query.exec()) {
|
||||
qWarning() << "failed to remove tag" << query.executedQuery() << query.lastError().text();
|
||||
return false;
|
||||
}
|
||||
qDebug() << "removed tag" << p_name;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NotebookDatabaseAccess::updateNodeTags(Node *p_node)
|
||||
{
|
||||
p_node->load();
|
||||
|
||||
if (p_node->getId() == Node::InvalidId) {
|
||||
qWarning() << "failed to update tags of node with invalid id" << p_node->fetchPath();
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto &nodeTags = p_node->getTags();
|
||||
|
||||
{
|
||||
const auto tags = QSet<QString>::fromList(queryNodeTags(p_node->getId()));
|
||||
if (tags.isEmpty() && nodeTags.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool needUpdate = false;
|
||||
if (tags.size() != nodeTags.size()) {
|
||||
needUpdate = true;
|
||||
}
|
||||
|
||||
for (const auto &tag : nodeTags) {
|
||||
if (tags.find(tag) == tags.end()) {
|
||||
needUpdate = true;
|
||||
|
||||
if (!addTag(tag)) {
|
||||
qWarning() << "failed to add tag before addNodeTags" << p_node->getId() << tag;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!needUpdate) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool ret = removeNodeTags(p_node->getId());
|
||||
if (!ret) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return addNodeTags(p_node->getId(), nodeTags);
|
||||
}
|
||||
|
||||
QStringList NotebookDatabaseAccess::queryNodeTags(ID p_id)
|
||||
{
|
||||
auto db = getDatabase();
|
||||
QSqlQuery query(db);
|
||||
query.prepare(QString("SELECT tag_name FROM %1 WHERE node_id = :node_id").arg(c_nodeTagTableName));
|
||||
query.bindValue(":node_id", p_id);
|
||||
if (!query.exec()) {
|
||||
qWarning() << "failed to query node's tags" << query.executedQuery() << query.lastError().text();
|
||||
return QStringList();
|
||||
}
|
||||
|
||||
QStringList tags;
|
||||
while (query.next()) {
|
||||
tags.append(query.value(0).toString());
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
|
||||
bool NotebookDatabaseAccess::removeNodeTags(ID p_id)
|
||||
{
|
||||
auto db = getDatabase();
|
||||
QSqlQuery query(db);
|
||||
query.prepare(QString("DELETE FROM %1\n"
|
||||
"WHERE node_id = :node_id").arg(c_nodeTagTableName));
|
||||
query.bindValue(":node_id", p_id);
|
||||
if (!query.exec()) {
|
||||
qWarning() << "failed to remove tags of node" << query.executedQuery() << query.lastError().text();
|
||||
return false;
|
||||
}
|
||||
qDebug() << "removed tags of node" << p_id;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NotebookDatabaseAccess::addNodeTags(ID p_id, const QStringList &p_tags)
|
||||
{
|
||||
Q_ASSERT(p_id != Node::InvalidId);
|
||||
if (p_tags.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto db = getDatabase();
|
||||
QSqlQuery query(db);
|
||||
query.prepare(QString("INSERT INTO %1 (node_id, tag_name)\n"
|
||||
" VALUES (?, ?)").arg(c_nodeTagTableName));
|
||||
|
||||
QVariantList ids;
|
||||
QVariantList tagNames;
|
||||
for (const auto &tag : p_tags) {
|
||||
ids << p_id;
|
||||
tagNames << tag;
|
||||
}
|
||||
|
||||
query.addBindValue(ids);
|
||||
query.addBindValue(tagNames);
|
||||
|
||||
if (!query.execBatch()) {
|
||||
qWarning() << "failed to add tags of node" << query.executedQuery() << query.lastError().text();
|
||||
return false;
|
||||
}
|
||||
|
||||
qDebug() << "added tags of node" << p_id << p_tags;
|
||||
return true;
|
||||
}
|
||||
|
||||
QList<ID> NotebookDatabaseAccess::queryTagNodes(const QString &p_tag)
|
||||
{
|
||||
QList<ID> nodes;
|
||||
auto db = getDatabase();
|
||||
QSqlQuery query(db);
|
||||
query.prepare(QString("SELECT node_id FROM %1 WHERE tag_name = :tag_name").arg(c_nodeTagTableName));
|
||||
query.bindValue(":tag_name", p_tag);
|
||||
if (!query.exec()) {
|
||||
qWarning() << "failed to query nodes of tag" << query.executedQuery() << query.lastError().text();
|
||||
return nodes;
|
||||
}
|
||||
|
||||
while (query.next()) {
|
||||
nodes.append(query.value(0).toULongLong());
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
QList<ID> NotebookDatabaseAccess::queryTagNodesRecursive(const QString &p_tag)
|
||||
{
|
||||
auto tags = queryTagAndChildren(p_tag);
|
||||
if (tags.size() <= 1) {
|
||||
return queryTagNodes(p_tag);
|
||||
}
|
||||
|
||||
QSet<ID> allIds;
|
||||
for (const auto &tag : tags) {
|
||||
auto ids = queryTagNodes(tag);
|
||||
for (const auto &id : ids) {
|
||||
allIds.insert(id);
|
||||
}
|
||||
}
|
||||
|
||||
return allIds.toList();
|
||||
}
|
||||
|
||||
QStringList NotebookDatabaseAccess::queryTagAndChildren(const QString &p_tag)
|
||||
{
|
||||
auto db = getDatabase();
|
||||
QSqlQuery query(db);
|
||||
query.prepare(QString("WITH RECURSIVE cte_children(name, parent_name) AS (\n"
|
||||
" SELECT tag.name, tag.parent_name\n"
|
||||
" FROM %1 tag\n"
|
||||
" WHERE tag.name = :name\n"
|
||||
" UNION ALL\n"
|
||||
" SELECT tag.name, tag.parent_name\n"
|
||||
" FROM %1 tag\n"
|
||||
" JOIN cte_children cte ON tag.parent_name = cte.name\n"
|
||||
" LIMIT 5000)\n"
|
||||
"SELECT name FROM cte_children").arg(c_tagTableName));
|
||||
query.bindValue(":name", p_tag);
|
||||
if (!query.exec()) {
|
||||
qWarning() << "failed to query tag and its children" << query.executedQuery() << query.lastError().text();
|
||||
return QStringList();
|
||||
}
|
||||
|
||||
QStringList ret;
|
||||
while (query.next()) {
|
||||
ret.append(query.value(0).toString());
|
||||
}
|
||||
|
||||
qDebug() << "tag and its children" << p_tag << ret;
|
||||
return ret;
|
||||
}
|
||||
|
||||
QStringList NotebookDatabaseAccess::getNodesOfTags(const QStringList &p_tags)
|
||||
{
|
||||
QStringList ret;
|
||||
if (p_tags.isEmpty()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
QList<ID> nodeIds;
|
||||
|
||||
if (p_tags.size() == 1) {
|
||||
nodeIds = queryTagNodesRecursive(p_tags.first());
|
||||
} else {
|
||||
QSet<ID> allIds;
|
||||
for (const auto &tag : p_tags) {
|
||||
auto ids = queryTagNodesRecursive(tag);
|
||||
for (const auto &id : ids) {
|
||||
allIds.insert(id);
|
||||
}
|
||||
}
|
||||
nodeIds = allIds.toList();
|
||||
}
|
||||
|
||||
for (const auto &id : nodeIds) {
|
||||
auto nodePath = queryNodePath(id);
|
||||
if (nodePath.isNull()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ret.append(nodePath);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
QList<NotebookDatabaseAccess::TagRecord> NotebookDatabaseAccess::getAllTags()
|
||||
{
|
||||
QList<TagRecord> ret;
|
||||
|
||||
auto db = getDatabase();
|
||||
QSqlQuery query(db);
|
||||
query.prepare(QString("SELECT name, parent_name FROM %1 ORDER BY parent_name, name").arg(c_tagTableName));
|
||||
if (!query.exec()) {
|
||||
qWarning() << "failed to query tags" << query.executedQuery() << query.lastError().text();
|
||||
return ret;
|
||||
}
|
||||
|
||||
while (query.next()) {
|
||||
ret.append(TagRecord());
|
||||
ret.last().m_name = query.value(0).toString();
|
||||
ret.last().m_parentName = query.value(1).toString();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
@ -24,6 +24,13 @@ namespace vnotex
|
||||
public:
|
||||
enum { InvalidId = 0 };
|
||||
|
||||
struct TagRecord
|
||||
{
|
||||
QString m_name;
|
||||
|
||||
QString m_parentName;
|
||||
};
|
||||
|
||||
friend class tests::TestNotebookDatabase;
|
||||
|
||||
NotebookDatabaseAccess(Notebook *p_notebook, const QString &p_databaseFile, QObject *p_parent = nullptr);
|
||||
@ -38,6 +45,8 @@ namespace vnotex
|
||||
|
||||
void close();
|
||||
|
||||
// Node table.
|
||||
public:
|
||||
bool addNode(Node *p_node, bool p_ignoreId);
|
||||
|
||||
// Whether there is a record with the same ID in DB and has the same path.
|
||||
@ -49,6 +58,29 @@ namespace vnotex
|
||||
|
||||
bool removeNode(const Node *p_node);
|
||||
|
||||
// Tag table.
|
||||
public:
|
||||
// Will update the tag if exists.
|
||||
bool addTag(const QString &p_name, const QString &p_parentName);
|
||||
|
||||
bool addTag(const QString &p_name);
|
||||
|
||||
bool renameTag(const QString &p_name, const QString &p_newName);
|
||||
|
||||
bool removeTag(const QString &p_name);
|
||||
|
||||
// Sorted by parent_name.
|
||||
QList<TagRecord> getAllTags();
|
||||
|
||||
QStringList queryTagAndChildren(const QString &p_tag);
|
||||
|
||||
// Node_tag table.
|
||||
public:
|
||||
bool updateNodeTags(Node *p_node);
|
||||
|
||||
// Return the relative path of nodes of tags @p_tags.
|
||||
QStringList getNodesOfTags(const QStringList &p_tags);
|
||||
|
||||
private:
|
||||
struct NodeRecord
|
||||
{
|
||||
@ -68,7 +100,9 @@ namespace vnotex
|
||||
// Return null if not exists.
|
||||
QSharedPointer<NodeRecord> queryNode(ID p_id);
|
||||
|
||||
QStringList queryNodePath(ID p_id);
|
||||
QStringList queryNodeParentPath(ID p_id);
|
||||
|
||||
QString queryNodePath(ID p_id);
|
||||
|
||||
bool nodeEqual(const NodeRecord *p_rec, const Node *p_node) const;
|
||||
|
||||
@ -78,6 +112,23 @@ namespace vnotex
|
||||
|
||||
bool removeNode(ID p_id);
|
||||
|
||||
// Return null if not exists.
|
||||
QSharedPointer<TagRecord> queryTag(const QString &p_name);
|
||||
|
||||
bool updateTagParent(const QString &p_name, const QString &p_parentName);
|
||||
|
||||
bool addTag(const QString &p_name, const QString &p_parentName, bool p_updateOnExists);
|
||||
|
||||
QStringList queryNodeTags(ID p_id);
|
||||
|
||||
QList<ID> queryTagNodes(const QString &p_tag);
|
||||
|
||||
QList<ID> queryTagNodesRecursive(const QString &p_tag);
|
||||
|
||||
bool removeNodeTags(ID p_id);
|
||||
|
||||
bool addNodeTags(ID p_id, const QStringList &p_tags);
|
||||
|
||||
Notebook *m_notebook = nullptr;
|
||||
|
||||
QString m_databaseFile;
|
||||
|
318
src/core/notebook/notebooktagmgr.cpp
Normal file
@ -0,0 +1,318 @@
|
||||
#include "notebooktagmgr.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QHash>
|
||||
|
||||
#include "bundlenotebook.h"
|
||||
#include "tag.h"
|
||||
|
||||
using namespace vnotex;
|
||||
|
||||
NotebookTagMgr::NotebookTagMgr(BundleNotebook *p_notebook)
|
||||
: QObject(p_notebook),
|
||||
m_notebook(p_notebook)
|
||||
{
|
||||
update();
|
||||
}
|
||||
|
||||
QVector<NotebookTagMgr::TagGraphPair> NotebookTagMgr::stringToTagGraph(const QString &p_text)
|
||||
{
|
||||
// parent>chlid;parent2>chlid2.
|
||||
QVector<TagGraphPair> tagGraph;
|
||||
auto pairs = p_text.split(QLatin1Char(';'));
|
||||
for (const auto &pa : pairs) {
|
||||
auto paCh = pa.split(QLatin1Char('>'));
|
||||
if (paCh.size() != 2 || paCh[0].isEmpty() || paCh[1].isEmpty()) {
|
||||
qWarning() << "ignore invalid <parent, child> tag pair" << pa;
|
||||
continue;
|
||||
}
|
||||
|
||||
TagGraphPair tagPair;
|
||||
tagPair.m_parent = paCh[0];
|
||||
tagPair.m_child = paCh[1];
|
||||
tagGraph.push_back(tagPair);
|
||||
}
|
||||
|
||||
return tagGraph;
|
||||
}
|
||||
|
||||
QString NotebookTagMgr::tagGraphToString(const QVector<TagGraphPair> &p_tagGraph)
|
||||
{
|
||||
QString text;
|
||||
if (p_tagGraph.isEmpty()) {
|
||||
return text;
|
||||
}
|
||||
|
||||
text = p_tagGraph[0].m_parent + QLatin1Char('>') + p_tagGraph[0].m_child;
|
||||
for (int i = 1; i < p_tagGraph.size(); ++i) {
|
||||
text += QLatin1Char(';') + p_tagGraph[i].m_parent + QLatin1Char('>') + p_tagGraph[i].m_child;
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
const QVector<QSharedPointer<Tag>> &NotebookTagMgr::getTopLevelTags() const
|
||||
{
|
||||
return m_topLevelTags;
|
||||
}
|
||||
|
||||
void NotebookTagMgr::update()
|
||||
{
|
||||
auto db = m_notebook->getDatabaseAccess();
|
||||
const auto allTags = db->getAllTags();
|
||||
|
||||
update(allTags);
|
||||
}
|
||||
|
||||
void NotebookTagMgr::update(const QList<NotebookDatabaseAccess::TagRecord> &p_allTags)
|
||||
{
|
||||
m_topLevelTags.clear();
|
||||
|
||||
QHash<QString, Tag *> nameToTag;
|
||||
|
||||
QVector<int> todoIdx;
|
||||
todoIdx.reserve(p_allTags.size());
|
||||
for (int i = 0; i < p_allTags.size(); ++i) {
|
||||
todoIdx.push_back(i);
|
||||
}
|
||||
|
||||
while (!todoIdx.isEmpty()) {
|
||||
QVector<int> pendingIdx;
|
||||
pendingIdx.reserve(p_allTags.size());
|
||||
|
||||
for (int i = 0; i < todoIdx.size(); ++i) {
|
||||
const auto &rec = p_allTags[todoIdx[i]];
|
||||
Q_ASSERT(!nameToTag.contains(rec.m_name));
|
||||
QSharedPointer<Tag> newTag;
|
||||
if (rec.m_parentName.isEmpty()) {
|
||||
// Top level.
|
||||
newTag = QSharedPointer<Tag>::create(rec.m_name);
|
||||
m_topLevelTags.push_back(newTag);
|
||||
} else {
|
||||
auto parentIt = nameToTag.find(rec.m_parentName);
|
||||
if (parentIt == nameToTag.end()) {
|
||||
// Need to process its parent first.
|
||||
pendingIdx.push_back(todoIdx[i]);
|
||||
continue;
|
||||
} else {
|
||||
newTag = QSharedPointer<Tag>::create(rec.m_name);
|
||||
parentIt.value()->addChild(newTag);
|
||||
}
|
||||
}
|
||||
|
||||
nameToTag.insert(newTag->name(), newTag.data());
|
||||
}
|
||||
|
||||
if (todoIdx.size() == pendingIdx.size()) {
|
||||
qWarning() << "cyclic parent-chlid tag definition detected";
|
||||
break;
|
||||
}
|
||||
|
||||
todoIdx = pendingIdx;
|
||||
}
|
||||
}
|
||||
|
||||
QStringList NotebookTagMgr::findNodesOfTag(const QString &p_name)
|
||||
{
|
||||
auto db = m_notebook->getDatabaseAccess();
|
||||
return db->getNodesOfTags(QStringList(p_name));
|
||||
}
|
||||
|
||||
QSharedPointer<Tag> NotebookTagMgr::findTag(const QString &p_name)
|
||||
{
|
||||
QSharedPointer<Tag> tag;
|
||||
forEachTag([&tag, p_name](const QSharedPointer<Tag> &p_tag) {
|
||||
if (p_tag->name() == p_name) {
|
||||
tag = p_tag;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
void NotebookTagMgr::forEachTag(const TagFinder &p_func) const
|
||||
{
|
||||
for (const auto &tag : m_topLevelTags) {
|
||||
if (!forEachTag(tag, p_func)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool NotebookTagMgr::forEachTag(const QSharedPointer<Tag> &p_tag, const TagFinder &p_func) const
|
||||
{
|
||||
if (!p_func(p_tag)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto &child : p_tag->getChildren()) {
|
||||
if (!forEachTag(child, p_func)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NotebookTagMgr::newTag(const QString &p_name, const QString &p_parentName)
|
||||
{
|
||||
if (p_name.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto db = m_notebook->getDatabaseAccess();
|
||||
bool ret = db->addTag(p_name, p_parentName);
|
||||
if (ret) {
|
||||
const auto allTags = db->getAllTags();
|
||||
update(allTags);
|
||||
if (!p_parentName.isEmpty()) {
|
||||
updateNotebookTagGraph(allTags);
|
||||
}
|
||||
emit m_notebook->tagsUpdated();
|
||||
return true;
|
||||
} else {
|
||||
qWarning() << "failed to new tag" << p_name << p_parentName;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool NotebookTagMgr::updateNodeTags(Node *p_node)
|
||||
{
|
||||
auto db = m_notebook->getDatabaseAccess();
|
||||
|
||||
// Make sure the node exists in DB.
|
||||
if (!db->addNode(p_node, false)) {
|
||||
qWarning() << "failed to add node to DB" << p_node->fetchPath() << p_node->getId();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (db->updateNodeTags(p_node)) {
|
||||
update();
|
||||
emit m_notebook->tagsUpdated();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NotebookTagMgr::updateNodeTags(Node *p_node, const QStringList &p_newTags)
|
||||
{
|
||||
p_node->updateTags(p_newTags);
|
||||
return updateNodeTags(p_node);
|
||||
}
|
||||
|
||||
bool NotebookTagMgr::renameTag(const QString &p_name, const QString &p_newName)
|
||||
{
|
||||
const auto nodePaths = findNodesOfTag(p_name);
|
||||
|
||||
auto db = m_notebook->getDatabaseAccess();
|
||||
if (!db->renameTag(p_name, p_newName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto allTags = db->getAllTags();
|
||||
update(allTags);
|
||||
|
||||
updateNotebookTagGraph(allTags);
|
||||
|
||||
// Update node tag.
|
||||
for (const auto &pa : nodePaths) {
|
||||
auto node = m_notebook->loadNodeByPath(pa);
|
||||
if (!node) {
|
||||
qWarning() << "node belongs to tag in DB but not exists" << p_name << pa;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto tags = node->getTags();
|
||||
for (auto &tag : tags) {
|
||||
if (tag == p_name) {
|
||||
tag = p_newName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
node->updateTags(tags);
|
||||
}
|
||||
|
||||
emit m_notebook->tagsUpdated();
|
||||
return true;
|
||||
}
|
||||
|
||||
void NotebookTagMgr::updateNotebookTagGraph(const QList<NotebookDatabaseAccess::TagRecord> &p_allTags)
|
||||
{
|
||||
QVector<TagGraphPair> graph;
|
||||
graph.reserve(p_allTags.size());
|
||||
for (const auto &tag : p_allTags) {
|
||||
if (tag.m_parentName.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
TagGraphPair pa;
|
||||
pa.m_parent = tag.m_parentName;
|
||||
pa.m_child = tag.m_name;
|
||||
graph.push_back(pa);
|
||||
}
|
||||
m_notebook->updateTagGraph(tagGraphToString(graph));
|
||||
}
|
||||
|
||||
bool NotebookTagMgr::removeTag(const QString &p_name)
|
||||
{
|
||||
const auto nodePaths = findNodesOfTag(p_name);
|
||||
|
||||
auto db = m_notebook->getDatabaseAccess();
|
||||
QStringList tagsAndChildren;
|
||||
if (!nodePaths.isEmpty()) {
|
||||
tagsAndChildren = db->queryTagAndChildren(p_name);
|
||||
if (tagsAndChildren.isEmpty()) {
|
||||
qWarning() << "failed to query tag and its children" << p_name;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!db->removeTag(p_name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto allTags = db->getAllTags();
|
||||
update(allTags);
|
||||
|
||||
updateNotebookTagGraph(allTags);
|
||||
|
||||
// Update node tag.
|
||||
for (const auto &pa : nodePaths) {
|
||||
auto node = m_notebook->loadNodeByPath(pa);
|
||||
if (!node) {
|
||||
qWarning() << "node belongs to tag in DB but not exists" << p_name << pa;
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto &tags = node->getTags();
|
||||
QStringList newTags;
|
||||
for (const auto &tag : tags) {
|
||||
if (tagsAndChildren.contains(tag)) {
|
||||
continue;
|
||||
}
|
||||
newTags.append(tag);
|
||||
}
|
||||
node->updateTags(newTags);
|
||||
}
|
||||
|
||||
emit m_notebook->tagsUpdated();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NotebookTagMgr::moveTag(const QString &p_name, const QString &p_newParentName)
|
||||
{
|
||||
auto db = m_notebook->getDatabaseAccess();
|
||||
if (!db->addTag(p_name, p_newParentName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto allTags = db->getAllTags();
|
||||
update(allTags);
|
||||
|
||||
updateNotebookTagGraph(allTags);
|
||||
|
||||
emit m_notebook->tagsUpdated();
|
||||
return true;
|
||||
}
|
78
src/core/notebook/notebooktagmgr.h
Normal file
@ -0,0 +1,78 @@
|
||||
#ifndef NOTEBOOKTAGMGR_H
|
||||
#define NOTEBOOKTAGMGR_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "tagi.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <QVector>
|
||||
#include <QSharedPointer>
|
||||
|
||||
#include "notebookdatabaseaccess.h"
|
||||
|
||||
namespace vnotex
|
||||
{
|
||||
class BundleNotebook;
|
||||
class Tag;
|
||||
|
||||
class NotebookTagMgr : public QObject, public TagI
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
struct TagGraphPair
|
||||
{
|
||||
QString m_parent;
|
||||
|
||||
QString m_child;
|
||||
};
|
||||
|
||||
explicit NotebookTagMgr(BundleNotebook *p_notebook);
|
||||
|
||||
void update();
|
||||
|
||||
static QVector<TagGraphPair> stringToTagGraph(const QString &p_text);
|
||||
|
||||
static QString tagGraphToString(const QVector<TagGraphPair> &p_tagGraph);
|
||||
|
||||
// TagI.
|
||||
public:
|
||||
const QVector<QSharedPointer<Tag>> &getTopLevelTags() const Q_DECL_OVERRIDE;
|
||||
|
||||
QStringList findNodesOfTag(const QString &p_name) Q_DECL_OVERRIDE;
|
||||
|
||||
QSharedPointer<Tag> findTag(const QString &p_name) Q_DECL_OVERRIDE;
|
||||
|
||||
bool newTag(const QString &p_name, const QString &p_parentName) Q_DECL_OVERRIDE;
|
||||
|
||||
bool renameTag(const QString &p_name, const QString &p_newName) Q_DECL_OVERRIDE;
|
||||
|
||||
bool updateNodeTags(Node *p_node) Q_DECL_OVERRIDE;
|
||||
|
||||
bool updateNodeTags(Node *p_node, const QStringList &p_newTags) Q_DECL_OVERRIDE;
|
||||
|
||||
bool removeTag(const QString &p_name) Q_DECL_OVERRIDE;
|
||||
|
||||
bool moveTag(const QString &p_name, const QString &p_newParentName) Q_DECL_OVERRIDE;
|
||||
|
||||
private:
|
||||
typedef std::function<bool(const QSharedPointer<Tag> &p_tag)> TagFinder;
|
||||
|
||||
// @p_func: return false to abort the search.
|
||||
void forEachTag(const TagFinder &p_func) const;
|
||||
|
||||
// Return false if abort.
|
||||
bool forEachTag(const QSharedPointer<Tag> &p_tag, const TagFinder &p_func) const;
|
||||
|
||||
void update(const QList<NotebookDatabaseAccess::TagRecord> &p_allTags);
|
||||
|
||||
void updateNotebookTagGraph(const QList<NotebookDatabaseAccess::TagRecord> &p_allTags);
|
||||
|
||||
BundleNotebook *m_notebook = nullptr;
|
||||
|
||||
QVector<QSharedPointer<Tag>> m_topLevelTags;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // NOTEBOOKTAGMGR_H
|
47
src/core/notebook/tag.cpp
Normal file
@ -0,0 +1,47 @@
|
||||
#include "tag.h"
|
||||
|
||||
#include <QRegularExpression>
|
||||
|
||||
#include <utils/pathutils.h>
|
||||
|
||||
using namespace vnotex;
|
||||
|
||||
Tag::Tag(const QString &p_name)
|
||||
: m_name(p_name)
|
||||
{
|
||||
}
|
||||
|
||||
const QVector<QSharedPointer<Tag>> &Tag::getChildren() const
|
||||
{
|
||||
return m_children;
|
||||
}
|
||||
|
||||
void Tag::addChild(const QSharedPointer<Tag> &p_tag)
|
||||
{
|
||||
p_tag->m_parent = this;
|
||||
m_children.push_back(p_tag);
|
||||
}
|
||||
|
||||
const QString &Tag::name() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
Tag *Tag::getParent() const
|
||||
{
|
||||
return m_parent;
|
||||
}
|
||||
|
||||
QString Tag::fetchPath() const
|
||||
{
|
||||
if (!m_parent) {
|
||||
return m_name;
|
||||
} else {
|
||||
return PathUtils::concatenateFilePath(m_parent->fetchPath(), m_name);
|
||||
}
|
||||
}
|
||||
|
||||
bool Tag::isValidName(const QString &p_name)
|
||||
{
|
||||
return !p_name.isEmpty() && !p_name.contains(QRegularExpression("[>/]"));
|
||||
}
|
37
src/core/notebook/tag.h
Normal file
@ -0,0 +1,37 @@
|
||||
#ifndef TAG_H
|
||||
#define TAG_H
|
||||
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
#include <QSharedPointer>
|
||||
#include <QEnableSharedFromThis>
|
||||
|
||||
namespace vnotex
|
||||
{
|
||||
class Tag : public QEnableSharedFromThis<Tag>
|
||||
{
|
||||
public:
|
||||
Tag(const QString &p_name);
|
||||
|
||||
const QVector<QSharedPointer<Tag>> &getChildren() const;
|
||||
|
||||
const QString &name() const;
|
||||
|
||||
Tag *getParent() const;
|
||||
|
||||
void addChild(const QSharedPointer<Tag> &p_tag);
|
||||
|
||||
QString fetchPath() const;
|
||||
|
||||
static bool isValidName(const QString &p_name);
|
||||
|
||||
private:
|
||||
Tag *m_parent = nullptr;
|
||||
|
||||
QString m_name;
|
||||
|
||||
QVector<QSharedPointer<Tag>> m_children;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // TAG_H
|
37
src/core/notebook/tagi.h
Normal file
@ -0,0 +1,37 @@
|
||||
#ifndef TAGI_H
|
||||
#define TAGI_H
|
||||
|
||||
#include <QVector>
|
||||
|
||||
#include "tag.h"
|
||||
|
||||
namespace vnotex
|
||||
{
|
||||
class Node;
|
||||
|
||||
// Tag interface for notebook.
|
||||
class TagI
|
||||
{
|
||||
public:
|
||||
virtual ~TagI() = default;
|
||||
|
||||
virtual const QVector<QSharedPointer<Tag>> &getTopLevelTags() const = 0;
|
||||
|
||||
virtual QStringList findNodesOfTag(const QString &p_name) = 0;
|
||||
|
||||
virtual QSharedPointer<Tag> findTag(const QString &p_name) = 0;
|
||||
|
||||
virtual bool newTag(const QString &p_name, const QString &p_parentName) = 0;
|
||||
|
||||
virtual bool renameTag(const QString &p_name, const QString &p_newName) = 0;
|
||||
|
||||
virtual bool updateNodeTags(Node *p_node) = 0;
|
||||
|
||||
virtual bool updateNodeTags(Node *p_node, const QStringList &p_newTags) = 0;
|
||||
|
||||
virtual bool removeTag(const QString &p_name) = 0;
|
||||
|
||||
virtual bool moveTag(const QString &p_name, const QString &p_newParentName) = 0;
|
||||
};
|
||||
}
|
||||
#endif // TAGI_H
|
@ -35,7 +35,7 @@ QSharedPointer<NotebookConfig> BundleNotebookConfigMgr::readNotebookConfig() con
|
||||
|
||||
void BundleNotebookConfigMgr::writeNotebookConfig()
|
||||
{
|
||||
auto config = NotebookConfig::fromNotebook(getCodeVersion(), getNotebook());
|
||||
auto config = NotebookConfig::fromNotebook(getCodeVersion(), getBundleNotebook());
|
||||
writeNotebookConfig(*config);
|
||||
}
|
||||
|
||||
@ -82,7 +82,7 @@ QString BundleNotebookConfigMgr::getDatabasePath()
|
||||
|
||||
BundleNotebook *BundleNotebookConfigMgr::getBundleNotebook() const
|
||||
{
|
||||
return dynamic_cast<BundleNotebook *>(getNotebook());
|
||||
return static_cast<BundleNotebook *>(getNotebook());
|
||||
}
|
||||
|
||||
bool BundleNotebookConfigMgr::isBuiltInFile(const Node *p_node, const QString &p_name) const
|
||||
|
@ -1,7 +1,7 @@
|
||||
#include "notebookconfig.h"
|
||||
|
||||
#include <notebook/notebookparameters.h>
|
||||
#include <notebook/notebook.h>
|
||||
#include <notebook/bundlenotebook.h>
|
||||
#include <versioncontroller/iversioncontroller.h>
|
||||
#include <utils/utils.h>
|
||||
#include "exception.h"
|
||||
@ -9,22 +9,6 @@
|
||||
|
||||
using namespace vnotex;
|
||||
|
||||
const QString NotebookConfig::c_version = "version";
|
||||
|
||||
const QString NotebookConfig::c_name = "name";
|
||||
|
||||
const QString NotebookConfig::c_description = "description";
|
||||
|
||||
const QString NotebookConfig::c_imageFolder = "image_folder";
|
||||
|
||||
const QString NotebookConfig::c_attachmentFolder = "attachment_folder";
|
||||
|
||||
const QString NotebookConfig::c_createdTimeUtc = "created_time";
|
||||
|
||||
const QString NotebookConfig::c_versionController = "version_controller";
|
||||
|
||||
const QString NotebookConfig::c_configMgr = "config_mgr";
|
||||
|
||||
QSharedPointer<NotebookConfig> NotebookConfig::fromNotebookParameters(int p_version,
|
||||
const NotebookParameters &p_paras)
|
||||
{
|
||||
@ -46,17 +30,19 @@ QJsonObject NotebookConfig::toJson() const
|
||||
{
|
||||
QJsonObject jobj;
|
||||
|
||||
jobj[NotebookConfig::c_version] = m_version;
|
||||
jobj[NotebookConfig::c_name] = m_name;
|
||||
jobj[NotebookConfig::c_description] = m_description;
|
||||
jobj[NotebookConfig::c_imageFolder] = m_imageFolder;
|
||||
jobj[NotebookConfig::c_attachmentFolder] = m_attachmentFolder;
|
||||
jobj[NotebookConfig::c_createdTimeUtc] = Utils::dateTimeStringUniform(m_createdTimeUtc);
|
||||
jobj[NotebookConfig::c_versionController] = m_versionController;
|
||||
jobj[NotebookConfig::c_configMgr] = m_notebookConfigMgr;
|
||||
jobj[QStringLiteral("version")] = m_version;
|
||||
jobj[QStringLiteral("name")] = m_name;
|
||||
jobj[QStringLiteral("description")] = m_description;
|
||||
jobj[QStringLiteral("image_folder")] = m_imageFolder;
|
||||
jobj[QStringLiteral("attachment_folder")] = m_attachmentFolder;
|
||||
jobj[QStringLiteral("created_time")] = Utils::dateTimeStringUniform(m_createdTimeUtc);
|
||||
jobj[QStringLiteral("version_controller")] = m_versionController;
|
||||
jobj[QStringLiteral("config_mgr")] = m_notebookConfigMgr;
|
||||
|
||||
jobj[QStringLiteral("history")] = saveHistory();
|
||||
|
||||
jobj[QStringLiteral("tag_graph")] = m_tagGraph;
|
||||
|
||||
jobj[QStringLiteral("extra_configs")] = m_extraConfigs;
|
||||
|
||||
return jobj;
|
||||
@ -64,32 +50,34 @@ QJsonObject NotebookConfig::toJson() const
|
||||
|
||||
void NotebookConfig::fromJson(const QJsonObject &p_jobj)
|
||||
{
|
||||
if (!p_jobj.contains(NotebookConfig::c_version)
|
||||
|| !p_jobj.contains(NotebookConfig::c_name)
|
||||
|| !p_jobj.contains(NotebookConfig::c_createdTimeUtc)
|
||||
|| !p_jobj.contains(NotebookConfig::c_versionController)
|
||||
|| !p_jobj.contains(NotebookConfig::c_configMgr)) {
|
||||
if (!p_jobj.contains(QStringLiteral("version"))
|
||||
|| !p_jobj.contains(QStringLiteral("name"))
|
||||
|| !p_jobj.contains(QStringLiteral("created_time"))
|
||||
|| !p_jobj.contains(QStringLiteral("version_controller"))
|
||||
|| !p_jobj.contains(QStringLiteral("config_mgr"))) {
|
||||
Exception::throwOne(Exception::Type::InvalidArgument,
|
||||
QString("failed to read notebook configuration from JSON (%1)").arg(QJsonObjectToString(p_jobj)));
|
||||
return;
|
||||
}
|
||||
|
||||
m_version = p_jobj[NotebookConfig::c_version].toInt();
|
||||
m_name = p_jobj[NotebookConfig::c_name].toString();
|
||||
m_description = p_jobj[NotebookConfig::c_description].toString();
|
||||
m_imageFolder = p_jobj[NotebookConfig::c_imageFolder].toString();
|
||||
m_attachmentFolder = p_jobj[NotebookConfig::c_attachmentFolder].toString();
|
||||
m_createdTimeUtc = Utils::dateTimeFromStringUniform(p_jobj[NotebookConfig::c_createdTimeUtc].toString());
|
||||
m_versionController = p_jobj[NotebookConfig::c_versionController].toString();
|
||||
m_notebookConfigMgr = p_jobj[NotebookConfig::c_configMgr].toString();
|
||||
m_version = p_jobj[QStringLiteral("version")].toInt();
|
||||
m_name = p_jobj[QStringLiteral("name")].toString();
|
||||
m_description = p_jobj[QStringLiteral("description")].toString();
|
||||
m_imageFolder = p_jobj[QStringLiteral("image_folder")].toString();
|
||||
m_attachmentFolder = p_jobj[QStringLiteral("attachment_folder")].toString();
|
||||
m_createdTimeUtc = Utils::dateTimeFromStringUniform(p_jobj[QStringLiteral("created_time")].toString());
|
||||
m_versionController = p_jobj[QStringLiteral("version_controller")].toString();
|
||||
m_notebookConfigMgr = p_jobj[QStringLiteral("config_mgr")].toString();
|
||||
|
||||
loadHistory(p_jobj);
|
||||
|
||||
m_tagGraph = p_jobj[QStringLiteral("tag_graph")].toString();
|
||||
|
||||
m_extraConfigs = p_jobj[QStringLiteral("extra_configs")].toObject();
|
||||
}
|
||||
|
||||
QSharedPointer<NotebookConfig> NotebookConfig::fromNotebook(int p_version,
|
||||
const Notebook *p_notebook)
|
||||
const BundleNotebook *p_notebook)
|
||||
{
|
||||
auto config = QSharedPointer<NotebookConfig>::create();
|
||||
|
||||
@ -102,6 +90,7 @@ QSharedPointer<NotebookConfig> NotebookConfig::fromNotebook(int p_version,
|
||||
config->m_versionController = p_notebook->getVersionController()->getName();
|
||||
config->m_notebookConfigMgr = p_notebook->getConfigMgr()->getName();
|
||||
config->m_history = p_notebook->getHistory();
|
||||
config->m_tagGraph = p_notebook->getTagGraph();
|
||||
config->m_extraConfigs = p_notebook->getExtraConfigs();
|
||||
|
||||
return config;
|
||||
|
@ -15,6 +15,7 @@ namespace vnotex
|
||||
{
|
||||
class NotebookParameters;
|
||||
|
||||
// Notebook config of BundleNotebook.
|
||||
class NotebookConfig
|
||||
{
|
||||
public:
|
||||
@ -24,7 +25,7 @@ namespace vnotex
|
||||
const NotebookParameters &p_paras);
|
||||
|
||||
static QSharedPointer<NotebookConfig> fromNotebook(int p_version,
|
||||
const Notebook *p_notebook);
|
||||
const BundleNotebook *p_notebook);
|
||||
|
||||
virtual QJsonObject toJson() const;
|
||||
|
||||
@ -48,6 +49,9 @@ namespace vnotex
|
||||
|
||||
QVector<HistoryItem> m_history;
|
||||
|
||||
// Graph of tags of this notebook like "parent>chlid;parent2>chlid2".
|
||||
QString m_tagGraph;
|
||||
|
||||
// Hold all the extra configs for other components or 3rd party plugins.
|
||||
// Use a unique name as the key and the value is a QJsonObject.
|
||||
QJsonObject m_extraConfigs;
|
||||
@ -56,22 +60,6 @@ namespace vnotex
|
||||
QJsonArray saveHistory() const;
|
||||
|
||||
void loadHistory(const QJsonObject &p_jobj);
|
||||
|
||||
static const QString c_version;
|
||||
|
||||
static const QString c_name;
|
||||
|
||||
static const QString c_description;
|
||||
|
||||
static const QString c_imageFolder;
|
||||
|
||||
static const QString c_attachmentFolder;
|
||||
|
||||
static const QString c_createdTimeUtc;
|
||||
|
||||
static const QString c_versionController;
|
||||
|
||||
static const QString c_configMgr;
|
||||
};
|
||||
} // ns vnotex
|
||||
|
||||
|
@ -221,6 +221,7 @@ QJsonObject SessionConfig::saveStateAndGeometry() const
|
||||
writeByteArray(obj, QStringLiteral("main_window_state"), m_mainWindowStateGeometry.m_mainState);
|
||||
writeByteArray(obj, QStringLiteral("main_window_geometry"), m_mainWindowStateGeometry.m_mainGeometry);
|
||||
writeStringList(obj, QStringLiteral("visible_docks_before_expand"), m_mainWindowStateGeometry.m_visibleDocksBeforeExpand);
|
||||
writeByteArray(obj, QStringLiteral("tag_explorer_state"), m_mainWindowStateGeometry.m_tagExplorerState);
|
||||
return obj;
|
||||
}
|
||||
|
||||
@ -351,6 +352,7 @@ 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"));
|
||||
m_mainWindowStateGeometry.m_visibleDocksBeforeExpand = readStringList(obj, QStringLiteral("visible_docks_before_expand"));
|
||||
m_mainWindowStateGeometry.m_tagExplorerState = readByteArray(obj, QStringLiteral("tag_explorer_state"));
|
||||
}
|
||||
|
||||
QByteArray SessionConfig::getViewAreaSessionAndClear()
|
||||
|
@ -36,7 +36,8 @@ namespace vnotex
|
||||
{
|
||||
return m_mainState == p_other.m_mainState
|
||||
&& m_mainGeometry == p_other.m_mainGeometry
|
||||
&& m_visibleDocksBeforeExpand == p_other.m_visibleDocksBeforeExpand;
|
||||
&& m_visibleDocksBeforeExpand == p_other.m_visibleDocksBeforeExpand
|
||||
&& m_tagExplorerState == p_other.m_tagExplorerState;
|
||||
}
|
||||
|
||||
QByteArray m_mainState;
|
||||
@ -44,6 +45,8 @@ namespace vnotex
|
||||
QByteArray m_mainGeometry;
|
||||
|
||||
QStringList m_visibleDocksBeforeExpand;
|
||||
|
||||
QByteArray m_tagExplorerState;
|
||||
};
|
||||
|
||||
enum OpenGL
|
||||
|
@ -42,6 +42,8 @@ void WidgetConfig::init(const QJsonObject &p_app,
|
||||
m_mainWindowKeepDocksExpandingContentArea = READSTRLIST(QStringLiteral("main_window_keep_docks_expanding_content_area"));
|
||||
|
||||
m_snippetPanelBuiltInSnippetsVisible = READBOOL(QStringLiteral("snippet_panel_builtin_snippets_visible"));
|
||||
|
||||
m_tagExplorerTwoColumnsEnabled = READBOOL(QStringLiteral("tag_explorer_two_columns_enabled"));
|
||||
}
|
||||
|
||||
QJsonObject WidgetConfig::toJson() const
|
||||
@ -59,7 +61,7 @@ QJsonObject WidgetConfig::toJson() const
|
||||
obj[QStringLiteral("node_explorer_close_before_open_with_enabled")] = m_nodeExplorerCloseBeforeOpenWithEnabled;
|
||||
|
||||
obj[QStringLiteral("search_panel_advanced_settings_visible")] = m_searchPanelAdvancedSettingsVisible;
|
||||
obj[QStringLiteral("snippet_panel_builtin_snippets_visible")] = m_snippetPanelBuiltInSnippetsVisible;
|
||||
obj[QStringLiteral("tag_explorer_two_columns_enabled")] = m_tagExplorerTwoColumnsEnabled;
|
||||
writeStringList(obj,
|
||||
QStringLiteral("main_window_keep_docks_expanding_content_area"),
|
||||
m_mainWindowKeepDocksExpandingContentArea);
|
||||
@ -176,3 +178,12 @@ void WidgetConfig::setSnippetPanelBuiltInSnippetsVisible(bool p_visible)
|
||||
updateConfig(m_snippetPanelBuiltInSnippetsVisible, p_visible, this);
|
||||
}
|
||||
|
||||
bool WidgetConfig::getTagExplorerTwoColumnsEnabled() const
|
||||
{
|
||||
return m_tagExplorerTwoColumnsEnabled;
|
||||
}
|
||||
|
||||
void WidgetConfig::setTagExplorerTwoColumnsEnabled(bool p_enabled)
|
||||
{
|
||||
updateConfig(m_tagExplorerTwoColumnsEnabled, p_enabled, this);
|
||||
}
|
||||
|
@ -51,6 +51,9 @@ namespace vnotex
|
||||
bool isSnippetPanelBuiltInSnippetsVisible() const;
|
||||
void setSnippetPanelBuiltInSnippetsVisible(bool p_visible);
|
||||
|
||||
bool getTagExplorerTwoColumnsEnabled() const;
|
||||
void setTagExplorerTwoColumnsEnabled(bool p_enabled);
|
||||
|
||||
private:
|
||||
int m_outlineAutoExpandedLevel = 6;
|
||||
|
||||
@ -74,6 +77,9 @@ namespace vnotex
|
||||
QStringList m_mainWindowKeepDocksExpandingContentArea;
|
||||
|
||||
bool m_snippetPanelBuiltInSnippetsVisible = true;
|
||||
|
||||
// Whether enable two columns for tag explorer.
|
||||
bool m_tagExplorerTwoColumnsEnabled = false;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,9 @@
|
||||
<file>icons/read_editor.svg</file>
|
||||
<file>icons/expand.svg</file>
|
||||
<file>icons/fullscreen.svg</file>
|
||||
<file>icons/tag_explorer.svg</file>
|
||||
<file>icons/tag_dock.svg</file>
|
||||
<file>icons/tag.svg</file>
|
||||
<file>icons/tag_selected.svg</file>
|
||||
<file>icons/help.svg</file>
|
||||
<file>icons/menu.svg</file>
|
||||
<file>icons/settings.svg</file>
|
||||
@ -34,7 +36,7 @@
|
||||
<file>icons/file_node.svg</file>
|
||||
<file>icons/folder_node.svg</file>
|
||||
<file>icons/manage_notebooks.svg</file>
|
||||
<file>icons/up_parent_node.svg</file>
|
||||
<file>icons/up_level.svg</file>
|
||||
<file>icons/properties.svg</file>
|
||||
<file>icons/recycle_bin.svg</file>
|
||||
<file>icons/scan_import.svg</file>
|
||||
@ -43,6 +45,7 @@
|
||||
<file>icons/buffer.svg</file>
|
||||
<file>icons/attachment_editor.svg</file>
|
||||
<file>icons/attachment_full_editor.svg</file>
|
||||
<file>icons/tag_editor.svg</file>
|
||||
<file>icons/split_menu.svg</file>
|
||||
<file>icons/split_window_list.svg</file>
|
||||
<file>icons/type_heading_editor.svg</file>
|
||||
|
@ -1,10 +1 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="512px" height="512px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
|
||||
<g>
|
||||
<polygon style="fill:#000000" points="288,448 288,192 192,192 192,208 224,208 224,448 192,448 192,464 320,464 320,448 "/>
|
||||
<path style="fill:#000000" d="M255.8,144.5c26.6,0,48.2-21.6,48.2-48.2s-21.6-48.2-48.2-48.2c-26.6,0-48.2,21.6-48.2,48.2S229.2,144.5,255.8,144.5z"/>
|
||||
</g>
|
||||
</svg>
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1633945130444" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5939" width="512" height="512" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"></style></defs><path d="M512 0C229.23 0 0 229.23 0 512s229.23 512 512 512 512-229.23 512-512S794.77 0 512 0zM512 928c-229.75 0-416-186.25-416-416S282.25 96 512 96s416 186.25 416 416S741.75 928 512 928z" p-id="5940" fill="#000000"></path><path d="M537.64 343.452c47.074 0 83.266-37.528 83.266-78.072 0-32.46-20.832-60.878-62.496-60.878-54.816 0-82.178 44.618-82.178 77.11C475.144 320.132 498.152 343.452 537.64 343.452z" p-id="5941" fill="#000000"></path><path d="M533.162 728.934c-7.648 0-10.914-10.136-3.264-39.55l43.25-166.406c16.386-60.848 10.944-100.398-21.92-100.398-39.456 0-131.458 39.83-211.458 107.798l16.416 27.392c25.246-17.256 67.906-34.762 77.792-34.762 7.648 0 6.56 10.168 0 35.508l-37.746 158.292c-23.008 89.266 1.088 109.538 33.984 109.538 32.864 0 117.808-30.47 195.57-109.632l-18.656-25.34C575.354 716.714 543.05 728.934 533.162 728.934z" p-id="5942" fill="#000000"></path></svg>
|
Before Width: | Height: | Size: 765 B After Width: | Height: | Size: 1.2 KiB |
@ -1,10 +1 @@
|
||||
<svg width="512" height="512" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<g display="inline">
|
||||
<title>Layer 1</title>
|
||||
<path stroke="#000000" fill="#000000" stroke-width="30" stroke-dasharray="null" stroke-linejoin="null" stroke-linecap="null" d="m85.750002,73.846584l43.194363,360.590916l241.122963,0l43.057672,-361.684444l-327.374998,1.093528z" fill-opacity="0" id="svg_6"/>
|
||||
</g>
|
||||
<g display="inline">
|
||||
<title>Layer 1 copy</title>
|
||||
<path id="svg_1" d="m278.734806,211.081489l-25.954577,-4.596678l9.562217,-4.993052c5.259196,-2.746226 10.027709,-5.404751 10.596662,-5.907837c1.413302,-1.249639 -16.187262,-28.447523 -18.394639,-28.425008c-0.970333,0.011001 -7.731781,10.542547 -15.025414,23.405944c-7.346415,12.956287 -14.657734,22.823491 -16.392375,22.122467c-20.530561,-8.296065 -37.281946,-16.689857 -37.281946,-18.681204c0,-1.360192 6.441626,-13.493725 14.314808,-26.963432l14.314815,-24.490333l39.029834,0l39.029834,0l10.187868,16.112893l10.187953,16.112893l10.628269,-5.452618c7.367742,-3.779934 10.17318,-4.322708 9.144661,-1.769575c-9.05562,22.481975 -22.568035,48.696355 -24.969931,48.442064c-1.662917,-0.175921 -14.703002,-2.388389 -28.978031,-4.916514l-0.000008,-0.000008zm-121.04853,100.301702c-9.877208,-17.009348 -18.729478,-32.934982 -19.671623,-35.390403c-0.966907,-2.519616 1.850192,-11.523045 6.467384,-20.669546l8.180422,-16.205438l-8.362438,-4.939858c-11.12868,-6.573858 -6.992066,-8.598561 22.529168,-11.027127l23.331407,-1.919317l9.151668,24.180839c5.033491,13.299451 9.795749,25.898384 10.582909,27.997793c0.820922,2.189446 -3.107919,0.732693 -9.21387,-3.416409l-10.645019,-7.233539l-7.43104,14.344659c-4.086999,7.889694 -7.488988,15.223935 -7.559854,16.298312c-0.071034,1.074377 12.472696,2.303847 27.874729,2.732066l28.003626,0.778551l1.827516,20.484288c1.005176,11.266424 1.619826,20.634873 1.366026,20.818805c-0.253785,0.183748 -13.513894,1.179924 -29.466873,2.213472l-29.005467,1.879108l-17.958669,-30.926279l0,0.000023zm110.540841,30.053665c-16.956491,-25.592907 -17.251221,-23.689252 7.951467,-51.353217l16.178415,-17.758401l-1.718289,12.977292l-1.718366,12.9773l18.129745,0c9.97134,0 18.129837,-0.797417 18.129837,-1.771913c0,-0.974657 -5.532483,-11.921252 -12.294352,-24.32608c-6.761869,-12.404828 -12.268679,-23.594376 -12.237409,-24.86568c0.070206,-2.850622 35.51503,-26.389649 37.20008,-24.704623c0.667669,0.667677 7.245506,12.559246 14.617434,26.425987l13.403679,25.21188l-18.035023,29.09066c-9.91941,15.999665 -19.449767,30.651136 -21.178467,32.558624c-1.728899,1.907488 -11.395904,4.238849 -21.482319,5.180634c-19.252488,1.797909 -18.728389,1.364868 -21.551245,17.805095c-0.493994,2.877139 -6.254444,-3.65119 -15.395187,-17.447572l0,0.000015z" stroke-linecap="null" stroke-linejoin="null" stroke-dasharray="null" stroke-width="50" fill="#000000"/>
|
||||
</g>
|
||||
</svg>
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1633928568811" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6018" width="512" height="512" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"></style></defs><path d="M682.666667 260.266667L661.333333 128H362.666667l-21.333334 132.266667-42.666666-6.4 25.6-149.333334c2.133333-10.666667 10.666667-17.066667 21.333333-17.066666h332.8c10.666667 0 19.2 8.533333 21.333333 17.066666l25.6 149.333334-42.666666 6.4z" p-id="6019" fill="#000000"></path><path d="M896 277.333333H128c-12.8 0-21.333333-8.533333-21.333333-21.333333s8.533333-21.333333 21.333333-21.333333h768c12.8 0 21.333333 8.533333 21.333333 21.333333s-8.533333 21.333333-21.333333 21.333333z" p-id="6020" fill="#000000"></path><path d="M746.666667 938.666667H277.333333c-10.666667 0-21.333333-8.533333-21.333333-19.2l-42.666667-661.333334c0-6.4 2.133333-10.666667 6.4-14.933333s8.533333-8.533333 14.933334-8.533333h554.666666c6.4 0 10.666667 2.133333 14.933334 6.4s6.4 10.666667 6.4 14.933333l-42.666667 661.333333c0 12.8-10.666667 21.333333-21.333333 21.333334z m-450.133334-42.666667H725.333333l40.533334-618.666667H258.133333l38.4 618.666667z" p-id="6021" fill="#000000"></path><path d="M584.533333 537.6c-6.4 0-14.933333-4.266667-19.2-10.666667L512 433.066667l-36.266667 66.133333c-6.4 10.666667-19.2 14.933333-29.866666 8.533333-10.666667-6.4-14.933333-19.2-8.533334-29.866666l40.533334-74.666667c6.4-10.666667 19.2-19.2 32-19.2s25.6 6.4 32 19.2l59.733333 102.4c6.4 10.666667 2.133333 23.466667-8.533333 29.866667 0 0-4.266667 2.133333-8.533334 2.133333zM646.4 695.466667h-125.866667c-12.8 0-21.333333-8.533333-21.333333-21.333334s8.533333-21.333333 21.333333-21.333333h115.2l-40.533333-68.266667c-6.4-10.666667-2.133333-23.466667 6.4-29.866666 10.666667-6.4 23.466667-2.133333 29.866667 6.4l46.933333 76.8c6.4 10.666667 6.4 25.6 0 36.266666-6.4 12.8-19.2 21.333333-32 21.333334zM458.666667 695.466667h-81.066667c-12.8 0-25.6-6.4-32-19.2s-6.4-25.6 0-36.266667l64-106.666667c6.4-10.666667 19.2-12.8 29.866667-8.533333 10.666667 6.4 12.8 19.2 8.533333 29.866667l-57.6 98.133333h70.4c12.8 0 21.333333 8.533333 21.333333 21.333333s-12.8 21.333333-23.466666 21.333334z" p-id="6022" fill="#000000"></path><path d="M550.4 725.333333c-6.4 0-10.666667-2.133333-14.933333-6.4l-29.866667-29.866666c-8.533333-8.533333-8.533333-21.333333 0-29.866667l29.866667-29.866667c8.533333-8.533333 21.333333-8.533333 29.866666 0 8.533333 8.533333 8.533333 21.333333 0 29.866667l-14.933333 14.933333 14.933333 14.933334c8.533333 8.533333 8.533333 21.333333 0 29.866666-4.266667 4.266667-8.533333 6.4-14.933333 6.4zM584.533333 537.6h-6.4l-42.666666-10.666667c-10.666667-2.133333-19.2-14.933333-14.933334-25.6 2.133333-10.666667 14.933333-17.066667 25.6-14.933333l21.333334 6.4 6.4-21.333333c2.133333-10.666667 14.933333-17.066667 25.6-14.933334 10.666667 2.133333 17.066667 14.933333 14.933333 25.6l-10.666667 42.666667c-2.133333 6.4-4.266667 10.666667-10.666666 12.8h-8.533334zM439.466667 605.866667c-8.533333 0-17.066667-6.4-21.333334-14.933334l-6.4-21.333333-21.333333 6.4c-10.666667 2.133333-23.466667-4.266667-25.6-14.933333-2.133333-10.666667 4.266667-23.466667 14.933333-25.6l42.666667-10.666667c6.4-2.133333 10.666667 0 17.066667 2.133333 4.266667 2.133333 8.533333 8.533333 10.666666 12.8l10.666667 42.666667c2.133333 10.666667-4.266667 23.466667-14.933333 25.6-4.266667-2.133333-4.266667-2.133333-6.4-2.133333z" p-id="6023" fill="#000000"></path></svg>
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 3.5 KiB |
@ -1,8 +1 @@
|
||||
<svg width="512" height="512" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<!-- Created with SVG-edit - http://svg-edit.googlecode.com/ -->
|
||||
<g>
|
||||
<title>Layer 2</title>
|
||||
<text fill="#000000" stroke-width="0" x="50.16984" y="94.53242" id="svg_1" font-size="24" font-family="Sans-serif" text-anchor="middle" xml:space="preserve" transform="matrix(14.638787563577418,0,0,15.425402876908336,-563.6705867690341,-1147.819919301283) " stroke="#000000">A</text>
|
||||
<text id="svg_3" fill="#000000" stroke-width="0" x="42.10906" y="100.56143" font-size="24" font-family="Sans-serif" text-anchor="middle" xml:space="preserve" transform="matrix(14.638787563577418,0,0,15.425402876908336,-251.02721518700292,-1086.2459218037245) " stroke="#000000">Z</text>
|
||||
</g>
|
||||
</svg>
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1633747200250" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5795" width="512" height="512" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"></style></defs><path d="M426.666667 554.666667 426.666667 469.333333 768 469.333333 768 554.666667 426.666667 554.666667M426.666667 810.666667 426.666667 725.333333 597.333333 725.333333 597.333333 810.666667 426.666667 810.666667M426.666667 298.666667 426.666667 213.333333 938.666667 213.333333 938.666667 298.666667 426.666667 298.666667M256 725.333333 362.666667 725.333333 213.333333 874.666667 64 725.333333 170.666667 725.333333 170.666667 298.666667 64 298.666667 213.333333 149.333333 362.666667 298.666667 256 298.666667 256 725.333333Z" p-id="5796" fill="#000000"></path></svg>
|
Before Width: | Height: | Size: 789 B After Width: | Height: | Size: 941 B |
Before Width: | Height: | Size: 947 B After Width: | Height: | Size: 947 B |
12
src/data/core/icons/tag_dock.svg
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="512px" height="512px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve">
|
||||
<g>
|
||||
<path fill="#000000" d="M448,64V32H288L32,320l160,160l23.471-23.904L240,480l240-272V64H448z M192,457.371L54.39,320L294.621,48H432v16v16
|
||||
v105.377l-216.555,247.99l-11.34,11.363L192,457.371z M464,201.377L240,457.371l-13.182-12.65L448,192V80h16V201.377z"/>
|
||||
<path fill="#000000" d="M352,160c17.645,0,32-14.355,32-32s-14.355-32-32-32s-32,14.355-32,32S334.355,160,352,160z M352,112
|
||||
c8.836,0,16,7.163,16,16s-7.164,16-16,16s-16-7.163-16-16S343.164,112,352,112z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 947 B |
12
src/data/core/icons/tag_editor.svg
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="512px" height="512px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve">
|
||||
<g>
|
||||
<path fill="#000000" d="M448,64V32H288L32,320l160,160l23.471-23.904L240,480l240-272V64H448z M192,457.371L54.39,320L294.621,48H432v16v16
|
||||
v105.377l-216.555,247.99l-11.34,11.363L192,457.371z M464,201.377L240,457.371l-13.182-12.65L448,192V80h16V201.377z"/>
|
||||
<path fill="#000000" d="M352,160c17.645,0,32-14.355,32-32s-14.355-32-32-32s-32,14.355-32,32S334.355,160,352,160z M352,112
|
||||
c8.836,0,16,7.163,16,16s-7.164,16-16,16s-16-7.163-16-16S343.164,112,352,112z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 947 B |
1
src/data/core/icons/tag_selected.svg
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1634127135446" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6562" width="512" height="512" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"></style></defs><path d="M928 128 928 402.754 454.306 934.96 480 960 960 416 960 128Z" p-id="6563" fill="#000000"></path><path d="M576 64 64 640l320 320 46.942-47.808 22.696-22.75L896 384 896 160 896 128 896 64 576 64zM704 320c-35.29 0-64-28.71-64-64s28.71-64 64-64 64 28.71 64 64S739.29 320 704 320z" p-id="6564" fill="#000000"></path><path d="M704 256m-32 0a16 16 0 1 0 64 0 16 16 0 1 0-64 0Z" p-id="6565" fill="#000000"></path></svg>
|
After Width: | Height: | Size: 788 B |
Before Width: | Height: | Size: 647 B After Width: | Height: | Size: 647 B |
@ -1,15 +1 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="512px" height="512px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
|
||||
<g>
|
||||
<path fill="#000000" d="M256,128c-81.9,0-145.7,48.8-224,128c67.4,67.7,124,128,224,128c99.9,0,173.4-76.4,224-126.6
|
||||
C428.2,198.6,354.8,128,256,128z M256,347.3c-49.4,0-89.6-41-89.6-91.3c0-50.4,40.2-91.3,89.6-91.3s89.6,41,89.6,91.3
|
||||
C345.6,306.4,305.4,347.3,256,347.3z"/>
|
||||
<g>
|
||||
<path fill="#000000" d="M256,224c0-7.9,2.9-15.1,7.6-20.7c-2.5-0.4-5-0.6-7.6-0.6c-28.8,0-52.3,23.9-52.3,53.3c0,29.4,23.5,53.3,52.3,53.3
|
||||
s52.3-23.9,52.3-53.3c0-2.3-0.2-4.6-0.4-6.9c-5.5,4.3-12.3,6.9-19.8,6.9C270.3,256,256,241.7,256,224z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1633747385916" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="14318" width="512" height="512" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"></style></defs><path d="M332.256 162.432c-12-4.992-25.728-2.24-34.88 6.944l-192 192c-12.512 12.512-12.512 32.736 0 45.248s32.736 12.512 45.248 0L288 269.248 288 800c0 17.696 14.336 32 32 32s32-14.304 32-32L352 192C352 179.072 344.192 167.392 332.256 162.432z" p-id="14319" fill="#000000"></path><path d="M918.624 617.376c-12.512-12.512-32.736-12.512-45.248 0L768 722.752 768 192c0-17.664-14.304-32-32-32s-32 14.336-32 32l0 608c0 12.928 7.776 24.64 19.744 29.568C727.712 831.232 731.872 832 736 832c8.32 0 16.512-3.264 22.624-9.376l160-160C931.136 650.112 931.136 629.888 918.624 617.376z" p-id="14320" fill="#000000"></path></svg>
|
Before Width: | Height: | Size: 1018 B After Width: | Height: | Size: 984 B |
@ -23,6 +23,7 @@
|
||||
"SnippetDock" : "Ctrl+G, S",
|
||||
"LocationListDock" : "Ctrl+G, C",
|
||||
"HistoryDock" : "",
|
||||
"TagDock" : "",
|
||||
"Search" : "Ctrl+Alt+F",
|
||||
"NavigationMode" : "Ctrl+G, W",
|
||||
"LocateNode" : "Ctrl+G, D",
|
||||
@ -117,7 +118,8 @@
|
||||
"FindAndReplace" : "Ctrl+F",
|
||||
"FindNext" : "F3",
|
||||
"FindPrevious" : "Shift+F3",
|
||||
"ApplySnippet" : "Ctrl+G, I"
|
||||
"ApplySnippet" : "Ctrl+G, I",
|
||||
"Tag" : "Ctrl+G, B"
|
||||
},
|
||||
"spell_check_auto_detect_language" : false,
|
||||
"spell_check_default_dictionary" : "en_US",
|
||||
@ -379,6 +381,7 @@
|
||||
"search_panel_advanced_settings_visible" : true,
|
||||
"//comment" : "Docks to ignore when expanding content area of main window",
|
||||
"main_window_keep_docks_expanding_content_area": ["OutlineDock.vnotex"],
|
||||
"snippet_panel_builtin_snippets_visible" : true
|
||||
"snippet_panel_builtin_snippets_visible" : true,
|
||||
"tag_explorer_two_columns_enabled" : true
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
1. All the keys without special notice are **case insensitive**;
|
||||
2. On macOS, `Ctrl` corresponds to `Command` except in Vi mode;
|
||||
3. The key sequence `Ctrl+G, I` means that first press both `Ctrl` and `G` simultaneously, release them, then press `I` and release;
|
||||
4. For a **complete shortcuts list**, please view the `vnotex.json` configuration file.
|
||||
4. For a **complete latest shortcuts list**, please view the `vnotex.json` configuration file.
|
||||
|
||||
## General
|
||||
- `Ctrl+G E`
|
||||
@ -101,10 +101,10 @@ Insert italic. Press `Ctrl+I` again to exit. Current selected text will be chang
|
||||
Insert inline code. Press `Ctrl+;` again to exit. Current selected text will be changed to inline code if exists.
|
||||
- `Ctrl+'`
|
||||
Insert fenced code block. Press `Ctrl+'` again to exit. Current selected text will be wrapped into a code block if exists.
|
||||
- `Ctrl+,`
|
||||
Insert inline math. Press `Ctrl+,` again to exit. Current selected text will be changed to inline math if exists.
|
||||
- `Ctrl+.`
|
||||
Insert math block. Press `Ctrl+.` again to exit. Current selected text will be changed to math block if exists.
|
||||
Insert inline math. Press `Ctrl+.` again to exit. Current selected text will be changed to inline math if exists.
|
||||
- `Ctrl+G, .`
|
||||
Insert math block. Press `Ctrl+G, .` again to exit. Current selected text will be changed to math block if exists.
|
||||
- `Ctrl+/`
|
||||
Insert table.
|
||||
- `Ctrl+<Num>`
|
||||
|
@ -2,7 +2,7 @@
|
||||
1. 以下按键除特别说明外,都不区分大小写;
|
||||
2. 在 macOS 下,`Ctrl` 对应于 `Command`,在 Vi 模式下除外;
|
||||
3. 按键序列 `Ctrl+G, I` 表示先同时按下 `Ctrl` 和 `G`,释放,然后按下 `I` 并释放;
|
||||
4. 可以通过查看配置文件 `vnotex.json` 来获取一个**完整的快捷键列表**。
|
||||
4. 可以通过查看配置文件 `vnotex.json` 来获取一个**完整的最新的快捷键列表**。
|
||||
|
||||
## 通用
|
||||
- `Ctrl+G E`
|
||||
@ -101,10 +101,10 @@ VNote 的很多部件均支持`Ctrl+J`和`Ctrl+K`导航。
|
||||
插入行内代码;再次按`Ctrl+;`退出。如果已经选择文本,则将当前选择文本改为行内代码。
|
||||
- `Ctrl+'`
|
||||
插入代码块;再次按`Ctrl+'`退出。如果已经选择文本,则将当前选择文本嵌入到代码块中。
|
||||
- `Ctrl+,`
|
||||
插入公式;再次按`Ctrl+,`退出。如果已经选择文本,则将当前选择文本改为公式。
|
||||
- `Ctrl+.`
|
||||
插入公式块;再次按`Ctrl+.`退出。如果已经选择文本,则将当前选择文本改为公式块。
|
||||
插入公式;再次按`Ctrl+.`退出。如果已经选择文本,则将当前选择文本改为公式。
|
||||
- `Ctrl+G, .`
|
||||
插入公式块;再次按`Ctrl+G, .`退出。如果已经选择文本,则将当前选择文本改为公式块。
|
||||
- `Ctrl+/`
|
||||
插入表格。
|
||||
- `Ctrl+<Num>`
|
||||
|
@ -48,7 +48,7 @@ int main(int argc, char *argv[])
|
||||
#if defined(Q_OS_WIN)
|
||||
{
|
||||
auto option = SessionConfig::getOpenGLAtBootstrap();
|
||||
qInfo() << "OpenGL option" << SessionConfig::openGLToString(option);
|
||||
qDebug() << "OpenGL option" << SessionConfig::openGLToString(option);
|
||||
switch (option) {
|
||||
case SessionConfig::OpenGL::Desktop:
|
||||
QCoreApplication::setAttribute(Qt::AA_UseDesktopOpenGL);
|
||||
|
@ -470,6 +470,11 @@ bool Searcher::firstPhaseSearch(Notebook *p_notebook, QVector<SearchSecondPhaseI
|
||||
return true;
|
||||
}
|
||||
|
||||
if (p_notebook->isRecycleBinNode(child.data())) {
|
||||
qDebug() << "skipped searching recycle bin";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (child->hasContent() && testTarget(SearchTarget::SearchFile)) {
|
||||
if (!firstPhaseSearch(child.data(), p_secondPhaseItems)) {
|
||||
return false;
|
||||
|
@ -4,8 +4,6 @@
|
||||
#include <QFileInfo>
|
||||
#include <QRegularExpression>
|
||||
#include <QImageReader>
|
||||
#include <QValidator>
|
||||
#include <QRegularExpressionValidator>
|
||||
|
||||
using namespace vnotex;
|
||||
|
||||
@ -138,13 +136,12 @@ bool PathUtils::isLegalPath(const QString &p_path)
|
||||
}
|
||||
|
||||
bool ret = false;
|
||||
int pos = -1;
|
||||
QString basePath = parentDirPath(p_path);
|
||||
QString name = dirName(p_path);
|
||||
QScopedPointer<QValidator> validator(new QRegularExpressionValidator(QRegularExpression(c_fileNameRegularExpression)));
|
||||
QRegularExpression nameRegExp(c_fileNameRegularExpression);
|
||||
while (!name.isEmpty()) {
|
||||
QValidator::State validFile = validator->validate(name, pos);
|
||||
if (validFile != QValidator::Acceptable) {
|
||||
auto match = nameRegExp.match(name);
|
||||
if (!match.hasMatch()) {
|
||||
break;
|
||||
}
|
||||
|
||||
|
80
src/widgets/dialogs/levellabelwithupbutton.cpp
Normal file
@ -0,0 +1,80 @@
|
||||
#include "levellabelwithupbutton.h"
|
||||
|
||||
#include <QPushButton>
|
||||
#include <QLabel>
|
||||
#include <QHBoxLayout>
|
||||
|
||||
#include <utils/iconutils.h>
|
||||
#include <core/vnotex.h>
|
||||
|
||||
using namespace vnotex;
|
||||
|
||||
LevelLabelWithUpButton::LevelLabelWithUpButton(QWidget *p_parent)
|
||||
: QWidget(p_parent)
|
||||
{
|
||||
setupUI();
|
||||
}
|
||||
|
||||
void LevelLabelWithUpButton::setupUI()
|
||||
{
|
||||
auto mainLayout = new QHBoxLayout(this);
|
||||
mainLayout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
m_label = new QLabel(this);
|
||||
mainLayout->addWidget(m_label, 1);
|
||||
|
||||
const auto iconFile = VNoteX::getInst().getThemeMgr().getIconFile("up_level.svg");
|
||||
m_upButton = new QPushButton(IconUtils::fetchIconWithDisabledState(iconFile),
|
||||
tr("Up"),
|
||||
this);
|
||||
m_upButton->setToolTip(tr("Go one level up"));
|
||||
connect(m_upButton, &QPushButton::clicked,
|
||||
this, [this]() {
|
||||
if (m_levelIdx < m_levels.size() - 1) {
|
||||
++m_levelIdx;
|
||||
updateLabelAndButton();
|
||||
emit levelChanged();
|
||||
}
|
||||
});
|
||||
mainLayout->addWidget(m_upButton, 0);
|
||||
|
||||
updateLabelAndButton();
|
||||
}
|
||||
|
||||
void LevelLabelWithUpButton::updateLabelAndButton()
|
||||
{
|
||||
if (m_levels.isEmpty()) {
|
||||
m_label->clear();
|
||||
} else {
|
||||
Q_ASSERT(m_levelIdx < m_levels.size());
|
||||
m_label->setText(m_levels[m_levelIdx].m_name);
|
||||
}
|
||||
|
||||
m_upButton->setVisible(!m_readOnly && (m_levelIdx < m_levels.size() - 1));
|
||||
}
|
||||
|
||||
const LevelLabelWithUpButton::Level &LevelLabelWithUpButton::getLevel() const
|
||||
{
|
||||
Q_ASSERT(m_levelIdx < m_levels.size());
|
||||
return m_levels[m_levelIdx];
|
||||
}
|
||||
|
||||
void LevelLabelWithUpButton::setLevels(const QVector<Level> &p_levels)
|
||||
{
|
||||
m_levels = p_levels;
|
||||
Q_ASSERT(!m_levels.isEmpty());
|
||||
m_levelIdx = 0;
|
||||
|
||||
updateLabelAndButton();
|
||||
emit levelChanged();
|
||||
}
|
||||
|
||||
void LevelLabelWithUpButton::setReadOnly(bool p_readonly)
|
||||
{
|
||||
if (m_readOnly == p_readonly) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_readOnly = p_readonly;
|
||||
updateLabelAndButton();
|
||||
}
|
52
src/widgets/dialogs/levellabelwithupbutton.h
Normal file
@ -0,0 +1,52 @@
|
||||
#ifndef LEVELLABELWITHUPBUTTON_H
|
||||
#define LEVELLABELWITHUPBUTTON_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class QLabel;
|
||||
class QPushButton;
|
||||
|
||||
namespace vnotex
|
||||
{
|
||||
// Used to navigate through a series of levels.
|
||||
class LevelLabelWithUpButton : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
struct Level
|
||||
{
|
||||
QString m_name;
|
||||
|
||||
const void *m_data = nullptr;
|
||||
};
|
||||
|
||||
LevelLabelWithUpButton(QWidget *p_parent = nullptr);
|
||||
|
||||
const Level &getLevel() const;
|
||||
|
||||
// From bottom to up.
|
||||
void setLevels(const QVector<Level> &p_levels);
|
||||
|
||||
void setReadOnly(bool p_readonly);
|
||||
|
||||
signals:
|
||||
void levelChanged();
|
||||
|
||||
private:
|
||||
void setupUI();
|
||||
|
||||
void updateLabelAndButton();
|
||||
|
||||
QLabel *m_label = nullptr;
|
||||
|
||||
QPushButton *m_upButton = nullptr;
|
||||
|
||||
QVector<Level> m_levels;
|
||||
|
||||
int m_levelIdx = -1;
|
||||
|
||||
bool m_readOnly = false;
|
||||
};
|
||||
} // ns vnotex
|
||||
|
||||
#endif // LEVELLABELWITHUPBUTTON_H
|
@ -2,11 +2,12 @@
|
||||
|
||||
#include <QLineEdit>
|
||||
|
||||
#include "notebook/notebook.h"
|
||||
#include "notebook/node.h"
|
||||
#include "../widgetsfactory.h"
|
||||
#include <notebook/notebook.h>
|
||||
#include <notebook/node.h>
|
||||
#include <utils/pathutils.h>
|
||||
#include "exception.h"
|
||||
#include <core/exception.h>
|
||||
|
||||
#include "../widgetsfactory.h"
|
||||
#include "nodeinfowidget.h"
|
||||
|
||||
using namespace vnotex;
|
||||
|
104
src/widgets/dialogs/newtagdialog.cpp
Normal file
@ -0,0 +1,104 @@
|
||||
#include "newtagdialog.h"
|
||||
|
||||
#include <QFormLayout>
|
||||
|
||||
#include <notebook/tagi.h>
|
||||
|
||||
#include "../widgetsfactory.h"
|
||||
#include "levellabelwithupbutton.h"
|
||||
#include "../lineeditwithsnippet.h"
|
||||
|
||||
using namespace vnotex;
|
||||
|
||||
NewTagDialog::NewTagDialog(TagI *p_tagI, Tag *p_tag, QWidget *p_parent)
|
||||
: ScrollDialog(p_parent),
|
||||
m_tagI(p_tagI),
|
||||
m_parentTag(p_tag)
|
||||
{
|
||||
setupUI();
|
||||
|
||||
m_nameLineEdit->setFocus();
|
||||
}
|
||||
|
||||
static QVector<LevelLabelWithUpButton::Level> tagToLevels(const Tag *p_tag)
|
||||
{
|
||||
QVector<LevelLabelWithUpButton::Level> levels;
|
||||
while (p_tag) {
|
||||
LevelLabelWithUpButton::Level level;
|
||||
level.m_name = p_tag->fetchPath();
|
||||
level.m_data = static_cast<const void *>(p_tag);
|
||||
levels.push_back(level);
|
||||
p_tag = p_tag->getParent();
|
||||
}
|
||||
|
||||
// Append an empty level.
|
||||
levels.push_back(LevelLabelWithUpButton::Level());
|
||||
|
||||
return levels;
|
||||
}
|
||||
|
||||
void NewTagDialog::setupUI()
|
||||
{
|
||||
auto mainWidget = new QWidget(this);
|
||||
setCentralWidget(mainWidget);
|
||||
|
||||
auto mainLayout = WidgetsFactory::createFormLayout(mainWidget);
|
||||
|
||||
{
|
||||
m_parentTagLabel = new LevelLabelWithUpButton(this);
|
||||
m_parentTagLabel->setLevels(tagToLevels(m_parentTag));
|
||||
mainLayout->addRow(tr("Location:"), m_parentTagLabel);
|
||||
}
|
||||
|
||||
m_nameLineEdit = WidgetsFactory::createLineEditWithSnippet(mainWidget);
|
||||
mainLayout->addRow(tr("Name:"), m_nameLineEdit);
|
||||
|
||||
setDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
|
||||
setWindowTitle(tr("New Tag"));
|
||||
}
|
||||
|
||||
bool NewTagDialog::validateInputs()
|
||||
{
|
||||
bool valid = true;
|
||||
QString msg;
|
||||
|
||||
auto name = getTagName();
|
||||
if (!Tag::isValidName(name)) {
|
||||
valid = false;
|
||||
msg = tr("Please specify a valid name for the tag.");
|
||||
} else if (m_tagI->findTag(name)) {
|
||||
valid = false;
|
||||
msg = tr("Name conflicts with existing tag.");
|
||||
}
|
||||
|
||||
setInformationText(msg, valid ? ScrollDialog::InformationLevel::Info
|
||||
: ScrollDialog::InformationLevel::Error);
|
||||
return valid;
|
||||
}
|
||||
|
||||
bool NewTagDialog::newTag()
|
||||
{
|
||||
const Tag *parentTag = static_cast<const Tag *>(m_parentTagLabel->getLevel().m_data);
|
||||
const auto parentName = parentTag ? parentTag->name() : QString();
|
||||
const auto name = getTagName();
|
||||
if (!m_tagI->newTag(name, parentName)) {
|
||||
setInformationText(tr("Failed to create tag (%1).").arg(name), ScrollDialog::InformationLevel::Error);
|
||||
// Tags maybe updated. Don't allow operation for now.
|
||||
setButtonEnabled(QDialogButtonBox::Ok, false);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void NewTagDialog::acceptedButtonClicked()
|
||||
{
|
||||
if (validateInputs() && newTag()) {
|
||||
accept();
|
||||
}
|
||||
}
|
||||
|
||||
QString NewTagDialog::getTagName() const
|
||||
{
|
||||
return m_nameLineEdit->evaluatedText();
|
||||
}
|
42
src/widgets/dialogs/newtagdialog.h
Normal file
@ -0,0 +1,42 @@
|
||||
#ifndef NEWTAGDIALOG_H
|
||||
#define NEWTAGDIALOG_H
|
||||
|
||||
#include "scrolldialog.h"
|
||||
|
||||
namespace vnotex
|
||||
{
|
||||
class TagI;
|
||||
class Tag;
|
||||
class LevelLabelWithUpButton;
|
||||
class LineEditWithSnippet;
|
||||
|
||||
class NewTagDialog : public ScrollDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
// New a tag under @p_tag.
|
||||
NewTagDialog(TagI *p_tagI, Tag *p_tag, QWidget *p_parent = nullptr);
|
||||
|
||||
protected:
|
||||
void acceptedButtonClicked() Q_DECL_OVERRIDE;
|
||||
|
||||
private:
|
||||
void setupUI();
|
||||
|
||||
bool validateInputs();
|
||||
|
||||
bool newTag();
|
||||
|
||||
QString getTagName() const;
|
||||
|
||||
TagI *m_tagI = nullptr;
|
||||
|
||||
Tag *m_parentTag = nullptr;
|
||||
|
||||
LevelLabelWithUpButton *m_parentTagLabel = nullptr;
|
||||
|
||||
LineEditWithSnippet *m_nameLineEdit = nullptr;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // NEWTAGDIALOG_H
|
@ -10,7 +10,7 @@
|
||||
#include <utils/pathutils.h>
|
||||
#include <utils/utils.h>
|
||||
#include "exception.h"
|
||||
#include "nodelabelwithupbutton.h"
|
||||
#include "levellabelwithupbutton.h"
|
||||
#include <utils/widgetutils.h>
|
||||
#include <buffer/filetypehelper.h>
|
||||
#include "../lineeditwithsnippet.h"
|
||||
@ -35,6 +35,20 @@ NodeInfoWidget::NodeInfoWidget(const Node *p_parentNode,
|
||||
setupUI(p_parentNode, p_flags);
|
||||
}
|
||||
|
||||
static QVector<LevelLabelWithUpButton::Level> nodeToLevels(const Node *p_node)
|
||||
{
|
||||
QVector<LevelLabelWithUpButton::Level> levels;
|
||||
while (p_node) {
|
||||
LevelLabelWithUpButton::Level level;
|
||||
level.m_name = p_node->fetchPath();
|
||||
level.m_data = static_cast<const void *>(p_node);
|
||||
levels.push_back(level);
|
||||
p_node = p_node->getParent();
|
||||
}
|
||||
|
||||
return levels;
|
||||
}
|
||||
|
||||
void NodeInfoWidget::setupUI(const Node *p_parentNode, Node::Flags p_newNodeFlags)
|
||||
{
|
||||
const bool createMode = m_mode == Mode::Create;
|
||||
@ -45,11 +59,14 @@ void NodeInfoWidget::setupUI(const Node *p_parentNode, Node::Flags p_newNodeFlag
|
||||
m_mainLayout->addRow(tr("Notebook:"),
|
||||
new QLabel(p_parentNode->getNotebook()->getName(), this));
|
||||
|
||||
m_parentNodeLabel = new NodeLabelWithUpButton(p_parentNode, this);
|
||||
m_parentNodeLabel->setReadOnly(!createMode);
|
||||
connect(m_parentNodeLabel, &NodeLabelWithUpButton::nodeChanged,
|
||||
this, &NodeInfoWidget::inputEdited);
|
||||
m_mainLayout->addRow(tr("Location:"), m_parentNodeLabel);
|
||||
{
|
||||
m_parentNodeLabel = new LevelLabelWithUpButton(this);
|
||||
m_parentNodeLabel->setReadOnly(!createMode);
|
||||
m_parentNodeLabel->setLevels(nodeToLevels(p_parentNode));
|
||||
connect(m_parentNodeLabel, &LevelLabelWithUpButton::levelChanged,
|
||||
this, &NodeInfoWidget::inputEdited);
|
||||
m_mainLayout->addRow(tr("Location:"), m_parentNodeLabel);
|
||||
}
|
||||
|
||||
if (createMode && isNote) {
|
||||
setupFileTypeComboBox(this);
|
||||
@ -115,7 +132,7 @@ const Notebook *NodeInfoWidget::getNotebook() const
|
||||
|
||||
const Node *NodeInfoWidget::getParentNode() const
|
||||
{
|
||||
return m_parentNodeLabel->getNode();
|
||||
return static_cast<const Node *>(m_parentNodeLabel->getLevel().m_data);
|
||||
}
|
||||
|
||||
void NodeInfoWidget::setNode(const Node *p_node)
|
||||
@ -129,7 +146,7 @@ void NodeInfoWidget::setNode(const Node *p_node)
|
||||
if (m_node) {
|
||||
Q_ASSERT(getNotebook() == m_node->getNotebook());
|
||||
m_nameLineEdit->setText(m_node->getName());
|
||||
m_parentNodeLabel->setNode(m_node->getParent());
|
||||
m_parentNodeLabel->setLevels(nodeToLevels(m_node->getParent()));
|
||||
|
||||
auto createdTime = Utils::dateTimeString(m_node->getCreatedTimeUtc().toLocalTime());
|
||||
m_createdDateTimeLabel->setText(createdTime);
|
||||
|
@ -13,7 +13,7 @@ class QComboBox;
|
||||
namespace vnotex
|
||||
{
|
||||
class Notebook;
|
||||
class NodeLabelWithUpButton;
|
||||
class LevelLabelWithUpButton;
|
||||
class LineEditWithSnippet;
|
||||
|
||||
class NodeInfoWidget : public QWidget
|
||||
@ -59,7 +59,7 @@ namespace vnotex
|
||||
|
||||
LineEditWithSnippet *m_nameLineEdit = nullptr;
|
||||
|
||||
NodeLabelWithUpButton *m_parentNodeLabel = nullptr;
|
||||
LevelLabelWithUpButton *m_parentNodeLabel = nullptr;
|
||||
|
||||
QLabel *m_createdDateTimeLabel = nullptr;
|
||||
|
||||
|
@ -1,76 +0,0 @@
|
||||
#include "nodelabelwithupbutton.h"
|
||||
|
||||
#include <QPushButton>
|
||||
#include <QLabel>
|
||||
#include <QHBoxLayout>
|
||||
|
||||
#include "notebook/node.h"
|
||||
#include <utils/iconutils.h>
|
||||
#include "vnotex.h"
|
||||
|
||||
using namespace vnotex;
|
||||
|
||||
NodeLabelWithUpButton::NodeLabelWithUpButton(const Node *p_node, QWidget *p_parent)
|
||||
: QWidget(p_parent),
|
||||
m_node(p_node)
|
||||
{
|
||||
setupUI();
|
||||
}
|
||||
|
||||
void NodeLabelWithUpButton::setupUI()
|
||||
{
|
||||
auto mainLayout = new QHBoxLayout(this);
|
||||
mainLayout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
m_label = new QLabel(this);
|
||||
mainLayout->addWidget(m_label, 1);
|
||||
|
||||
auto iconFile = VNoteX::getInst().getThemeMgr().getIconFile("up_parent_node.svg");
|
||||
m_upButton = new QPushButton(IconUtils::fetchIconWithDisabledState(iconFile),
|
||||
tr("Up"),
|
||||
this);
|
||||
m_upButton->setToolTip(tr("Create note under an upper level node"));
|
||||
connect(m_upButton, &QPushButton::clicked,
|
||||
this, [this]() {
|
||||
if (!m_node->isRoot()) {
|
||||
m_node = m_node->getParent();
|
||||
updateLabelAndButton();
|
||||
emit nodeChanged(m_node);
|
||||
}
|
||||
});
|
||||
mainLayout->addWidget(m_upButton, 0);
|
||||
|
||||
updateLabelAndButton();
|
||||
}
|
||||
|
||||
void NodeLabelWithUpButton::updateLabelAndButton()
|
||||
{
|
||||
m_label->setText(m_node->fetchPath());
|
||||
m_upButton->setVisible(!m_readOnly && !m_node->isRoot());
|
||||
}
|
||||
|
||||
const Node *NodeLabelWithUpButton::getNode() const
|
||||
{
|
||||
return m_node;
|
||||
}
|
||||
|
||||
void NodeLabelWithUpButton::setNode(const Node *p_node)
|
||||
{
|
||||
if (m_node == p_node) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_node = p_node;
|
||||
updateLabelAndButton();
|
||||
emit nodeChanged(m_node);
|
||||
}
|
||||
|
||||
void NodeLabelWithUpButton::setReadOnly(bool p_readonly)
|
||||
{
|
||||
if (m_readOnly == p_readonly) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_readOnly = p_readonly;
|
||||
updateLabelAndButton();
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
#ifndef NODELABELWITHUPBUTTON_H
|
||||
#define NODELABELWITHUPBUTTON_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class QLabel;
|
||||
class QPushButton;
|
||||
|
||||
namespace vnotex
|
||||
{
|
||||
class Node;
|
||||
|
||||
class NodeLabelWithUpButton : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
NodeLabelWithUpButton(const Node *p_node, QWidget *p_parent = nullptr);
|
||||
|
||||
const Node *getNode() const;
|
||||
|
||||
void setNode(const Node *p_node);
|
||||
|
||||
void setReadOnly(bool p_readonly);
|
||||
|
||||
signals:
|
||||
void nodeChanged(const Node *p_node);
|
||||
|
||||
private:
|
||||
void setupUI();
|
||||
|
||||
void updateLabelAndButton();
|
||||
|
||||
QLabel *m_label = nullptr;
|
||||
|
||||
QPushButton *m_upButton = nullptr;
|
||||
|
||||
const Node *m_node = nullptr;
|
||||
|
||||
bool m_readOnly = false;
|
||||
};
|
||||
} // ns vnotex
|
||||
|
||||
#endif // NODELABELWITHUPBUTTON_H
|
@ -1,15 +1,16 @@
|
||||
#include "notepropertiesdialog.h"
|
||||
|
||||
#include "notebook/notebook.h"
|
||||
#include "notebook/node.h"
|
||||
#include "../widgetsfactory.h"
|
||||
#include <notebook/notebook.h>
|
||||
#include <notebook/node.h>
|
||||
#include <utils/pathutils.h>
|
||||
#include "exception.h"
|
||||
#include "nodeinfowidget.h"
|
||||
#include "../lineedit.h"
|
||||
#include <core/exception.h>
|
||||
#include <core/events.h>
|
||||
#include <core/vnotex.h>
|
||||
|
||||
#include "../widgetsfactory.h"
|
||||
#include "nodeinfowidget.h"
|
||||
#include "../lineedit.h"
|
||||
|
||||
using namespace vnotex;
|
||||
|
||||
NotePropertiesDialog::NotePropertiesDialog(Node *p_node, QWidget *p_parent)
|
||||
|
82
src/widgets/dialogs/renametagdialog.cpp
Normal file
@ -0,0 +1,82 @@
|
||||
#include "renametagdialog.h"
|
||||
|
||||
#include <QFormLayout>
|
||||
|
||||
#include <notebook/tagi.h>
|
||||
#include <utils/widgetutils.h>
|
||||
|
||||
#include "../widgetsfactory.h"
|
||||
#include "../lineeditwithsnippet.h"
|
||||
|
||||
using namespace vnotex;
|
||||
|
||||
RenameTagDialog::RenameTagDialog(TagI *p_tagI, const QString &p_name, QWidget *p_parent)
|
||||
: ScrollDialog(p_parent),
|
||||
m_tagI(p_tagI),
|
||||
m_tagName(p_name)
|
||||
{
|
||||
setupUI();
|
||||
|
||||
m_nameLineEdit->setFocus();
|
||||
WidgetUtils::selectBaseName(m_nameLineEdit);
|
||||
}
|
||||
|
||||
void RenameTagDialog::setupUI()
|
||||
{
|
||||
auto mainWidget = new QWidget(this);
|
||||
setCentralWidget(mainWidget);
|
||||
|
||||
auto mainLayout = WidgetsFactory::createFormLayout(mainWidget);
|
||||
|
||||
m_nameLineEdit = WidgetsFactory::createLineEditWithSnippet(m_tagName, mainWidget);
|
||||
mainLayout->addRow(tr("Name:"), m_nameLineEdit);
|
||||
|
||||
setDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
|
||||
setWindowTitle(tr("Rename Tag"));
|
||||
}
|
||||
|
||||
bool RenameTagDialog::validateInputs()
|
||||
{
|
||||
bool valid = true;
|
||||
QString msg;
|
||||
|
||||
auto name = getTagName();
|
||||
if (name == m_tagName) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!Tag::isValidName(name)) {
|
||||
valid = false;
|
||||
msg = tr("Please specify a valid name for the tag.");
|
||||
} else if (m_tagI->findTag(name)) {
|
||||
valid = false;
|
||||
msg = tr("Name conflicts with existing tag.");
|
||||
}
|
||||
|
||||
setInformationText(msg, valid ? ScrollDialog::InformationLevel::Info
|
||||
: ScrollDialog::InformationLevel::Error);
|
||||
return valid;
|
||||
}
|
||||
|
||||
bool RenameTagDialog::renameTag()
|
||||
{
|
||||
if (!m_tagI->renameTag(m_tagName, getTagName())) {
|
||||
setInformationText(tr("Failed to rename tag (%1) to (%2).").arg(m_tagName, getTagName()), ScrollDialog::InformationLevel::Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void RenameTagDialog::acceptedButtonClicked()
|
||||
{
|
||||
if (validateInputs() && renameTag()) {
|
||||
accept();
|
||||
}
|
||||
}
|
||||
|
||||
QString RenameTagDialog::getTagName() const
|
||||
{
|
||||
return m_nameLineEdit->evaluatedText();
|
||||
}
|
37
src/widgets/dialogs/renametagdialog.h
Normal file
@ -0,0 +1,37 @@
|
||||
#ifndef RENAMETAGDIALOG_H
|
||||
#define RENAMETAGDIALOG_H
|
||||
|
||||
#include "scrolldialog.h"
|
||||
|
||||
namespace vnotex
|
||||
{
|
||||
class TagI;
|
||||
class LineEditWithSnippet;
|
||||
|
||||
class RenameTagDialog : public ScrollDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
RenameTagDialog(TagI *p_tagI, const QString &p_name, QWidget *p_parent = nullptr);
|
||||
|
||||
QString getTagName() const;
|
||||
|
||||
protected:
|
||||
void acceptedButtonClicked() Q_DECL_OVERRIDE;
|
||||
|
||||
private:
|
||||
void setupUI();
|
||||
|
||||
bool validateInputs();
|
||||
|
||||
bool renameTag();
|
||||
|
||||
TagI *m_tagI = nullptr;
|
||||
|
||||
const QString m_tagName;
|
||||
|
||||
LineEditWithSnippet *m_nameLineEdit = nullptr;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // RENAMETAGDIALOG_H
|
@ -44,22 +44,6 @@ void SortDialog::setupUI(const QString &p_title, const QString &p_info)
|
||||
m_treeWidget->setRootIsDecorated(false);
|
||||
m_treeWidget->setSelectionMode(QAbstractItemView::ContiguousSelection);
|
||||
m_treeWidget->setDragDropMode(QAbstractItemView::InternalMove);
|
||||
connect(static_cast<TreeWidget *>(m_treeWidget), &TreeWidget::rowsMoved,
|
||||
this, [this](int p_first, int p_last, int p_row) {
|
||||
auto item = m_treeWidget->topLevelItem(p_row);
|
||||
if (item) {
|
||||
// Keep all items selected.
|
||||
m_treeWidget->setCurrentItem(item);
|
||||
|
||||
const int cnt = p_last - p_first + 1;
|
||||
for (int i = 0; i < cnt; ++i) {
|
||||
auto it = m_treeWidget->topLevelItem(p_row + i);
|
||||
if (it) {
|
||||
it->setSelected(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
bodyLayout->addWidget(m_treeWidget);
|
||||
|
||||
// Buttons for top/up/down/bottom.
|
||||
|
58
src/widgets/dialogs/viewtagsdialog.cpp
Normal file
@ -0,0 +1,58 @@
|
||||
#include "viewtagsdialog.h"
|
||||
|
||||
#include <QFormLayout>
|
||||
#include <QLabel>
|
||||
|
||||
#include <notebook/node.h>
|
||||
|
||||
#include "../widgetsfactory.h"
|
||||
#include "../tagviewer.h"
|
||||
|
||||
using namespace vnotex;
|
||||
|
||||
ViewTagsDialog::ViewTagsDialog(Node *p_node, QWidget *p_parent)
|
||||
: Dialog(p_parent)
|
||||
{
|
||||
setupUI();
|
||||
|
||||
setNode(p_node);
|
||||
|
||||
m_tagViewer->setFocus();
|
||||
}
|
||||
|
||||
void ViewTagsDialog::setupUI()
|
||||
{
|
||||
auto mainWidget = new QWidget(this);
|
||||
setCentralWidget(mainWidget);
|
||||
|
||||
auto mainLayout = WidgetsFactory::createFormLayout(mainWidget);
|
||||
|
||||
m_nodeNameLabel = new QLabel(mainWidget);
|
||||
mainLayout->addRow(tr("Name:"), m_nodeNameLabel);
|
||||
|
||||
m_tagViewer = new TagViewer(mainWidget);
|
||||
mainLayout->addRow(tr("Tags:"), m_tagViewer);
|
||||
|
||||
setDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
|
||||
setWindowTitle(tr("Tags"));
|
||||
}
|
||||
|
||||
void ViewTagsDialog::acceptedButtonClicked()
|
||||
{
|
||||
m_tagViewer->save();
|
||||
accept();
|
||||
}
|
||||
|
||||
void ViewTagsDialog::setNode(Node *p_node)
|
||||
{
|
||||
if (m_node == p_node) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_node = p_node;
|
||||
Q_ASSERT(m_node);
|
||||
|
||||
m_nodeNameLabel->setText(m_node->getName());
|
||||
m_tagViewer->setNode(m_node);
|
||||
}
|
35
src/widgets/dialogs/viewtagsdialog.h
Normal file
@ -0,0 +1,35 @@
|
||||
#ifndef VIEWTAGSDIALOG_H
|
||||
#define VIEWTAGSDIALOG_H
|
||||
|
||||
#include "dialog.h"
|
||||
|
||||
class QLabel;
|
||||
|
||||
namespace vnotex
|
||||
{
|
||||
class Node;
|
||||
class TagViewer;
|
||||
|
||||
class ViewTagsDialog : public Dialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ViewTagsDialog(Node *p_node, QWidget *p_parent = nullptr);
|
||||
|
||||
protected:
|
||||
void acceptedButtonClicked() Q_DECL_OVERRIDE;
|
||||
|
||||
private:
|
||||
void setupUI();
|
||||
|
||||
void setNode(Node *p_node);
|
||||
|
||||
Node *m_node = nullptr;
|
||||
|
||||
QLabel *m_nodeNameLabel = nullptr;
|
||||
|
||||
TagViewer *m_tagViewer = nullptr;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // VIEWTAGSDIALOG_H
|
@ -23,6 +23,7 @@
|
||||
#include "searchpanel.h"
|
||||
#include "snippetpanel.h"
|
||||
#include "historypanel.h"
|
||||
#include "tagexplorer.h"
|
||||
|
||||
using namespace vnotex;
|
||||
|
||||
@ -68,6 +69,8 @@ QString DockWidgetHelper::iconFileName(DockIndex p_dockIndex)
|
||||
return "outline_dock.svg";
|
||||
case DockIndex::HistoryDock:
|
||||
return "history_dock.svg";
|
||||
case DockIndex::TagDock:
|
||||
return "tag_dock.svg";
|
||||
case DockIndex::SearchDock:
|
||||
return "search_dock.svg";
|
||||
case DockIndex::SnippetDock:
|
||||
@ -95,17 +98,20 @@ void DockWidgetHelper::setupDocks()
|
||||
tabifiedDockIndex.append(m_docks.size());
|
||||
setupNavigationDock();
|
||||
|
||||
setupOutlineDock();
|
||||
|
||||
tabifiedDockIndex.append(m_docks.size());
|
||||
setupHistoryDock();
|
||||
|
||||
tabifiedDockIndex.append(m_docks.size());
|
||||
setupTagDock();
|
||||
|
||||
tabifiedDockIndex.append(m_docks.size());
|
||||
setupSearchDock();
|
||||
|
||||
tabifiedDockIndex.append(m_docks.size());
|
||||
setupSnippetDock();
|
||||
|
||||
setupOutlineDock();
|
||||
|
||||
setupLocationListDock();
|
||||
|
||||
setupShortcuts();
|
||||
@ -175,6 +181,18 @@ void DockWidgetHelper::setupHistoryDock()
|
||||
m_mainWindow->addDockWidget(Qt::LeftDockWidgetArea, dock);
|
||||
}
|
||||
|
||||
void DockWidgetHelper::setupTagDock()
|
||||
{
|
||||
auto dock = createDockWidget(DockIndex::TagDock, tr("Tags"), m_mainWindow);
|
||||
|
||||
dock->setObjectName(QStringLiteral("TagDock.vnotex"));
|
||||
dock->setAllowedAreas(Qt::AllDockWidgetAreas);
|
||||
|
||||
dock->setWidget(m_mainWindow->m_tagExplorer);
|
||||
dock->setFocusProxy(m_mainWindow->m_tagExplorer);
|
||||
m_mainWindow->addDockWidget(Qt::LeftDockWidgetArea, dock);
|
||||
}
|
||||
|
||||
void DockWidgetHelper::setupLocationListDock()
|
||||
{
|
||||
auto dock = createDockWidget(DockIndex::LocationListDock, tr("Location List"), m_mainWindow);
|
||||
@ -248,6 +266,9 @@ void DockWidgetHelper::setupShortcuts()
|
||||
setupDockActivateShortcut(m_docks[DockIndex::HistoryDock],
|
||||
coreConfig.getShortcut(CoreConfig::Shortcut::HistoryDock));
|
||||
|
||||
setupDockActivateShortcut(m_docks[DockIndex::TagDock],
|
||||
coreConfig.getShortcut(CoreConfig::Shortcut::TagDock));
|
||||
|
||||
setupDockActivateShortcut(m_docks[DockIndex::SearchDock],
|
||||
coreConfig.getShortcut(CoreConfig::Shortcut::SearchDock));
|
||||
// Extra shortcut for SearchDock.
|
||||
|
@ -24,10 +24,11 @@ namespace vnotex
|
||||
enum DockIndex
|
||||
{
|
||||
NavigationDock = 0,
|
||||
OutlineDock,
|
||||
HistoryDock,
|
||||
TagDock,
|
||||
SearchDock,
|
||||
SnippetDock,
|
||||
OutlineDock,
|
||||
LocationListDock,
|
||||
MaxDock
|
||||
};
|
||||
@ -102,6 +103,8 @@ namespace vnotex
|
||||
|
||||
void setupHistoryDock();
|
||||
|
||||
void setupTagDock();
|
||||
|
||||
void setupLocationListDock();
|
||||
|
||||
QDockWidget *createDockWidget(DockIndex p_dockIndex, const QString &p_title, QWidget *p_parent);
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
#include <utils/widgetutils.h>
|
||||
#include <utils/pathutils.h>
|
||||
#include <utils/iconutils.h>
|
||||
#include <utils/utils.h>
|
||||
#include <core/vnotex.h>
|
||||
#include <core/exception.h>
|
||||
@ -18,26 +19,33 @@
|
||||
#include "listwidget.h"
|
||||
#include "mainwindow.h"
|
||||
#include "messageboxhelper.h"
|
||||
#include "navigationmodemgr.h"
|
||||
|
||||
using namespace vnotex;
|
||||
|
||||
HistoryPanel::HistoryPanel(QWidget *p_parent)
|
||||
: QFrame(p_parent)
|
||||
{
|
||||
initIcons();
|
||||
|
||||
setupUI();
|
||||
|
||||
updateSeparators();
|
||||
}
|
||||
|
||||
void HistoryPanel::initIcons()
|
||||
{
|
||||
const auto &themeMgr = VNoteX::getInst().getThemeMgr();
|
||||
m_fileIcon = IconUtils::fetchIcon(themeMgr.getIconFile(QStringLiteral("file_node.svg")));
|
||||
}
|
||||
|
||||
void HistoryPanel::setupUI()
|
||||
{
|
||||
auto mainLayout = new QVBoxLayout(this);
|
||||
WidgetUtils::setContentsMargins(mainLayout);
|
||||
|
||||
{
|
||||
setupTitleBar(QString(), this);
|
||||
mainLayout->addWidget(m_titleBar);
|
||||
}
|
||||
setupTitleBar(QString(), this);
|
||||
mainLayout->addWidget(m_titleBar);
|
||||
|
||||
m_historyList = new ListWidget(true, this);
|
||||
m_historyList->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
@ -48,6 +56,9 @@ void HistoryPanel::setupUI()
|
||||
this, &HistoryPanel::openItem);
|
||||
mainLayout->addWidget(m_historyList);
|
||||
|
||||
m_navigationWrapper.reset(new NavigationModeWrapper<QListWidget, QListWidgetItem>(m_historyList));
|
||||
NavigationModeMgr::getInst().registerNavigationTarget(m_navigationWrapper.data());
|
||||
|
||||
setFocusProxy(m_historyList);
|
||||
}
|
||||
|
||||
@ -132,8 +143,6 @@ bool HistoryPanel::isValidItem(const QListWidgetItem *p_item) const
|
||||
|
||||
void HistoryPanel::updateHistoryList()
|
||||
{
|
||||
m_pendingUpdate = false;
|
||||
|
||||
m_historyList->clear();
|
||||
|
||||
const auto &history = HistoryMgr::getInst().getHistory();
|
||||
@ -168,19 +177,12 @@ void HistoryPanel::updateHistoryList()
|
||||
}
|
||||
}
|
||||
|
||||
void HistoryPanel::showEvent(QShowEvent *p_event)
|
||||
void HistoryPanel::initialize()
|
||||
{
|
||||
QFrame::showEvent(p_event);
|
||||
connect(&HistoryMgr::getInst(), &HistoryMgr::historyUpdated,
|
||||
this, &HistoryPanel::updateHistoryList);
|
||||
|
||||
if (!m_initialized) {
|
||||
m_initialized = true;
|
||||
connect(&HistoryMgr::getInst(), &HistoryMgr::historyUpdated,
|
||||
this, &HistoryPanel::updateHistoryListIfProper);
|
||||
}
|
||||
|
||||
if (m_pendingUpdate) {
|
||||
updateHistoryList();
|
||||
}
|
||||
updateHistoryList();
|
||||
}
|
||||
|
||||
void HistoryPanel::updateSeparators()
|
||||
@ -199,21 +201,13 @@ void HistoryPanel::updateSeparators()
|
||||
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);
|
||||
item->setIcon(m_fileIcon);
|
||||
if (p_hisItem.m_notebookName.isEmpty()) {
|
||||
item->setToolTip(tr("%1\n%2").arg(p_hisItem.m_item.m_path,
|
||||
Utils::dateTimeString(p_hisItem.m_item.m_lastAccessedTimeUtc.toLocalTime())));
|
||||
|
@ -3,6 +3,10 @@
|
||||
|
||||
#include <QFrame>
|
||||
#include <QDateTime>
|
||||
#include <QIcon>
|
||||
#include <QScopedPointer>
|
||||
|
||||
#include "navigationmodewrapper.h"
|
||||
|
||||
class QListWidget;
|
||||
class QListWidgetItem;
|
||||
@ -18,8 +22,7 @@ namespace vnotex
|
||||
public:
|
||||
explicit HistoryPanel(QWidget *p_parent = nullptr);
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent *p_event) Q_DECL_OVERRIDE;
|
||||
void initialize();
|
||||
|
||||
private slots:
|
||||
void handleContextMenuRequested(const QPoint &p_pos);
|
||||
@ -36,14 +39,14 @@ namespace vnotex
|
||||
QDateTime m_dateUtc;
|
||||
};
|
||||
|
||||
void initIcons();
|
||||
|
||||
void setupUI();
|
||||
|
||||
void setupTitleBar(const QString &p_title, QWidget *p_parent = nullptr);
|
||||
|
||||
void updateHistoryList();
|
||||
|
||||
void updateHistoryListIfProper();
|
||||
|
||||
void updateSeparators();
|
||||
|
||||
void addItem(const HistoryItemFull &p_hisItem);
|
||||
@ -56,11 +59,11 @@ namespace vnotex
|
||||
|
||||
QListWidget *m_historyList = nullptr;
|
||||
|
||||
bool m_initialized = false;
|
||||
|
||||
bool m_pendingUpdate = true;
|
||||
QScopedPointer<NavigationModeWrapper<QListWidget, QListWidgetItem>> m_navigationWrapper;
|
||||
|
||||
QVector<SeparatorData> m_separators;
|
||||
|
||||
QIcon m_fileIcon;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -99,3 +99,28 @@ bool ListWidget::isSeparatorItem(const QListWidgetItem *p_item)
|
||||
{
|
||||
return p_item->type() == ItemTypeSeparator;
|
||||
}
|
||||
|
||||
QListWidgetItem *ListWidget::findItem(const QListWidget *p_widget, const QVariant &p_data)
|
||||
{
|
||||
QListWidgetItem *item = nullptr;
|
||||
forEachItem(p_widget, [&item, &p_data](QListWidgetItem *itemIter) {
|
||||
if (itemIter->data(Qt::UserRole) == p_data) {
|
||||
item = itemIter;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
void ListWidget::forEachItem(const QListWidget *p_widget, const std::function<bool(QListWidgetItem *p_item)> &p_func)
|
||||
{
|
||||
int cnt = p_widget->count();
|
||||
for (int i = 0; i < cnt; ++i) {
|
||||
if (!p_func(p_widget->item(i))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,9 @@
|
||||
#define LISTWIDGET_H
|
||||
|
||||
#include <QListWidget>
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <QVector>
|
||||
|
||||
namespace vnotex
|
||||
@ -20,6 +23,11 @@ namespace vnotex
|
||||
|
||||
static bool isSeparatorItem(const QListWidgetItem *p_item);
|
||||
|
||||
static QListWidgetItem *findItem(const QListWidget *p_widget, const QVariant &p_data);
|
||||
|
||||
// @p_func: return false to abort the iteration.
|
||||
static void forEachItem(const QListWidget *p_widget, const std::function<bool(QListWidgetItem *p_item)> &p_func);
|
||||
|
||||
protected:
|
||||
void keyPressEvent(QKeyEvent *p_event) Q_DECL_OVERRIDE;
|
||||
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include "widgetsfactory.h"
|
||||
#include "titlebar.h"
|
||||
#include "styleditemdelegate.h"
|
||||
#include "navigationmodemgr.h"
|
||||
|
||||
#include <core/vnotex.h>
|
||||
#include <core/thememgr.h>
|
||||
@ -56,6 +57,9 @@ void LocationList::setupUI()
|
||||
});
|
||||
mainLayout->addWidget(m_tree);
|
||||
|
||||
m_navigationWrapper.reset(new NavigationModeWrapper<QTreeWidget, QTreeWidgetItem>(m_tree));
|
||||
NavigationModeMgr::getInst().registerNavigationTarget(m_navigationWrapper.data());
|
||||
|
||||
setFocusProxy(m_tree);
|
||||
}
|
||||
|
||||
@ -90,14 +94,6 @@ const QIcon &LocationList::getItemIcon(LocationType p_type)
|
||||
}
|
||||
}
|
||||
|
||||
NavigationModeWrapper<QTreeWidget, QTreeWidgetItem> *LocationList::getNavigationModeWrapper()
|
||||
{
|
||||
if (!m_navigationWrapper) {
|
||||
m_navigationWrapper.reset(new NavigationModeWrapper<QTreeWidget, QTreeWidgetItem>(m_tree));
|
||||
}
|
||||
return m_navigationWrapper.data();
|
||||
}
|
||||
|
||||
void LocationList::setupTitleBar(const QString &p_title, QWidget *p_parent)
|
||||
{
|
||||
m_titleBar = new TitleBar(p_title, true, TitleBar::Action::None, p_parent);
|
||||
|
@ -24,8 +24,6 @@ namespace vnotex
|
||||
|
||||
explicit LocationList(QWidget *p_parent = nullptr);
|
||||
|
||||
NavigationModeWrapper<QTreeWidget, QTreeWidgetItem> *getNavigationModeWrapper();
|
||||
|
||||
void clear();
|
||||
|
||||
void addLocation(const ComplexLocation &p_location);
|
||||
|
@ -53,6 +53,7 @@
|
||||
#include <utils/iconutils.h>
|
||||
#include <core/thememgr.h>
|
||||
#include "dialogs/updater.h"
|
||||
#include "tagexplorer.h"
|
||||
|
||||
using namespace vnotex;
|
||||
|
||||
@ -118,6 +119,8 @@ void MainWindow::kickOffOnStart(const QStringList &p_paths)
|
||||
|
||||
checkNotebooksFailedToLoad();
|
||||
|
||||
loadWidgetsData();
|
||||
|
||||
demoWidget();
|
||||
|
||||
openFiles(p_paths);
|
||||
@ -140,6 +143,7 @@ void MainWindow::kickOffOnStart(const QStringList &p_paths)
|
||||
if (!file.isEmpty()) {
|
||||
auto paras = QSharedPointer<FileOpenParameters>::create();
|
||||
paras->m_readOnly = true;
|
||||
paras->m_sessionEnabled = false;
|
||||
emit VNoteX::getInst().openFileRequested(file, paras);
|
||||
}
|
||||
}
|
||||
@ -247,7 +251,9 @@ void MainWindow::setupCentralWidget()
|
||||
|
||||
void MainWindow::setupDocks()
|
||||
{
|
||||
setupNotebookExplorer(this);
|
||||
setupNotebookExplorer();
|
||||
|
||||
setupTagExplorer();
|
||||
|
||||
setupOutlineViewer();
|
||||
|
||||
@ -298,13 +304,11 @@ void MainWindow::setupLocationList()
|
||||
{
|
||||
m_locationList = new LocationList(this);
|
||||
m_locationList->setObjectName("LocationList.vnotex");
|
||||
|
||||
NavigationModeMgr::getInst().registerNavigationTarget(m_locationList->getNavigationModeWrapper());
|
||||
}
|
||||
|
||||
void MainWindow::setupNotebookExplorer(QWidget *p_parent)
|
||||
void MainWindow::setupNotebookExplorer()
|
||||
{
|
||||
m_notebookExplorer = new NotebookExplorer(p_parent);
|
||||
m_notebookExplorer = new NotebookExplorer(this);
|
||||
connect(&VNoteX::getInst(), &VNoteX::newNotebookRequested,
|
||||
m_notebookExplorer, &NotebookExplorer::newNotebook);
|
||||
connect(&VNoteX::getInst(), &VNoteX::newNotebookFromFolderRequested,
|
||||
@ -387,6 +391,8 @@ void MainWindow::closeEvent(QCloseEvent *p_event)
|
||||
return;
|
||||
}
|
||||
|
||||
m_trayIcon->hide();
|
||||
|
||||
QMainWindow::closeEvent(p_event);
|
||||
qApp->exit(exitCode > -1 ? exitCode : 0);
|
||||
} else {
|
||||
@ -408,6 +414,7 @@ void MainWindow::saveStateAndGeometry()
|
||||
sg.m_mainState = saveState();
|
||||
sg.m_mainGeometry = saveGeometry();
|
||||
sg.m_visibleDocksBeforeExpand = m_visibleDocksBeforeExpand;
|
||||
sg.m_tagExplorerState = m_tagExplorer->saveState();
|
||||
|
||||
auto& sessionConfig = ConfigMgr::getInst().getSessionConfig();
|
||||
sessionConfig.setMainWindowStateGeometry(sg);
|
||||
@ -434,6 +441,10 @@ void MainWindow::loadStateAndGeometry(bool p_stateOnly)
|
||||
m_visibleDocksBeforeExpand = m_dockWidgetHelper.getVisibleDocks();
|
||||
}
|
||||
}
|
||||
|
||||
if (!sg.m_tagExplorerState.isEmpty()) {
|
||||
m_tagExplorer->restoreState(sg.m_tagExplorerState);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::resetStateAndGeometry()
|
||||
@ -484,6 +495,7 @@ void MainWindow::setupOutlineViewer()
|
||||
m_outlineViewer = new OutlineViewer(QString(), this);
|
||||
m_outlineViewer->setObjectName("OutlineViewer.vnotex");
|
||||
|
||||
// There are OutlineViewers in each ViewWindow. We only need to register navigation mode for the outline panel.
|
||||
NavigationModeMgr::getInst().registerNavigationTarget(m_outlineViewer->getNavigationModeWrapper());
|
||||
|
||||
connect(m_viewArea, &ViewArea::currentViewWindowChanged,
|
||||
@ -738,3 +750,17 @@ void MainWindow::checkNotebooksFailedToLoad()
|
||||
notebookMgr.clearNotebooksFailedToLoad();
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::setupTagExplorer()
|
||||
{
|
||||
m_tagExplorer = new TagExplorer(this);
|
||||
connect(&VNoteX::getInst().getNotebookMgr(), &NotebookMgr::currentNotebookChanged,
|
||||
m_tagExplorer, &TagExplorer::setNotebook);
|
||||
}
|
||||
|
||||
void MainWindow::loadWidgetsData()
|
||||
{
|
||||
m_historyPanel->initialize();
|
||||
|
||||
m_snippetPanel->initialize();
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ namespace vnotex
|
||||
{
|
||||
class ToolBox;
|
||||
class NotebookExplorer;
|
||||
class TagExplorer;
|
||||
class ViewArea;
|
||||
class Event;
|
||||
class OutlineViewer;
|
||||
@ -110,7 +111,9 @@ namespace vnotex
|
||||
|
||||
void setupHistoryPanel();
|
||||
|
||||
void setupNotebookExplorer(QWidget *p_parent = nullptr);
|
||||
void setupNotebookExplorer();
|
||||
|
||||
void setupTagExplorer();
|
||||
|
||||
void setupDocks();
|
||||
|
||||
@ -143,6 +146,8 @@ namespace vnotex
|
||||
|
||||
void checkNotebooksFailedToLoad();
|
||||
|
||||
void loadWidgetsData();
|
||||
|
||||
ToolBarHelper m_toolBarHelper;
|
||||
|
||||
StatusBarHelper m_statusBarHelper;
|
||||
@ -153,6 +158,8 @@ namespace vnotex
|
||||
|
||||
NotebookExplorer *m_notebookExplorer = nullptr;
|
||||
|
||||
TagExplorer *m_tagExplorer = nullptr;
|
||||
|
||||
ViewArea *m_viewArea = nullptr;
|
||||
|
||||
QWidget *m_viewAreaStatusWidget = nullptr;
|
||||
|
@ -260,6 +260,8 @@ void MarkdownViewWindow::setupToolBar()
|
||||
|
||||
addAction(toolBar, ViewWindowToolBarHelper::Attachment);
|
||||
|
||||
addAction(toolBar, ViewWindowToolBarHelper::Tag);
|
||||
|
||||
toolBar->addSeparator();
|
||||
|
||||
addAction(toolBar, ViewWindowToolBarHelper::SectionNumber);
|
||||
@ -1115,18 +1117,21 @@ QPoint MarkdownViewWindow::getFloatingWidgetPosition()
|
||||
QString MarkdownViewWindow::selectedText() const
|
||||
{
|
||||
switch (m_mode) {
|
||||
case ViewWindowMode::FullPreview:
|
||||
case ViewWindowMode::Invalid:
|
||||
Q_FALLTHROUGH();
|
||||
case ViewWindowMode::Read:
|
||||
Q_ASSERT(m_viewer);
|
||||
return m_viewer->selectedText();
|
||||
|
||||
case ViewWindowMode::Edit:
|
||||
Q_FALLTHROUGH();
|
||||
case ViewWindowMode::FullPreview:
|
||||
Q_FALLTHROUGH();
|
||||
case ViewWindowMode::FocusPreview:
|
||||
Q_ASSERT(m_editor);
|
||||
return m_editor->getTextEdit()->selectedText();
|
||||
|
||||
default:
|
||||
return QString();
|
||||
}
|
||||
return QString("");
|
||||
}
|
||||
|
||||
void MarkdownViewWindow::handleImageHostChanged(const QString &p_hostName)
|
||||
|
@ -18,6 +18,7 @@ namespace vnotex
|
||||
public:
|
||||
~NavigationModeMgr();
|
||||
|
||||
// Maybe we need a unregisterNavigationTarget()?
|
||||
void registerNavigationTarget(NavigationMode *p_target);
|
||||
|
||||
static NavigationModeMgr &getInst();
|
||||
|
@ -16,10 +16,10 @@
|
||||
#include "dialogs/importnotebookdialog.h"
|
||||
#include "dialogs/importfolderdialog.h"
|
||||
#include "dialogs/importlegacynotebookdialog.h"
|
||||
#include "vnotex.h"
|
||||
#include <core/vnotex.h>
|
||||
#include "mainwindow.h"
|
||||
#include "notebook/notebook.h"
|
||||
#include "notebookmgr.h"
|
||||
#include <notebook/notebook.h>
|
||||
#include <core/notebookmgr.h>
|
||||
#include <utils/iconutils.h>
|
||||
#include <utils/widgetutils.h>
|
||||
#include <utils/pathutils.h>
|
||||
@ -144,8 +144,8 @@ TitleBar *NotebookExplorer::setupTitleBar(QWidget *p_parent)
|
||||
return;
|
||||
}
|
||||
int ret = MessageBoxHelper::questionOkCancel(MessageBoxHelper::Warning,
|
||||
tr("Scan the whole notebook (%1) and import external files automatically.").arg(m_currentNotebook->getName()),
|
||||
tr("This operation helps importing external files that are added outside VNote. "
|
||||
tr("Scan the whole notebook (%1) and import external files automatically?").arg(m_currentNotebook->getName()),
|
||||
tr("This operation helps importing external files that are added outside from VNote. "
|
||||
"It may import unexpected files."),
|
||||
tr("It is recommended to always manage files within VNote."),
|
||||
VNoteX::getInst().getMainWindow());
|
||||
@ -220,8 +220,6 @@ void NotebookExplorer::loadNotebooks()
|
||||
auto ¬ebookMgr = VNoteX::getInst().getNotebookMgr();
|
||||
const auto ¬ebooks = notebookMgr.getNotebooks();
|
||||
m_selector->setNotebooks(notebooks);
|
||||
|
||||
emit updateTitleBarMenuActions();
|
||||
}
|
||||
|
||||
void NotebookExplorer::reloadNotebook(const Notebook *p_notebook)
|
||||
@ -241,8 +239,6 @@ void NotebookExplorer::setCurrentNotebook(const QSharedPointer<Notebook> &p_note
|
||||
m_nodeExplorer->setNotebook(p_notebook);
|
||||
|
||||
recoverSession();
|
||||
|
||||
emit updateTitleBarMenuActions();
|
||||
}
|
||||
|
||||
void NotebookExplorer::newNotebook()
|
||||
@ -546,6 +542,14 @@ void NotebookExplorer::recoverSession()
|
||||
void NotebookExplorer::rebuildDatabase()
|
||||
{
|
||||
if (m_currentNotebook) {
|
||||
int okRet = MessageBoxHelper::questionOkCancel(MessageBoxHelper::Warning,
|
||||
tr("Rebuild the database of notebook (%1)?").arg(m_currentNotebook->getName()),
|
||||
tr("This operation will rebuild the notebook database from configuration files. It may take time."),
|
||||
tr("A notebook may use a database for cache, such as IDs of nodes and tags."),
|
||||
VNoteX::getInst().getMainWindow());
|
||||
if (okRet != QMessageBox::Ok) {
|
||||
return;
|
||||
}
|
||||
|
||||
QProgressDialog proDlg(tr("Rebuilding notebook database..."),
|
||||
QString(),
|
||||
@ -563,10 +567,12 @@ void NotebookExplorer::rebuildDatabase()
|
||||
|
||||
if (ret) {
|
||||
MessageBoxHelper::notify(MessageBoxHelper::Type::Information,
|
||||
tr("Notebook database has been rebuilt."));
|
||||
tr("Notebook database has been rebuilt."),
|
||||
VNoteX::getInst().getMainWindow());
|
||||
} else {
|
||||
MessageBoxHelper::notify(MessageBoxHelper::Type::Warning,
|
||||
tr("Failed to rebuild notebook database."));
|
||||
tr("Failed to rebuild notebook database."),
|
||||
VNoteX::getInst().getMainWindow());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -60,9 +60,6 @@ namespace vnotex
|
||||
signals:
|
||||
void notebookActivated(ID p_notebookId);
|
||||
|
||||
// Internal use only.
|
||||
void updateTitleBarMenuActions();
|
||||
|
||||
private:
|
||||
void setupUI();
|
||||
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include "dialogs/folderpropertiesdialog.h"
|
||||
#include "dialogs/deleteconfirmdialog.h"
|
||||
#include "dialogs/sortdialog.h"
|
||||
#include "dialogs/viewtagsdialog.h"
|
||||
#include <utils/widgetutils.h>
|
||||
#include <utils/pathutils.h>
|
||||
#include <utils/clipboardutils.h>
|
||||
@ -39,19 +40,7 @@
|
||||
|
||||
using namespace vnotex;
|
||||
|
||||
QIcon NotebookNodeExplorer::s_folderNodeIcon;
|
||||
|
||||
QIcon NotebookNodeExplorer::s_fileNodeIcon;
|
||||
|
||||
QIcon NotebookNodeExplorer::s_invalidFolderNodeIcon;
|
||||
|
||||
QIcon NotebookNodeExplorer::s_invalidFileNodeIcon;
|
||||
|
||||
QIcon NotebookNodeExplorer::s_recycleBinNodeIcon;
|
||||
|
||||
QIcon NotebookNodeExplorer::s_externalFolderNodeIcon;
|
||||
|
||||
QIcon NotebookNodeExplorer::s_externalFileNodeIcon;
|
||||
QIcon NotebookNodeExplorer::s_nodeIcons[NodeIcon::MaxIcons];
|
||||
|
||||
NotebookNodeExplorer::NodeData::NodeData()
|
||||
{
|
||||
@ -188,7 +177,7 @@ NotebookNodeExplorer::NotebookNodeExplorer(QWidget *p_parent)
|
||||
|
||||
void NotebookNodeExplorer::initNodeIcons() const
|
||||
{
|
||||
if (!s_folderNodeIcon.isNull()) {
|
||||
if (!s_nodeIcons[0].isNull()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -205,13 +194,13 @@ void NotebookNodeExplorer::initNodeIcons() const
|
||||
const QString fileIconName("file_node.svg");
|
||||
const QString recycleBinIconName("recycle_bin.svg");
|
||||
|
||||
s_folderNodeIcon = IconUtils::fetchIcon(themeMgr.getIconFile(folderIconName), fg);
|
||||
s_fileNodeIcon = IconUtils::fetchIcon(themeMgr.getIconFile(fileIconName), fg);
|
||||
s_invalidFolderNodeIcon = IconUtils::fetchIcon(themeMgr.getIconFile(folderIconName), invalidFg);
|
||||
s_invalidFileNodeIcon = IconUtils::fetchIcon(themeMgr.getIconFile(fileIconName), invalidFg);
|
||||
s_recycleBinNodeIcon = IconUtils::fetchIcon(themeMgr.getIconFile(recycleBinIconName), fg);
|
||||
s_externalFolderNodeIcon = IconUtils::fetchIcon(themeMgr.getIconFile(folderIconName), externalFg);
|
||||
s_externalFileNodeIcon = IconUtils::fetchIcon(themeMgr.getIconFile(fileIconName), externalFg);
|
||||
s_nodeIcons[NodeIcon::FolderNode] = IconUtils::fetchIcon(themeMgr.getIconFile(folderIconName), fg);
|
||||
s_nodeIcons[NodeIcon::FileNode] = IconUtils::fetchIcon(themeMgr.getIconFile(fileIconName), fg);
|
||||
s_nodeIcons[NodeIcon::InvalidFolderNode] = IconUtils::fetchIcon(themeMgr.getIconFile(folderIconName), invalidFg);
|
||||
s_nodeIcons[NodeIcon::InvalidFileNode] = IconUtils::fetchIcon(themeMgr.getIconFile(fileIconName), invalidFg);
|
||||
s_nodeIcons[NodeIcon::RecycleBinNode] = IconUtils::fetchIcon(themeMgr.getIconFile(recycleBinIconName), fg);
|
||||
s_nodeIcons[NodeIcon::ExternalFolderNode] = IconUtils::fetchIcon(themeMgr.getIconFile(folderIconName), externalFg);
|
||||
s_nodeIcons[NodeIcon::ExternalFileNode] = IconUtils::fetchIcon(themeMgr.getIconFile(fileIconName), externalFg);
|
||||
}
|
||||
|
||||
void NotebookNodeExplorer::setupUI()
|
||||
@ -497,7 +486,7 @@ void NotebookNodeExplorer::fillTreeItem(QTreeWidgetItem *p_item, Node *p_node, b
|
||||
setItemNodeData(p_item, NodeData(p_node, p_loaded));
|
||||
p_item->setText(Column::Name, p_node->getName());
|
||||
p_item->setIcon(Column::Name, getNodeItemIcon(p_node));
|
||||
p_item->setToolTip(Column::Name, p_node->exists() ? p_node->getName() : (tr("[Invalid] %1").arg(p_node->getName())));
|
||||
p_item->setToolTip(Column::Name, generateToolTip(p_node));
|
||||
}
|
||||
|
||||
void NotebookNodeExplorer::fillTreeItem(QTreeWidgetItem *p_item, const QSharedPointer<ExternalNode> &p_node) const
|
||||
@ -511,19 +500,19 @@ void NotebookNodeExplorer::fillTreeItem(QTreeWidgetItem *p_item, const QSharedPo
|
||||
const QIcon &NotebookNodeExplorer::getNodeItemIcon(const Node *p_node) const
|
||||
{
|
||||
if (p_node->hasContent()) {
|
||||
return p_node->exists() ? s_fileNodeIcon : s_invalidFileNodeIcon;
|
||||
return p_node->exists() ? s_nodeIcons[NodeIcon::FileNode] : s_nodeIcons[NodeIcon::InvalidFileNode];
|
||||
} else {
|
||||
if (p_node->getUse() == Node::Use::RecycleBin) {
|
||||
return s_recycleBinNodeIcon;
|
||||
return s_nodeIcons[NodeIcon::RecycleBinNode];
|
||||
}
|
||||
|
||||
return p_node->exists() ? s_folderNodeIcon : s_invalidFolderNodeIcon;
|
||||
return p_node->exists() ? s_nodeIcons[NodeIcon::FolderNode] : s_nodeIcons[NodeIcon::InvalidFolderNode];
|
||||
}
|
||||
}
|
||||
|
||||
const QIcon &NotebookNodeExplorer::getNodeItemIcon(const ExternalNode *p_node) const
|
||||
{
|
||||
return p_node->isFolder() ? s_externalFolderNodeIcon : s_externalFileNodeIcon;
|
||||
return p_node->isFolder() ? s_nodeIcons[NodeIcon::ExternalFolderNode] : s_nodeIcons[NodeIcon::ExternalFileNode];
|
||||
}
|
||||
|
||||
Node *NotebookNodeExplorer::getCurrentNode() const
|
||||
@ -752,158 +741,126 @@ void NotebookNodeExplorer::clearStateCache(const Notebook *p_notebook)
|
||||
|
||||
void NotebookNodeExplorer::createContextMenuOnRoot(QMenu *p_menu)
|
||||
{
|
||||
auto act = createAction(Action::NewNote, p_menu);
|
||||
p_menu->addAction(act);
|
||||
createAndAddAction(Action::NewNote, p_menu);
|
||||
|
||||
act = createAction(Action::NewFolder, p_menu);
|
||||
p_menu->addAction(act);
|
||||
createAndAddAction(Action::NewFolder, p_menu);
|
||||
|
||||
if (isPasteOnNodeAvailable(nullptr)) {
|
||||
p_menu->addSeparator();
|
||||
act = createAction(Action::Paste, p_menu);
|
||||
p_menu->addAction(act);
|
||||
createAndAddAction(Action::Paste, p_menu);
|
||||
}
|
||||
|
||||
p_menu->addSeparator();
|
||||
|
||||
act = createAction(Action::Reload, p_menu);
|
||||
p_menu->addAction(act);
|
||||
createAndAddAction(Action::Reload, p_menu);
|
||||
|
||||
act = createAction(Action::ReloadIndex, p_menu);
|
||||
p_menu->addAction(act);
|
||||
createAndAddAction(Action::ReloadIndex, p_menu);
|
||||
|
||||
act = createAction(Action::OpenLocation, p_menu);
|
||||
p_menu->addAction(act);
|
||||
createAndAddAction(Action::OpenLocation, p_menu);
|
||||
}
|
||||
|
||||
void NotebookNodeExplorer::createContextMenuOnNode(QMenu *p_menu, const Node *p_node)
|
||||
{
|
||||
const int selectedSize = m_masterExplorer->selectedItems().size();
|
||||
QAction *act = nullptr;
|
||||
|
||||
if (m_notebook->isRecycleBinNode(p_node)) {
|
||||
// Recycle bin node.
|
||||
act = createAction(Action::Reload, p_menu);
|
||||
p_menu->addAction(act);
|
||||
createAndAddAction(Action::Reload, p_menu);
|
||||
|
||||
act = createAction(Action::ReloadIndex, p_menu);
|
||||
p_menu->addAction(act);
|
||||
createAndAddAction(Action::ReloadIndex, p_menu);
|
||||
|
||||
if (selectedSize == 1) {
|
||||
act = createAction(Action::EmptyRecycleBin, p_menu);
|
||||
p_menu->addAction(act);
|
||||
createAndAddAction(Action::EmptyRecycleBin, p_menu);
|
||||
|
||||
act = createAction(Action::OpenLocation, p_menu);
|
||||
p_menu->addAction(act);
|
||||
createAndAddAction(Action::OpenLocation, p_menu);
|
||||
}
|
||||
} else if (m_notebook->isNodeInRecycleBin(p_node)) {
|
||||
// Node in recycle bin.
|
||||
act = createAction(Action::Open, p_menu);
|
||||
p_menu->addAction(act);
|
||||
createAndAddAction(Action::Open, p_menu);
|
||||
|
||||
addOpenWithMenu(p_menu);
|
||||
|
||||
p_menu->addSeparator();
|
||||
|
||||
if (selectedSize == 1 && p_node->isContainer()) {
|
||||
act = createAction(Action::ExpandAll, p_menu);
|
||||
p_menu->addAction(act);
|
||||
createAndAddAction(Action::ExpandAll, p_menu);
|
||||
}
|
||||
|
||||
p_menu->addSeparator();
|
||||
|
||||
act = createAction(Action::Cut, p_menu);
|
||||
p_menu->addAction(act);
|
||||
createAndAddAction(Action::Cut, p_menu);
|
||||
|
||||
act = createAction(Action::DeleteFromRecycleBin, p_menu);
|
||||
p_menu->addAction(act);
|
||||
createAndAddAction(Action::DeleteFromRecycleBin, p_menu);
|
||||
|
||||
p_menu->addSeparator();
|
||||
|
||||
act = createAction(Action::Reload, p_menu);
|
||||
p_menu->addAction(act);
|
||||
createAndAddAction(Action::Reload, p_menu);
|
||||
|
||||
act = createAction(Action::ReloadIndex, p_menu);
|
||||
p_menu->addAction(act);
|
||||
createAndAddAction(Action::ReloadIndex, p_menu);
|
||||
|
||||
if (selectedSize == 1) {
|
||||
p_menu->addSeparator();
|
||||
|
||||
act = createAction(Action::CopyPath, p_menu);
|
||||
p_menu->addAction(act);
|
||||
createAndAddAction(Action::CopyPath, p_menu);
|
||||
|
||||
act = createAction(Action::OpenLocation, p_menu);
|
||||
p_menu->addAction(act);
|
||||
createAndAddAction(Action::OpenLocation, p_menu);
|
||||
}
|
||||
} else {
|
||||
act = createAction(Action::Open, p_menu);
|
||||
p_menu->addAction(act);
|
||||
createAndAddAction(Action::Open, p_menu);
|
||||
|
||||
addOpenWithMenu(p_menu);
|
||||
|
||||
p_menu->addSeparator();
|
||||
|
||||
if (selectedSize == 1 && p_node->isContainer()) {
|
||||
act = createAction(Action::ExpandAll, p_menu);
|
||||
p_menu->addAction(act);
|
||||
createAndAddAction(Action::ExpandAll, p_menu);
|
||||
}
|
||||
|
||||
p_menu->addSeparator();
|
||||
|
||||
act = createAction(Action::NewNote, p_menu);
|
||||
p_menu->addAction(act);
|
||||
createAndAddAction(Action::NewNote, p_menu);
|
||||
|
||||
act = createAction(Action::NewFolder, p_menu);
|
||||
p_menu->addAction(act);
|
||||
createAndAddAction(Action::NewFolder, p_menu);
|
||||
|
||||
p_menu->addSeparator();
|
||||
|
||||
act = createAction(Action::Copy, p_menu);
|
||||
p_menu->addAction(act);
|
||||
createAndAddAction(Action::Copy, p_menu);
|
||||
|
||||
act = createAction(Action::Cut, p_menu);
|
||||
p_menu->addAction(act);
|
||||
createAndAddAction(Action::Cut, p_menu);
|
||||
|
||||
if (selectedSize == 1 && isPasteOnNodeAvailable(p_node)) {
|
||||
act = createAction(Action::Paste, p_menu);
|
||||
p_menu->addAction(act);
|
||||
createAndAddAction(Action::Paste, p_menu);
|
||||
}
|
||||
|
||||
act = createAction(Action::Delete, p_menu);
|
||||
p_menu->addAction(act);
|
||||
createAndAddAction(Action::Delete, p_menu);
|
||||
|
||||
act = createAction(Action::RemoveFromConfig, p_menu);
|
||||
p_menu->addAction(act);
|
||||
createAndAddAction(Action::RemoveFromConfig, p_menu);
|
||||
|
||||
p_menu->addSeparator();
|
||||
|
||||
act = createAction(Action::Reload, p_menu);
|
||||
p_menu->addAction(act);
|
||||
createAndAddAction(Action::Reload, p_menu);
|
||||
|
||||
act = createAction(Action::ReloadIndex, p_menu);
|
||||
p_menu->addAction(act);
|
||||
createAndAddAction(Action::ReloadIndex, p_menu);
|
||||
|
||||
act = createAction(Action::Sort, p_menu);
|
||||
p_menu->addAction(act);
|
||||
createAndAddAction(Action::Sort, p_menu);
|
||||
|
||||
{
|
||||
if (selectedSize == 1
|
||||
&& m_notebook->tag()
|
||||
&& !p_node->isContainer()) {
|
||||
p_menu->addSeparator();
|
||||
|
||||
act = createAction(Action::PinToQuickAccess, p_menu);
|
||||
p_menu->addAction(act);
|
||||
createAndAddAction(Action::Tag, p_menu);
|
||||
}
|
||||
|
||||
p_menu->addSeparator();
|
||||
|
||||
createAndAddAction(Action::PinToQuickAccess, p_menu);
|
||||
|
||||
if (selectedSize == 1) {
|
||||
p_menu->addSeparator();
|
||||
createAndAddAction(Action::CopyPath, p_menu);
|
||||
|
||||
act = createAction(Action::CopyPath, p_menu);
|
||||
p_menu->addAction(act);
|
||||
createAndAddAction(Action::OpenLocation, p_menu);
|
||||
|
||||
act = createAction(Action::OpenLocation, p_menu);
|
||||
p_menu->addAction(act);
|
||||
|
||||
act = createAction(Action::Properties, p_menu);
|
||||
p_menu->addAction(act);
|
||||
createAndAddAction(Action::Properties, p_menu);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -913,33 +870,22 @@ void NotebookNodeExplorer::createContextMenuOnExternalNode(QMenu *p_menu, const
|
||||
Q_UNUSED(p_node);
|
||||
|
||||
const int selectedSize = m_masterExplorer->selectedItems().size();
|
||||
QAction *act = nullptr;
|
||||
|
||||
act = createAction(Action::Open, p_menu);
|
||||
p_menu->addAction(act);
|
||||
createAndAddAction(Action::Open, p_menu);
|
||||
|
||||
addOpenWithMenu(p_menu);
|
||||
|
||||
p_menu->addSeparator();
|
||||
|
||||
act = createAction(Action::ImportToConfig, p_menu);
|
||||
p_menu->addAction(act);
|
||||
createAndAddAction(Action::ImportToConfig, p_menu);
|
||||
|
||||
{
|
||||
p_menu->addSeparator();
|
||||
p_menu->addSeparator();
|
||||
|
||||
act = createAction(Action::PinToQuickAccess, p_menu);
|
||||
p_menu->addAction(act);
|
||||
}
|
||||
createAndAddAction(Action::PinToQuickAccess, p_menu);
|
||||
|
||||
if (selectedSize == 1) {
|
||||
p_menu->addSeparator();
|
||||
createAndAddAction(Action::CopyPath, p_menu);
|
||||
|
||||
act = createAction(Action::CopyPath, p_menu);
|
||||
p_menu->addAction(act);
|
||||
|
||||
act = createAction(Action::OpenLocation, p_menu);
|
||||
p_menu->addAction(act);
|
||||
createAndAddAction(Action::OpenLocation, p_menu);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1191,31 +1137,73 @@ QAction *NotebookNodeExplorer::createAction(Action p_act, QObject *p_parent)
|
||||
break;
|
||||
|
||||
case Action::PinToQuickAccess:
|
||||
act = new QAction(tr("Pin To &Quick Access"), p_parent);
|
||||
act = new QAction(generateMenuActionIcon(QStringLiteral("quick_access_menu.svg")),
|
||||
tr("Pin To &Quick Access"),
|
||||
p_parent);
|
||||
connect(act, &QAction::triggered,
|
||||
this, [this]() {
|
||||
auto nodes = getSelectedNodes();
|
||||
QStringList files;
|
||||
bool hasFilteredAway = false;
|
||||
for (const auto &node : nodes.first) {
|
||||
if (node->hasContent()) {
|
||||
files.push_back(node->fetchAbsolutePath());
|
||||
} else {
|
||||
hasFilteredAway = true;
|
||||
}
|
||||
}
|
||||
for (const auto &node : nodes.second) {
|
||||
if (!node->isFolder()) {
|
||||
files.push_back(node->fetchAbsolutePath());
|
||||
} else {
|
||||
hasFilteredAway = true;
|
||||
}
|
||||
}
|
||||
if (!files.isEmpty()) {
|
||||
emit VNoteX::getInst().pinToQuickAccessRequested(files);
|
||||
}
|
||||
if (hasFilteredAway) {
|
||||
VNoteX::getInst().showStatusMessageShort(tr("Folder is not supported by quick access"));
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
case Action::Tag:
|
||||
act = new QAction(generateMenuActionIcon(QStringLiteral("tag.svg")), tr("&Tags"), p_parent);
|
||||
connect(act, &QAction::triggered,
|
||||
this, [this]() {
|
||||
auto item = m_masterExplorer->currentItem();
|
||||
if (!item || !m_notebook->tag()) {
|
||||
return;
|
||||
}
|
||||
auto data = getItemNodeData(item);
|
||||
if (data.isNode()) {
|
||||
auto node = data.getNode();
|
||||
if (checkInvalidNode(node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ViewTagsDialog dialog(node, VNoteX::getInst().getMainWindow());
|
||||
dialog.exec();
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
Q_ASSERT(false);
|
||||
break;
|
||||
}
|
||||
|
||||
return act;
|
||||
}
|
||||
|
||||
QAction *NotebookNodeExplorer::createAndAddAction(Action p_act, QMenu *p_menu)
|
||||
{
|
||||
auto act = createAction(p_act, p_menu);
|
||||
p_menu->addAction(act);
|
||||
return act;
|
||||
}
|
||||
|
||||
void NotebookNodeExplorer::copySelectedNodes(bool p_move)
|
||||
{
|
||||
auto nodes = getSelectedNodes().first;
|
||||
@ -2082,3 +2070,23 @@ void NotebookNodeExplorer::loadItemChildren(QTreeWidgetItem *p_item) const
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString NotebookNodeExplorer::generateToolTip(const Node *p_node)
|
||||
{
|
||||
Q_ASSERT(p_node->isLoaded());
|
||||
QString tip = p_node->exists() ? p_node->getName() : (tr("[Invalid] %1").arg(p_node->getName()));
|
||||
tip += QLatin1String("\n\n");
|
||||
|
||||
if (!p_node->getTags().isEmpty()) {
|
||||
const auto &tags = p_node->getTags();
|
||||
QString tagString = tags.first();
|
||||
for (int i = 1; i < tags.size(); ++i) {
|
||||
tagString += QLatin1String("; ") + tags[i];
|
||||
}
|
||||
tip += tr("Tags: %1\n").arg(tagString);
|
||||
}
|
||||
|
||||
tip += tr("Created Time: %1\n").arg(Utils::dateTimeString(p_node->getCreatedTimeUtc().toLocalTime()));
|
||||
tip += tr("Modified Time: %1").arg(Utils::dateTimeString(p_node->getModifiedTimeUtc().toLocalTime()));
|
||||
return tip;
|
||||
}
|
||||
|
@ -115,6 +115,8 @@ namespace vnotex
|
||||
|
||||
Node *currentExploredNode() const;
|
||||
|
||||
static QString generateToolTip(const Node *p_node);
|
||||
|
||||
signals:
|
||||
void nodeActivated(Node *p_node, const QSharedPointer<FileOpenParameters> &p_paras);
|
||||
|
||||
@ -154,7 +156,8 @@ namespace vnotex
|
||||
ImportToConfig,
|
||||
Open,
|
||||
ExpandAll,
|
||||
PinToQuickAccess
|
||||
PinToQuickAccess,
|
||||
Tag
|
||||
};
|
||||
|
||||
void setupUI();
|
||||
@ -214,6 +217,8 @@ namespace vnotex
|
||||
// Factory function to create action.
|
||||
QAction *createAction(Action p_act, QObject *p_parent);
|
||||
|
||||
QAction *createAndAddAction(Action p_act, QMenu *p_menu);
|
||||
|
||||
void copySelectedNodes(bool p_move);
|
||||
|
||||
void pasteNodesFromClipboard();
|
||||
@ -301,19 +306,19 @@ namespace vnotex
|
||||
|
||||
bool m_autoImportExternalFiles = true;
|
||||
|
||||
static QIcon s_folderNodeIcon;
|
||||
enum NodeIcon
|
||||
{
|
||||
FolderNode = 0,
|
||||
FileNode,
|
||||
InvalidFolderNode,
|
||||
InvalidFileNode,
|
||||
RecycleBinNode,
|
||||
ExternalFolderNode,
|
||||
ExternalFileNode,
|
||||
MaxIcons
|
||||
};
|
||||
|
||||
static QIcon s_fileNodeIcon;
|
||||
|
||||
static QIcon s_invalidFolderNodeIcon;
|
||||
|
||||
static QIcon s_invalidFileNodeIcon;
|
||||
|
||||
static QIcon s_recycleBinNodeIcon;
|
||||
|
||||
static QIcon s_externalFolderNodeIcon;
|
||||
|
||||
static QIcon s_externalFileNodeIcon;
|
||||
static QIcon s_nodeIcons[NodeIcon::MaxIcons];
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,11 @@ OutlinePopup::OutlinePopup(QToolButton *p_btn, QWidget *p_parent)
|
||||
m_button(p_btn)
|
||||
{
|
||||
setupUI();
|
||||
|
||||
connect(this, &QMenu::aboutToShow,
|
||||
this, [this]() {
|
||||
m_viewer->setFocus();
|
||||
});
|
||||
}
|
||||
|
||||
void OutlinePopup::setupUI()
|
||||
@ -36,8 +41,6 @@ void OutlinePopup::showEvent(QShowEvent* p_event)
|
||||
{
|
||||
QMenu::showEvent(p_event);
|
||||
|
||||
m_viewer->setFocus();
|
||||
|
||||
// Move it to be right-aligned.
|
||||
if (m_button->isVisible()) {
|
||||
const auto p = pos();
|
||||
|
@ -1,8 +1,6 @@
|
||||
#include "quickselector.h"
|
||||
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidgetAction>
|
||||
#include <QMenu>
|
||||
#include <QLabel>
|
||||
#include <QListWidgetItem>
|
||||
#include <QKeyEvent>
|
||||
@ -72,10 +70,14 @@ void QuickSelector::setupUI(const QString &p_title)
|
||||
mainLayout->addWidget(new QLabel(p_title, this));
|
||||
}
|
||||
|
||||
m_searchLineEdit = dynamic_cast<LineEdit *>(WidgetsFactory::createLineEdit(this));
|
||||
m_searchLineEdit = static_cast<LineEdit *>(WidgetsFactory::createLineEdit(this));
|
||||
m_searchLineEdit->setInputMethodEnabled(false);
|
||||
connect(m_searchLineEdit, &QLineEdit::textEdited,
|
||||
this, &QuickSelector::searchAndFilter);
|
||||
connect(m_searchLineEdit, &QLineEdit::returnPressed,
|
||||
this, [this]() {
|
||||
activateItem(m_itemList->currentItem());
|
||||
});
|
||||
mainLayout->addWidget(m_searchLineEdit);
|
||||
|
||||
setFocusProxy(m_searchLineEdit);
|
||||
@ -154,11 +156,6 @@ bool QuickSelector::eventFilter(QObject *p_obj, QEvent *p_event)
|
||||
m_searchLineEdit->setFocus();
|
||||
}
|
||||
return true;
|
||||
} else if (key == Qt::Key_Enter || key == Qt::Key_Return) {
|
||||
if (p_obj == m_searchLineEdit) {
|
||||
activateItem(m_itemList->currentItem());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return FloatingWidget::eventFilter(p_obj, p_event);
|
||||
|
@ -50,6 +50,11 @@ void SnippetPanel::setupUI()
|
||||
setFocusProxy(m_snippetList);
|
||||
}
|
||||
|
||||
void SnippetPanel::initialize()
|
||||
{
|
||||
updateSnippetList();
|
||||
}
|
||||
|
||||
void SnippetPanel::setupTitleBar(const QString &p_title, QWidget *p_parent)
|
||||
{
|
||||
m_titleBar = new TitleBar(p_title, true, TitleBar::Action::Menu, p_parent);
|
||||
@ -125,16 +130,6 @@ void SnippetPanel::updateSnippetList()
|
||||
updateItemsCountLabel();
|
||||
}
|
||||
|
||||
void SnippetPanel::showEvent(QShowEvent *p_event)
|
||||
{
|
||||
QFrame::showEvent(p_event);
|
||||
|
||||
if (!m_listInitialized) {
|
||||
m_listInitialized = true;
|
||||
updateSnippetList();
|
||||
}
|
||||
}
|
||||
|
||||
void SnippetPanel::handleContextMenuRequested(const QPoint &p_pos)
|
||||
{
|
||||
auto item = m_snippetList->itemAt(p_pos);
|
||||
|
@ -16,12 +16,11 @@ namespace vnotex
|
||||
public:
|
||||
explicit SnippetPanel(QWidget *p_parent = nullptr);
|
||||
|
||||
void initialize();
|
||||
|
||||
signals:
|
||||
void applySnippetRequested(const QString &p_name);
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent *p_event) Q_DECL_OVERRIDE;
|
||||
|
||||
private slots:
|
||||
void newSnippet();
|
||||
|
||||
@ -48,8 +47,6 @@ namespace vnotex
|
||||
|
||||
QListWidget *m_snippetList = nullptr;
|
||||
|
||||
bool m_listInitialized = false;
|
||||
|
||||
bool m_builtInSnippetsVisible = true;
|
||||
};
|
||||
}
|
||||
|
@ -18,7 +18,9 @@ namespace vnotex
|
||||
|
||||
enum
|
||||
{
|
||||
HighlightsRole = 0x0101
|
||||
// Qt::UserRole = 0x0100
|
||||
UserRole2 = 0x0101,
|
||||
HighlightsRole = 0x0102
|
||||
};
|
||||
|
||||
|
||||
|
423
src/widgets/tagexplorer.cpp
Normal file
@ -0,0 +1,423 @@
|
||||
#include "tagexplorer.h"
|
||||
|
||||
#include <QVBoxLayout>
|
||||
#include <QToolButton>
|
||||
#include <QMenu>
|
||||
#include <QListWidgetItem>
|
||||
#include <QTreeWidgetItem>
|
||||
#include <QSplitter>
|
||||
#include <QDebug>
|
||||
#include <QTimer>
|
||||
#include <QAbstractItemModel>
|
||||
|
||||
#include "titlebar.h"
|
||||
|
||||
#include <utils/widgetutils.h>
|
||||
#include <utils/iconutils.h>
|
||||
#include <core/vnotex.h>
|
||||
#include <core/notebookmgr.h>
|
||||
#include <notebook/notebook.h>
|
||||
#include <notebook/tagi.h>
|
||||
#include <core/widgetconfig.h>
|
||||
#include <core/configmgr.h>
|
||||
#include <core/fileopenparameters.h>
|
||||
|
||||
#include "widgetsfactory.h"
|
||||
#include "listwidget.h"
|
||||
#include "treewidget.h"
|
||||
#include "navigationmodemgr.h"
|
||||
#include "notebooknodeexplorer.h"
|
||||
#include "mainwindow.h"
|
||||
#include "messageboxhelper.h"
|
||||
#include "dialogs/newtagdialog.h"
|
||||
#include "dialogs/renametagdialog.h"
|
||||
|
||||
using namespace vnotex;
|
||||
|
||||
TagExplorer::TagExplorer(QWidget *p_parent)
|
||||
: QFrame(p_parent)
|
||||
{
|
||||
initIcons();
|
||||
|
||||
setupUI();
|
||||
}
|
||||
|
||||
void TagExplorer::initIcons()
|
||||
{
|
||||
const auto &themeMgr = VNoteX::getInst().getThemeMgr();
|
||||
m_tagIcon = IconUtils::fetchIcon(themeMgr.getIconFile(QStringLiteral("tag.svg")));
|
||||
m_nodeIcon = IconUtils::fetchIcon(themeMgr.getIconFile(QStringLiteral("file_node.svg")));
|
||||
}
|
||||
|
||||
void TagExplorer::setupUI()
|
||||
{
|
||||
auto mainLayout = new QVBoxLayout(this);
|
||||
WidgetUtils::setContentsMargins(mainLayout);
|
||||
|
||||
setupTitleBar(this);
|
||||
mainLayout->addWidget(m_titleBar);
|
||||
|
||||
m_splitter = new QSplitter(this);
|
||||
mainLayout->addWidget(m_splitter);
|
||||
|
||||
setTwoColumnsEnabled(ConfigMgr::getInst().getWidgetConfig().getTagExplorerTwoColumnsEnabled());
|
||||
|
||||
setupTagTree(m_splitter);
|
||||
m_splitter->addWidget(m_tagTree);
|
||||
|
||||
setupNodeList(m_splitter);
|
||||
m_splitter->addWidget(m_nodeList);
|
||||
|
||||
setFocusProxy(m_tagTree);
|
||||
}
|
||||
|
||||
void TagExplorer::setupTitleBar(QWidget *p_parent)
|
||||
{
|
||||
m_titleBar = new TitleBar(QString(), false, TitleBar::Action::Menu, p_parent);
|
||||
m_titleBar->setActionButtonsAlwaysShown(true);
|
||||
|
||||
auto twoColumnsAct = m_titleBar->addMenuAction(tr("Two Columns"),
|
||||
m_titleBar,
|
||||
[this](bool p_checked) {
|
||||
ConfigMgr::getInst().getWidgetConfig().setTagExplorerTwoColumnsEnabled(p_checked);
|
||||
setTwoColumnsEnabled(p_checked);
|
||||
});
|
||||
twoColumnsAct->setCheckable(true);
|
||||
twoColumnsAct->setChecked(ConfigMgr::getInst().getWidgetConfig().getTagExplorerTwoColumnsEnabled());
|
||||
}
|
||||
|
||||
void TagExplorer::setTwoColumnsEnabled(bool p_enabled)
|
||||
{
|
||||
if (m_splitter) {
|
||||
m_splitter->setOrientation(p_enabled ? Qt::Horizontal : Qt::Vertical);
|
||||
}
|
||||
}
|
||||
|
||||
void TagExplorer::setupTagTree(QWidget *p_parent)
|
||||
{
|
||||
auto timer = new QTimer(this);
|
||||
timer->setSingleShot(true);
|
||||
timer->setInterval(500);
|
||||
connect(timer, &QTimer::timeout,
|
||||
this, &TagExplorer::activateTagItem);
|
||||
|
||||
m_tagTree = new TreeWidget(TreeWidget::ClickSpaceToClearSelection, p_parent);
|
||||
TreeWidget::setupSingleColumnHeaderlessTree(m_tagTree, true, false);
|
||||
TreeWidget::showHorizontalScrollbar(m_tagTree);
|
||||
m_tagTree->setDragDropMode(QAbstractItemView::InternalMove);
|
||||
connect(m_tagTree, &QTreeWidget::currentItemChanged,
|
||||
timer, QOverload<void>::of(&QTimer::start));
|
||||
connect(m_tagTree, &QTreeWidget::itemClicked,
|
||||
timer, QOverload<void>::of(&QTimer::start));
|
||||
connect(m_tagTree, &QTreeWidget::customContextMenuRequested,
|
||||
this, &TagExplorer::handleTagTreeContextMenuRequested);
|
||||
connect(m_tagTree, &TreeWidget::itemMoved,
|
||||
this, &TagExplorer::handleTagMoved);
|
||||
|
||||
m_tagTreeNavigationWrapper.reset(new NavigationModeWrapper<QTreeWidget, QTreeWidgetItem>(m_tagTree));
|
||||
NavigationModeMgr::getInst().registerNavigationTarget(m_tagTreeNavigationWrapper.data());
|
||||
}
|
||||
|
||||
void TagExplorer::setupNodeList(QWidget *p_parent)
|
||||
{
|
||||
m_nodeList = new ListWidget(p_parent);
|
||||
m_nodeList->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
m_nodeList->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
connect(m_nodeList, &QListWidget::customContextMenuRequested,
|
||||
this, &TagExplorer::handleNodeListContextMenuRequested);
|
||||
connect(m_nodeList, &QListWidget::itemActivated,
|
||||
this, &TagExplorer::openItem);
|
||||
|
||||
m_nodeListNavigationWrapper.reset(new NavigationModeWrapper<QListWidget, QListWidgetItem>(m_nodeList));
|
||||
NavigationModeMgr::getInst().registerNavigationTarget(m_nodeListNavigationWrapper.data());
|
||||
}
|
||||
|
||||
QByteArray TagExplorer::saveState() const
|
||||
{
|
||||
return m_splitter->saveState();
|
||||
}
|
||||
|
||||
void TagExplorer::restoreState(const QByteArray &p_data)
|
||||
{
|
||||
m_splitter->restoreState(p_data);
|
||||
}
|
||||
|
||||
void TagExplorer::setNotebook(const QSharedPointer<Notebook> &p_notebook)
|
||||
{
|
||||
if (m_notebook == p_notebook) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_notebook) {
|
||||
disconnect(m_notebook.data(), nullptr, this, nullptr);
|
||||
}
|
||||
|
||||
m_notebook = p_notebook;
|
||||
if (m_notebook) {
|
||||
connect(m_notebook.data(), &Notebook::tagsUpdated,
|
||||
this, &TagExplorer::updateTags);
|
||||
}
|
||||
|
||||
m_lastTagName.clear();
|
||||
|
||||
updateTags();
|
||||
}
|
||||
|
||||
void TagExplorer::updateTags()
|
||||
{
|
||||
m_tagTree->clear();
|
||||
|
||||
auto tagI = m_notebook ? m_notebook->tag() : nullptr;
|
||||
if (!tagI) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto &topLevelTags = tagI->getTopLevelTags();
|
||||
for (const auto &tag : topLevelTags) {
|
||||
auto item = new QTreeWidgetItem(m_tagTree);
|
||||
fillTagItem(tag, item);
|
||||
loadTagChildren(tag, item);
|
||||
}
|
||||
|
||||
m_tagTree->expandAll();
|
||||
|
||||
scrollToTag(m_lastTagName);
|
||||
}
|
||||
|
||||
void TagExplorer::loadTagChildren(const QSharedPointer<Tag> &p_tag, QTreeWidgetItem *p_parentItem)
|
||||
{
|
||||
for (const auto &child : p_tag->getChildren()) {
|
||||
auto item = new QTreeWidgetItem(p_parentItem);
|
||||
fillTagItem(child, item);
|
||||
loadTagChildren(child, item);
|
||||
}
|
||||
}
|
||||
|
||||
void TagExplorer::fillTagItem(const QSharedPointer<Tag> &p_tag, QTreeWidgetItem *p_item) const
|
||||
{
|
||||
p_item->setText(Column::Name, p_tag->name());
|
||||
p_item->setToolTip(Column::Name, p_tag->name());
|
||||
p_item->setIcon(Column::Name, m_tagIcon);
|
||||
p_item->setData(Column::Name, Qt::UserRole, p_tag->name());
|
||||
}
|
||||
|
||||
void TagExplorer::activateTagItem()
|
||||
{
|
||||
auto items = m_tagTree->selectedItems();
|
||||
if (items.size() != 1) {
|
||||
m_lastTagName.clear();
|
||||
m_nodeList->clear();
|
||||
return;
|
||||
}
|
||||
|
||||
m_lastTagName = itemTag(items[0]);
|
||||
updateNodeList(m_lastTagName);
|
||||
}
|
||||
|
||||
QString TagExplorer::itemTag(const QTreeWidgetItem *p_item) const
|
||||
{
|
||||
return p_item->data(Column::Name, Qt::UserRole).toString();
|
||||
}
|
||||
|
||||
QString TagExplorer::itemNode(const QListWidgetItem *p_item) const
|
||||
{
|
||||
return p_item->data(Qt::UserRole).toString();
|
||||
}
|
||||
|
||||
void TagExplorer::updateNodeList(const QString &p_tag)
|
||||
{
|
||||
m_nodeList->clear();
|
||||
|
||||
Q_ASSERT(m_notebook);
|
||||
auto tagI = m_notebook->tag();
|
||||
Q_ASSERT(tagI);
|
||||
const auto nodePaths = tagI->findNodesOfTag(p_tag);
|
||||
for (const auto &pa : nodePaths) {
|
||||
auto node = m_notebook->loadNodeByPath(pa);
|
||||
if (!node) {
|
||||
qWarning() << "node belongs to tag in DB but not exists" << p_tag << pa;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (m_notebook->isNodeInRecycleBin(node.data())) {
|
||||
qDebug() << "skipped node in recycle bin" << p_tag << pa;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto item = new QListWidgetItem(m_nodeList);
|
||||
item->setText(node->getName());
|
||||
item->setToolTip(NotebookNodeExplorer::generateToolTip(node.data()));
|
||||
item->setIcon(m_nodeIcon);
|
||||
item->setData(Qt::UserRole, pa);
|
||||
}
|
||||
|
||||
VNoteX::getInst().showStatusMessageShort(tr("Search of tag succeeded: %1").arg(p_tag));
|
||||
}
|
||||
|
||||
void TagExplorer::handleNodeListContextMenuRequested(const QPoint &p_pos)
|
||||
{
|
||||
if (!m_notebook) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto item = m_nodeList->itemAt(p_pos);
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
QMenu menu(this);
|
||||
|
||||
const int selectedCount = m_nodeList->selectedItems().size();
|
||||
|
||||
menu.addAction(tr("&Open"),
|
||||
&menu,
|
||||
[this]() {
|
||||
const auto selectedItems = m_nodeList->selectedItems();
|
||||
for (const auto &selectedItem : selectedItems) {
|
||||
openItem(selectedItem);
|
||||
}
|
||||
});
|
||||
|
||||
if (selectedCount == 1) {
|
||||
menu.addAction(tr("&Locate Node"),
|
||||
&menu,
|
||||
[this]() {
|
||||
auto item = m_nodeList->currentItem();
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto node = m_notebook->loadNodeByPath(itemNode(item));
|
||||
Q_ASSERT(node);
|
||||
if (node) {
|
||||
emit VNoteX::getInst().locateNodeRequested(node.data());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
menu.exec(m_nodeList->mapToGlobal(p_pos));
|
||||
}
|
||||
|
||||
void TagExplorer::openItem(const QListWidgetItem *p_item)
|
||||
{
|
||||
if (!p_item) {
|
||||
return;
|
||||
}
|
||||
|
||||
Q_ASSERT(m_notebook);
|
||||
auto node = m_notebook->loadNodeByPath(itemNode(p_item));
|
||||
if (node) {
|
||||
emit VNoteX::getInst().openNodeRequested(node.data(), QSharedPointer<FileOpenParameters>::create());
|
||||
}
|
||||
}
|
||||
|
||||
void TagExplorer::handleTagTreeContextMenuRequested(const QPoint &p_pos)
|
||||
{
|
||||
if (!m_notebook) {
|
||||
return;
|
||||
}
|
||||
|
||||
QMenu menu(this);
|
||||
|
||||
auto item = m_tagTree->itemAt(p_pos);
|
||||
|
||||
menu.addAction(tr("&New Tag"), this, &TagExplorer::newTag);
|
||||
|
||||
if (item && m_tagTree->selectedItems().size() == 1) {
|
||||
menu.addAction(tr("&Rename"), this, &TagExplorer::renameTag);
|
||||
|
||||
menu.addAction(tr("&Delete"), this, &TagExplorer::removeTag);
|
||||
}
|
||||
|
||||
menu.exec(m_tagTree->mapToGlobal(p_pos));
|
||||
}
|
||||
|
||||
void TagExplorer::newTag()
|
||||
{
|
||||
Q_ASSERT(m_notebook);
|
||||
|
||||
QSharedPointer<Tag> parentTag;
|
||||
|
||||
auto item = m_tagTree->currentItem();
|
||||
if (item) {
|
||||
const auto tagName = itemTag(item);
|
||||
parentTag = m_notebook->tag()->findTag(tagName);
|
||||
Q_ASSERT(parentTag);
|
||||
}
|
||||
|
||||
NewTagDialog dialog(m_notebook->tag(), parentTag.data(), VNoteX::getInst().getMainWindow());
|
||||
dialog.exec();
|
||||
}
|
||||
|
||||
void TagExplorer::renameTag()
|
||||
{
|
||||
Q_ASSERT(m_notebook);
|
||||
auto item = m_tagTree->currentItem();
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
RenameTagDialog dialog(m_notebook->tag(), itemTag(item), VNoteX::getInst().getMainWindow());
|
||||
if (dialog.exec() == QDialog::Accepted) {
|
||||
scrollToTag(dialog.getTagName());
|
||||
}
|
||||
}
|
||||
|
||||
void TagExplorer::removeTag()
|
||||
{
|
||||
Q_ASSERT(m_notebook);
|
||||
auto item = m_tagTree->currentItem();
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto tagName = itemTag(item);
|
||||
int okRet = MessageBoxHelper::questionOkCancel(MessageBoxHelper::Warning,
|
||||
tr("Delete the tag and all its chlidren tags?"),
|
||||
tr("Only tags and the references of them will be deleted."),
|
||||
QString(),
|
||||
VNoteX::getInst().getMainWindow());
|
||||
if (okRet != QMessageBox::Ok) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_notebook->tag()->removeTag(tagName)) {
|
||||
VNoteX::getInst().showStatusMessageShort(tr("Tag deleted"));
|
||||
} else {
|
||||
VNoteX::getInst().showStatusMessageShort(tr("Failed to delete tag: %1").arg(tagName));
|
||||
}
|
||||
}
|
||||
|
||||
void TagExplorer::handleTagMoved(QTreeWidgetItem *p_item)
|
||||
{
|
||||
const auto tagName = itemTag(p_item);
|
||||
auto tag = m_notebook->tag()->findTag(tagName);
|
||||
Q_ASSERT(tag);
|
||||
const auto oldParentName = tag->getParent() ? tag->getParent()->name() : QString();
|
||||
const auto newParentName = p_item->parent() ? itemTag(p_item->parent()) : QString();
|
||||
if (oldParentName == newParentName) {
|
||||
// Sorting tags is not supported for now.
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << "re-parent tag" << tagName << oldParentName << "->" << newParentName;
|
||||
bool ret = m_notebook->tag()->moveTag(tagName, newParentName);
|
||||
if (!ret) {
|
||||
MessageBoxHelper::notify(MessageBoxHelper::Type::Warning,
|
||||
tr("Failed to move tag (%1).").arg(tagName),
|
||||
VNoteX::getInst().getMainWindow());
|
||||
}
|
||||
}
|
||||
|
||||
void TagExplorer::scrollToTag(const QString &p_name)
|
||||
{
|
||||
if (p_name.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto item = TreeWidget::findItem(m_tagTree, p_name, Column::Name);
|
||||
if (item) {
|
||||
m_tagTree->setCurrentItem(item);
|
||||
m_tagTree->scrollToItem(item);
|
||||
}
|
||||
}
|
105
src/widgets/tagexplorer.h
Normal file
@ -0,0 +1,105 @@
|
||||
#ifndef TAGEXPLORER_H
|
||||
#define TAGEXPLORER_H
|
||||
|
||||
#include <QFrame>
|
||||
#include <QSharedPointer>
|
||||
#include <QScopedPointer>
|
||||
|
||||
#include "navigationmodewrapper.h"
|
||||
|
||||
class QListWidget;
|
||||
class QListWidgetItem;
|
||||
class QTreeWidget;
|
||||
class QTreeWidgetItem;
|
||||
class QSplitter;
|
||||
|
||||
namespace vnotex
|
||||
{
|
||||
class TitleBar;
|
||||
class Notebook;
|
||||
class Tag;
|
||||
class TreeWidget;
|
||||
|
||||
class TagExplorer : public QFrame
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit TagExplorer(QWidget *p_parent = nullptr);
|
||||
|
||||
QByteArray saveState() const;
|
||||
|
||||
void restoreState(const QByteArray &p_data);
|
||||
|
||||
public slots:
|
||||
void setNotebook(const QSharedPointer<Notebook> &p_notebook);
|
||||
|
||||
private slots:
|
||||
void handleNodeListContextMenuRequested(const QPoint &p_pos);
|
||||
|
||||
void handleTagTreeContextMenuRequested(const QPoint &p_pos);
|
||||
|
||||
void handleTagMoved(QTreeWidgetItem *p_item);
|
||||
|
||||
private:
|
||||
enum Column { Name = 0 };
|
||||
|
||||
void initIcons();
|
||||
|
||||
void setupUI();
|
||||
|
||||
void setupTitleBar(QWidget *p_parent = nullptr);
|
||||
|
||||
void setupTagTree(QWidget *p_parent = nullptr);
|
||||
|
||||
void setupNodeList(QWidget *p_parent = nullptr);
|
||||
|
||||
void setTwoColumnsEnabled(bool p_enabled);
|
||||
|
||||
void updateTags();
|
||||
|
||||
void loadTagChildren(const QSharedPointer<Tag> &p_tag, QTreeWidgetItem *p_parentItem);
|
||||
|
||||
void fillTagItem(const QSharedPointer<Tag> &p_tag, QTreeWidgetItem *p_item) const;
|
||||
|
||||
void activateTagItem();
|
||||
|
||||
QString itemTag(const QTreeWidgetItem *p_item) const;
|
||||
|
||||
QString itemNode(const QListWidgetItem *p_item) const;
|
||||
|
||||
void updateNodeList(const QString &p_tag);
|
||||
|
||||
void openItem(const QListWidgetItem *p_item);
|
||||
|
||||
void newTag();
|
||||
|
||||
void renameTag();
|
||||
|
||||
void removeTag();
|
||||
|
||||
void scrollToTag(const QString &p_name);
|
||||
|
||||
QSharedPointer<Notebook> m_notebook;
|
||||
|
||||
// Used to cache current selected tag after update.
|
||||
QString m_lastTagName;
|
||||
|
||||
TitleBar *m_titleBar = nullptr;
|
||||
|
||||
QSplitter *m_splitter = nullptr;
|
||||
|
||||
TreeWidget *m_tagTree = nullptr;
|
||||
|
||||
QScopedPointer<NavigationModeWrapper<QTreeWidget, QTreeWidgetItem>> m_tagTreeNavigationWrapper;
|
||||
|
||||
QListWidget *m_nodeList = nullptr;
|
||||
|
||||
QScopedPointer<NavigationModeWrapper<QListWidget, QListWidgetItem>> m_nodeListNavigationWrapper;
|
||||
|
||||
QIcon m_tagIcon;
|
||||
|
||||
QIcon m_nodeIcon;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // TAGEXPLORER_H
|
49
src/widgets/tagpopup.cpp
Normal file
@ -0,0 +1,49 @@
|
||||
#include "tagpopup.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include <utils/widgetutils.h>
|
||||
#include <buffer/buffer.h>
|
||||
|
||||
#include "tagviewer.h"
|
||||
|
||||
using namespace vnotex;
|
||||
|
||||
TagPopup::TagPopup(QToolButton *p_btn, QWidget *p_parent)
|
||||
: QMenu(p_parent),
|
||||
m_button(p_btn)
|
||||
{
|
||||
setupUI();
|
||||
|
||||
connect(this, &QMenu::aboutToShow,
|
||||
this, [this]() {
|
||||
m_tagViewer->setNode(m_buffer ? m_buffer->getNode() : nullptr);
|
||||
// Enable input method.
|
||||
m_tagViewer->activateWindow();
|
||||
m_tagViewer->setFocus();
|
||||
});
|
||||
|
||||
connect(this, &QMenu::aboutToHide,
|
||||
m_tagViewer, &TagViewer::save);
|
||||
}
|
||||
|
||||
void TagPopup::setupUI()
|
||||
{
|
||||
auto mainLayout = new QVBoxLayout(this);
|
||||
WidgetUtils::setContentsMargins(mainLayout);
|
||||
|
||||
m_tagViewer = new TagViewer(this);
|
||||
mainLayout->addWidget(m_tagViewer);
|
||||
|
||||
setMinimumSize(256, 320);
|
||||
}
|
||||
|
||||
void TagPopup::setBuffer(Buffer *p_buffer)
|
||||
{
|
||||
if (m_buffer == p_buffer) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_buffer = p_buffer;
|
||||
}
|
34
src/widgets/tagpopup.h
Normal file
@ -0,0 +1,34 @@
|
||||
#ifndef TAGPOPUP_H
|
||||
#define TAGPOPUP_H
|
||||
|
||||
#include <QMenu>
|
||||
|
||||
class QToolButton;
|
||||
|
||||
namespace vnotex
|
||||
{
|
||||
class Buffer;
|
||||
class TagViewer;
|
||||
|
||||
class TagPopup : public QMenu
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
TagPopup(QToolButton *p_btn, QWidget *p_parent = nullptr);
|
||||
|
||||
void setBuffer(Buffer *p_buffer);
|
||||
|
||||
private:
|
||||
void setupUI();
|
||||
|
||||
Buffer *m_buffer = nullptr;
|
||||
|
||||
// Button for this menu.
|
||||
QToolButton *m_button = nullptr;
|
||||
|
||||
// Managed by QObject.
|
||||
TagViewer *m_tagViewer = nullptr;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // TAGPOPUP_H
|
323
src/widgets/tagviewer.cpp
Normal file
@ -0,0 +1,323 @@
|
||||
#include "tagviewer.h"
|
||||
|
||||
#include <QVBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QListWidgetItem>
|
||||
#include <QKeyEvent>
|
||||
#include <QRegularExpression>
|
||||
#include <QGuiApplication>
|
||||
#include <QRegularExpressionValidator>
|
||||
#include <QRegularExpression>
|
||||
#include <QHash>
|
||||
|
||||
#include <utils/widgetutils.h>
|
||||
#include <utils/iconutils.h>
|
||||
#include <notebook/tagi.h>
|
||||
#include <notebook/node.h>
|
||||
#include <notebook/notebook.h>
|
||||
#include <core/vnotex.h>
|
||||
|
||||
#include "lineedit.h"
|
||||
#include "listwidget.h"
|
||||
#include "widgetsfactory.h"
|
||||
#include "styleditemdelegate.h"
|
||||
#include "messageboxhelper.h"
|
||||
#include "mainwindow.h"
|
||||
|
||||
using namespace vnotex;
|
||||
|
||||
QIcon TagViewer::s_tagIcon;
|
||||
|
||||
QIcon TagViewer::s_selectedTagIcon;
|
||||
|
||||
TagViewer::TagViewer(QWidget *p_parent)
|
||||
: QFrame(p_parent)
|
||||
{
|
||||
initIcons();
|
||||
|
||||
setupUI();
|
||||
}
|
||||
|
||||
void TagViewer::setupUI()
|
||||
{
|
||||
auto mainLayout = new QVBoxLayout(this);
|
||||
|
||||
m_searchLineEdit = static_cast<LineEdit *>(WidgetsFactory::createLineEdit(this));
|
||||
m_searchLineEdit->setPlaceholderText(tr("Enter to add a tag"));
|
||||
m_searchLineEdit->setToolTip(tr("[Shift+Enter] to add current selected tag in the list"));
|
||||
connect(m_searchLineEdit, &QLineEdit::textChanged,
|
||||
this, &TagViewer::searchAndFilter);
|
||||
connect(m_searchLineEdit, &QLineEdit::returnPressed,
|
||||
this, &TagViewer::handleSearchLineEditReturnPressed);
|
||||
mainLayout->addWidget(m_searchLineEdit);
|
||||
|
||||
auto tagNameValidator = new QRegularExpressionValidator(QRegularExpression("[^>]*"), m_searchLineEdit);
|
||||
m_searchLineEdit->setValidator(tagNameValidator);
|
||||
|
||||
setFocusProxy(m_searchLineEdit);
|
||||
m_searchLineEdit->installEventFilter(this);
|
||||
|
||||
m_tagList = new ListWidget(this);
|
||||
m_tagList->setWrapping(true);
|
||||
m_tagList->setFlow(QListView::LeftToRight);
|
||||
m_tagList->setIconSize(QSize(18, 18));
|
||||
connect(m_tagList, &QListWidget::itemClicked,
|
||||
this, &TagViewer::toggleItemTag);
|
||||
connect(m_tagList, &QListWidget::itemActivated,
|
||||
this, &TagViewer::toggleItemTag);
|
||||
mainLayout->addWidget(m_tagList);
|
||||
|
||||
m_tagList->installEventFilter(this);
|
||||
}
|
||||
|
||||
bool TagViewer::eventFilter(QObject *p_obj, QEvent *p_event)
|
||||
{
|
||||
if ((p_obj == m_searchLineEdit || p_obj == m_tagList)
|
||||
&& p_event->type() == QEvent::KeyPress) {
|
||||
auto keyEve = static_cast<QKeyEvent *>(p_event);
|
||||
const auto key = keyEve->key();
|
||||
if (key == Qt::Key_Tab || key == Qt::Key_Backtab) {
|
||||
// Change focus.
|
||||
if (p_obj == m_searchLineEdit) {
|
||||
m_tagList->setFocus();
|
||||
} else {
|
||||
m_searchLineEdit->setFocus();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return QFrame::eventFilter(p_obj, p_event);
|
||||
}
|
||||
|
||||
void TagViewer::setNode(Node *p_node)
|
||||
{
|
||||
// Since there may be update on tags, always update the list.
|
||||
// When first time viewing the tags of one node, it is a good chance to sync the node's tag to DB.
|
||||
if (m_node != p_node) {
|
||||
m_node = p_node;
|
||||
if (m_node) {
|
||||
bool ret = tagI()->updateNodeTags(m_node);
|
||||
if (!ret) {
|
||||
qWarning() << "failed to update tags of node" << m_node->fetchPath();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_hasChange = false;
|
||||
|
||||
updateTagList();
|
||||
}
|
||||
|
||||
void TagViewer::updateTagList()
|
||||
{
|
||||
m_tagList->clear();
|
||||
if (!m_node) {
|
||||
return;
|
||||
}
|
||||
|
||||
QSet<QString> tagsAdded;
|
||||
const auto &nodeTags = m_node->getTags();
|
||||
for (const auto &tag : nodeTags) {
|
||||
if (tagsAdded.contains(tag)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
tagsAdded.insert(tag);
|
||||
addTagItem(tag, true);
|
||||
}
|
||||
|
||||
const auto &allTags = tagI()->getTopLevelTags();
|
||||
for (const auto &tag : allTags) {
|
||||
addTags(tag, tagsAdded);
|
||||
}
|
||||
|
||||
if (!tagsAdded.isEmpty()) {
|
||||
m_tagList->setCurrentRow(0);
|
||||
// Qt's BUG: need to set it again to make it in grid form after setCurrentRow().
|
||||
m_tagList->setWrapping(true);
|
||||
}
|
||||
}
|
||||
|
||||
void TagViewer::addTags(const QSharedPointer<Tag> &p_tag, QSet<QString> &p_addedTags)
|
||||
{
|
||||
// Itself.
|
||||
if (!p_addedTags.contains(p_tag->name())) {
|
||||
p_addedTags.insert(p_tag->name());
|
||||
addTagItem(p_tag->name(), false);
|
||||
}
|
||||
|
||||
// Children.
|
||||
for (const auto &child : p_tag->getChildren()) {
|
||||
addTags(child, p_addedTags);
|
||||
}
|
||||
}
|
||||
|
||||
void TagViewer::initIcons()
|
||||
{
|
||||
if (!s_tagIcon.isNull()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto &themeMgr = VNoteX::getInst().getThemeMgr();
|
||||
s_tagIcon = IconUtils::fetchIcon(themeMgr.getIconFile(QStringLiteral("tag.svg")));
|
||||
s_selectedTagIcon = IconUtils::fetchIcon(themeMgr.getIconFile(QStringLiteral("tag_selected.svg")));
|
||||
}
|
||||
|
||||
void TagViewer::addTagItem(const QString &p_tagName, bool p_selected, bool p_prepend)
|
||||
{
|
||||
auto item = new QListWidgetItem(p_tagName);
|
||||
if (!p_prepend) {
|
||||
m_tagList->addItem(item);
|
||||
} else {
|
||||
m_tagList->insertItem(0, item);
|
||||
}
|
||||
|
||||
item->setToolTip(p_tagName);
|
||||
item->setData(Qt::UserRole, p_tagName);
|
||||
setItemTagSelected(item, p_selected);
|
||||
}
|
||||
|
||||
QString TagViewer::itemTag(const QListWidgetItem *p_item) const
|
||||
{
|
||||
return p_item->data(Qt::UserRole).toString();
|
||||
}
|
||||
|
||||
bool TagViewer::isItemTagSelected(const QListWidgetItem *p_item) const
|
||||
{
|
||||
return p_item->data(UserRole2).toBool();
|
||||
}
|
||||
|
||||
TagI *TagViewer::tagI()
|
||||
{
|
||||
return m_node->getNotebook()->tag();
|
||||
}
|
||||
|
||||
void TagViewer::searchAndFilter(const QString &p_text)
|
||||
{
|
||||
// Take the last tag for search.
|
||||
const auto text = p_text.trimmed();
|
||||
|
||||
if (text.isEmpty()) {
|
||||
// Show all items.
|
||||
filterItems([](const QListWidgetItem *) {
|
||||
return true;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
filterItems([this, &text](const QListWidgetItem *p_item) {
|
||||
if (itemTag(p_item).contains(text)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
void TagViewer::filterItems(const std::function<bool(const QListWidgetItem *)> &p_judge)
|
||||
{
|
||||
QListWidgetItem *firstHit = nullptr;
|
||||
ListWidget::forEachItem(m_tagList, [&firstHit, &p_judge](QListWidgetItem *itemIter) {
|
||||
if (p_judge(itemIter)) {
|
||||
if (!firstHit) {
|
||||
firstHit = itemIter;
|
||||
}
|
||||
itemIter->setHidden(false);
|
||||
} else {
|
||||
itemIter->setHidden(true);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
m_tagList->setCurrentItem(firstHit);
|
||||
}
|
||||
|
||||
void TagViewer::handleSearchLineEditReturnPressed()
|
||||
{
|
||||
if (QGuiApplication::keyboardModifiers() == Qt::ShiftModifier) {
|
||||
// Add current selected tag in the list.
|
||||
auto item = m_tagList->currentItem();
|
||||
if (item && !isItemTagSelected(item)) {
|
||||
setItemTagSelected(item, true);
|
||||
m_searchLineEdit->clear();
|
||||
m_hasChange = true;
|
||||
}
|
||||
} else {
|
||||
// Decode input text and add tags.
|
||||
const auto tagName = m_searchLineEdit->text().trimmed();
|
||||
if (tagName.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto item = findItem(tagName)) {
|
||||
// Add existing tag.
|
||||
setItemTagSelected(item, true);
|
||||
} else {
|
||||
// Add new tag.
|
||||
addTagItem(tagName, true, true);
|
||||
}
|
||||
|
||||
m_searchLineEdit->clear();
|
||||
m_hasChange = true;
|
||||
}
|
||||
}
|
||||
|
||||
void TagViewer::toggleItemTag(QListWidgetItem *p_item)
|
||||
{
|
||||
m_hasChange = true;
|
||||
setItemTagSelected(p_item, !isItemTagSelected(p_item));
|
||||
}
|
||||
|
||||
void TagViewer::setItemTagSelected(QListWidgetItem *p_item, bool p_selected)
|
||||
{
|
||||
p_item->setIcon(p_selected ? s_selectedTagIcon : s_tagIcon);
|
||||
p_item->setData(UserRole2, p_selected);
|
||||
}
|
||||
|
||||
QListWidgetItem *TagViewer::findItem(const QString &p_tagName) const
|
||||
{
|
||||
return ListWidget::findItem(m_tagList, p_tagName);
|
||||
}
|
||||
|
||||
void TagViewer::save()
|
||||
{
|
||||
if (!m_node || !m_hasChange) {
|
||||
return;
|
||||
}
|
||||
|
||||
QHash<QString, int> selectedTags;
|
||||
ListWidget::forEachItem(m_tagList, [this, &selectedTags](QListWidgetItem *itemIter) {
|
||||
if (isItemTagSelected(itemIter)) {
|
||||
selectedTags.insert(itemTag(itemIter), 0);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if (selectedTags.size() == m_node->getTags().size()) {
|
||||
bool same = true;
|
||||
for (const auto &tag : m_node->getTags()) {
|
||||
auto iter = selectedTags.find(tag);
|
||||
if (iter == selectedTags.end()) {
|
||||
same = false;
|
||||
break;
|
||||
} else {
|
||||
iter.value()++;
|
||||
if (iter.value() > 1) {
|
||||
same = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (same) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool ret = tagI()->updateNodeTags(m_node, selectedTags.keys());
|
||||
if (ret) {
|
||||
VNoteX::getInst().showStatusMessageShort(tr("Tags updated: %1").arg(m_node->getTags().join(QLatin1String("; "))));
|
||||
} else {
|
||||
MessageBoxHelper::notify(MessageBoxHelper::Type::Warning,
|
||||
tr("Failed to update tags of node (%1).").arg(m_node->getName()),
|
||||
VNoteX::getInst().getMainWindow());
|
||||
}
|
||||
}
|
79
src/widgets/tagviewer.h
Normal file
@ -0,0 +1,79 @@
|
||||
#ifndef TAGVIEWER_H
|
||||
#define TAGVIEWER_H
|
||||
|
||||
#include <QFrame>
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <QIcon>
|
||||
#include <QSharedPointer>
|
||||
#include <QSet>
|
||||
|
||||
class QListWidget;
|
||||
class QListWidgetItem;
|
||||
|
||||
namespace vnotex
|
||||
{
|
||||
class LineEdit;
|
||||
class Node;
|
||||
class TagI;
|
||||
class Tag;
|
||||
|
||||
class TagViewer : public QFrame
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit TagViewer(QWidget *p_parent = nullptr);
|
||||
|
||||
void setNode(Node *p_node);
|
||||
|
||||
void save();
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *p_obj, QEvent *p_event) Q_DECL_OVERRIDE;
|
||||
|
||||
private:
|
||||
void setupUI();
|
||||
|
||||
void updateTagList();
|
||||
|
||||
TagI *tagI();
|
||||
|
||||
void addTagItem(const QString &p_tagName, bool p_selected, bool p_prepend = false);
|
||||
|
||||
QString itemTag(const QListWidgetItem *p_item) const;
|
||||
|
||||
bool isItemTagSelected(const QListWidgetItem *p_item) const;
|
||||
|
||||
void addTags(const QSharedPointer<Tag> &p_tag, QSet<QString> &p_addedTags);
|
||||
|
||||
void searchAndFilter(const QString &p_text);
|
||||
|
||||
void filterItems(const std::function<bool(const QListWidgetItem *)> &p_judge);
|
||||
|
||||
void handleSearchLineEditReturnPressed();
|
||||
|
||||
void toggleItemTag(QListWidgetItem *p_item);
|
||||
|
||||
void setItemTagSelected(QListWidgetItem *p_item, bool p_selected);
|
||||
|
||||
QListWidgetItem *findItem(const QString &p_tagName) const;
|
||||
|
||||
static void initIcons();
|
||||
|
||||
// View the tags of @m_node.
|
||||
Node *m_node = nullptr;
|
||||
|
||||
bool m_hasChange = false;
|
||||
|
||||
LineEdit *m_searchLineEdit = nullptr;
|
||||
|
||||
QListWidget *m_tagList = nullptr;
|
||||
|
||||
static QIcon s_tagIcon;
|
||||
|
||||
static QIcon s_selectedTagIcon;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // TAGVIEWER_H
|
@ -68,6 +68,8 @@ void TextViewWindow::setupToolBar()
|
||||
|
||||
addAction(toolBar, ViewWindowToolBarHelper::Attachment);
|
||||
|
||||
addAction(toolBar, ViewWindowToolBarHelper::Tag);
|
||||
|
||||
ToolBarHelper::addSpacer(toolBar);
|
||||
addAction(toolBar, ViewWindowToolBarHelper::FindAndReplace);
|
||||
}
|
||||
|
@ -352,7 +352,7 @@ QToolBar *ToolBarHelper::setupSettingsToolBar(MainWindow *p_win, QToolBar *p_too
|
||||
menu->addAction(fullScreenAct);
|
||||
}
|
||||
|
||||
auto stayOnTopAct = menu->addAction(generateIcon("stay_on_top.svg"), MainWindow::tr("Stay On Top"),
|
||||
auto stayOnTopAct = menu->addAction(generateIcon("stay_on_top.svg"), MainWindow::tr("Stay on Top"),
|
||||
p_win, &MainWindow::setStayOnTop);
|
||||
stayOnTopAct->setCheckable(true);
|
||||
WidgetUtils::addActionShortcut(stayOnTopAct,
|
||||
|
@ -58,11 +58,11 @@ void TreeWidget::showHorizontalScrollbar(QTreeWidget *p_tree)
|
||||
p_tree->header()->setStretchLastSection(false);
|
||||
}
|
||||
|
||||
QTreeWidgetItem *TreeWidget::findItem(const QTreeWidget *p_widget, const QVariant &p_data)
|
||||
QTreeWidgetItem *TreeWidget::findItem(const QTreeWidget *p_widget, const QVariant &p_data, int p_column)
|
||||
{
|
||||
int nrTop = p_widget->topLevelItemCount();
|
||||
for (int i = 0; i < nrTop; ++i) {
|
||||
auto item = findItemHelper(p_widget->topLevelItem(i), p_data);
|
||||
auto item = findItemHelper(p_widget->topLevelItem(i), p_data, p_column);
|
||||
if (item) {
|
||||
return item;
|
||||
}
|
||||
@ -71,7 +71,7 @@ QTreeWidgetItem *TreeWidget::findItem(const QTreeWidget *p_widget, const QVarian
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QTreeWidgetItem *TreeWidget::findItemHelper(QTreeWidgetItem *p_item, const QVariant &p_data)
|
||||
QTreeWidgetItem *TreeWidget::findItemHelper(QTreeWidgetItem *p_item, const QVariant &p_data, int p_column)
|
||||
{
|
||||
if (!p_item) {
|
||||
return nullptr;
|
||||
@ -83,7 +83,7 @@ QTreeWidgetItem *TreeWidget::findItemHelper(QTreeWidgetItem *p_item, const QVari
|
||||
|
||||
int nrChild = p_item->childCount();
|
||||
for (int i = 0; i < nrChild; ++i) {
|
||||
auto item = findItemHelper(p_item->child(i), p_data);
|
||||
auto item = findItemHelper(p_item->child(i), p_data, p_column);
|
||||
if (item) {
|
||||
return item;
|
||||
}
|
||||
@ -217,24 +217,9 @@ void TreeWidget::dropEvent(QDropEvent *p_event)
|
||||
{
|
||||
auto dragItems = selectedItems();
|
||||
|
||||
int first = -1, last = -1;
|
||||
QTreeWidgetItem *firstItem = NULL;
|
||||
for (int i = 0; i < dragItems.size(); ++i) {
|
||||
int row = indexFromItem(dragItems[i]).row();
|
||||
if (row > last) {
|
||||
last = row;
|
||||
}
|
||||
|
||||
if (first == -1 || row < first) {
|
||||
first = row;
|
||||
firstItem = dragItems[i];
|
||||
}
|
||||
}
|
||||
|
||||
Q_ASSERT(firstItem);
|
||||
|
||||
QTreeWidget::dropEvent(p_event);
|
||||
|
||||
int target = indexFromItem(firstItem).row();
|
||||
emit rowsMoved(first, last, target);
|
||||
if (dragItems.size() == 1) {
|
||||
emit itemMoved(dragItems[0]);
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ namespace vnotex
|
||||
|
||||
static void showHorizontalScrollbar(QTreeWidget *p_tree);
|
||||
|
||||
static QTreeWidgetItem *findItem(const QTreeWidget *p_widget, const QVariant &p_data);
|
||||
static QTreeWidgetItem *findItem(const QTreeWidget *p_widget, const QVariant &p_data, int p_column = 0);
|
||||
|
||||
// Next visible item.
|
||||
static QTreeWidgetItem *nextItem(const QTreeWidget* p_tree,
|
||||
@ -36,8 +36,8 @@ namespace vnotex
|
||||
static QVector<QTreeWidgetItem *> getVisibleItems(const QTreeWidget *p_widget);
|
||||
|
||||
signals:
|
||||
// Rows [@p_first, @p_last] were moved to @p_row.
|
||||
void rowsMoved(int p_first, int p_last, int p_row);
|
||||
// Emit when single item is selected and Drag&Drop to move internally.
|
||||
void itemMoved(QTreeWidgetItem *p_item);
|
||||
|
||||
protected:
|
||||
void mousePressEvent(QMouseEvent *p_event) Q_DECL_OVERRIDE;
|
||||
@ -47,7 +47,7 @@ namespace vnotex
|
||||
void dropEvent(QDropEvent *p_event) Q_DECL_OVERRIDE;
|
||||
|
||||
private:
|
||||
static QTreeWidgetItem *findItemHelper(QTreeWidgetItem *p_item, const QVariant &p_data);
|
||||
static QTreeWidgetItem *findItemHelper(QTreeWidgetItem *p_item, const QVariant &p_data, int p_column);
|
||||
|
||||
static QTreeWidgetItem *nextSibling(const QTreeWidget *p_widget,
|
||||
QTreeWidgetItem *p_item,
|
||||
|
@ -1292,7 +1292,9 @@ void ViewArea::takeSnapshot(ViewAreaSession &p_session) const
|
||||
}
|
||||
wsSnap.m_currentViewWindowIndex = ws->m_currentViewWindowIndex;
|
||||
for (auto win : ws->m_viewWindows) {
|
||||
wsSnap.m_viewWindows.push_back(win->saveSession());
|
||||
if (win->isSessionEnabled()) {
|
||||
wsSnap.m_viewWindows.push_back(win->saveSession());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include "editreaddiscardaction.h"
|
||||
#include "viewsplit.h"
|
||||
#include "attachmentpopup.h"
|
||||
#include "tagpopup.h"
|
||||
#include "outlinepopup.h"
|
||||
#include "dragdropareaindicator.h"
|
||||
#include "attachmentdragdropareaindicator.h"
|
||||
@ -141,6 +142,8 @@ void ViewWindow::handleBufferChanged(const QSharedPointer<FileOpenParameters> &p
|
||||
this, &ViewWindow::attachmentChanged);
|
||||
}
|
||||
|
||||
m_sessionEnabled = p_paras->m_sessionEnabled;
|
||||
|
||||
handleBufferChangedInternal(p_paras);
|
||||
|
||||
emit bufferChanged();
|
||||
@ -399,6 +402,19 @@ QAction *ViewWindow::addAction(QToolBar *p_toolBar, ViewWindowToolBarHelper::Act
|
||||
break;
|
||||
}
|
||||
|
||||
case ViewWindowToolBarHelper::Tag:
|
||||
{
|
||||
act = ViewWindowToolBarHelper::addAction(p_toolBar, p_action);
|
||||
auto popup = static_cast<TagPopup *>(static_cast<QToolButton *>(p_toolBar->widgetForAction(act))->menu());
|
||||
connect(this, &ViewWindow::bufferChanged,
|
||||
this, [this, act, popup]() {
|
||||
auto buffer = getBuffer();
|
||||
act->setEnabled(buffer ? buffer->isTagSupported() : false);
|
||||
popup->setBuffer(buffer);
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case ViewWindowToolBarHelper::Outline:
|
||||
{
|
||||
act = ViewWindowToolBarHelper::addAction(p_toolBar, p_action);
|
||||
@ -1152,6 +1168,7 @@ QToolBar *ViewWindow::createToolBar(QWidget *p_parent)
|
||||
toolBar->setIconSize(QSize(iconSize, iconSize));
|
||||
|
||||
/*
|
||||
// The extension button of tool bar.
|
||||
auto extBtn = toolBar->findChild<QToolButton *>(QLatin1String("qt_toolbar_ext_button"));
|
||||
Q_ASSERT(extBtn);
|
||||
*/
|
||||
@ -1239,3 +1256,8 @@ void ViewWindow::updateLastFindInfo(const QStringList &p_texts, FindOptions p_op
|
||||
m_findInfo.m_texts = p_texts;
|
||||
m_findInfo.m_options = p_options;
|
||||
}
|
||||
|
||||
bool ViewWindow::isSessionEnabled() const
|
||||
{
|
||||
return m_sessionEnabled;
|
||||
}
|
||||
|
@ -95,6 +95,8 @@ namespace vnotex
|
||||
// Return the result from the FloatingWidget.
|
||||
QVariant showFloatingWidget(FloatingWidget *p_widget);
|
||||
|
||||
bool isSessionEnabled() const;
|
||||
|
||||
public slots:
|
||||
virtual void handleEditorConfigChange() = 0;
|
||||
|
||||
@ -316,6 +318,14 @@ namespace vnotex
|
||||
|
||||
Buffer *m_buffer = nullptr;
|
||||
|
||||
// Whether check file missing or changed outside.
|
||||
bool m_fileChangeCheckEnabled = true;
|
||||
|
||||
// Last find info.
|
||||
FindInfo m_findInfo;
|
||||
|
||||
bool m_sessionEnabled = true;
|
||||
|
||||
// Null if this window has not been added to any split.
|
||||
ViewSplit *m_viewSplit = nullptr;
|
||||
|
||||
@ -342,12 +352,6 @@ namespace vnotex
|
||||
// Managed by QObject.
|
||||
QToolBar *m_toolBar = nullptr;
|
||||
|
||||
// Whether check file missing or changed outside.
|
||||
bool m_fileChangeCheckEnabled = true;
|
||||
|
||||
// Last find info.
|
||||
FindInfo m_findInfo;
|
||||
|
||||
QSharedPointer<StatusWidget> m_statusWidget;
|
||||
|
||||
EditReadDiscardAction *m_editReadDiscardAct = nullptr;
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include "editreaddiscardaction.h"
|
||||
#include "widgetsfactory.h"
|
||||
#include "attachmentpopup.h"
|
||||
#include "tagpopup.h"
|
||||
#include "propertydefs.h"
|
||||
#include "outlinepopup.h"
|
||||
#include "viewwindow.h"
|
||||
@ -293,6 +294,23 @@ QAction *ViewWindowToolBarHelper::addAction(QToolBar *p_tb, Action p_action)
|
||||
break;
|
||||
}
|
||||
|
||||
case Action::Tag:
|
||||
{
|
||||
act = p_tb->addAction(ToolBarHelper::generateIcon("tag_editor.svg"),
|
||||
ViewWindow::tr("Tags"));
|
||||
|
||||
auto toolBtn = dynamic_cast<QToolButton *>(p_tb->widgetForAction(act));
|
||||
Q_ASSERT(toolBtn);
|
||||
toolBtn->setPopupMode(QToolButton::InstantPopup);
|
||||
toolBtn->setProperty(PropertyDefs::c_toolButtonWithoutMenuIndicator, true);
|
||||
|
||||
addButtonShortcut(toolBtn, editorConfig.getShortcut(Shortcut::Tag), viewWindow);
|
||||
|
||||
auto menu = new TagPopup(toolBtn, p_tb);
|
||||
toolBtn->setMenu(menu);
|
||||
break;
|
||||
}
|
||||
|
||||
case Action::Outline:
|
||||
{
|
||||
act = p_tb->addAction(ToolBarHelper::generateIcon("outline_editor.svg"),
|
||||
|
@ -41,6 +41,7 @@ namespace vnotex
|
||||
TypeMax,
|
||||
|
||||
Attachment,
|
||||
Tag,
|
||||
Outline,
|
||||
FindAndReplace,
|
||||
SectionNumber,
|
||||
|