mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-05 13:59:52 +08:00
2061 lines
61 KiB
C++
2061 lines
61 KiB
C++
#include "notebooknodeexplorer.h"
|
|
|
|
#include <QTreeWidget>
|
|
#include <QVBoxLayout>
|
|
#include <QSplitter>
|
|
#include <QTreeWidget>
|
|
#include <QMenu>
|
|
#include <QAction>
|
|
#include <QSet>
|
|
#include <QShortcut>
|
|
|
|
#include <notebook/notebook.h>
|
|
#include <notebook/node.h>
|
|
#include <notebook/externalnode.h>
|
|
#include "exception.h"
|
|
#include "messageboxhelper.h"
|
|
#include "vnotex.h"
|
|
#include "mainwindow.h"
|
|
#include <utils/iconutils.h>
|
|
#include <utils/docsutils.h>
|
|
#include <utils/processutils.h>
|
|
#include "treewidget.h"
|
|
#include "dialogs/notepropertiesdialog.h"
|
|
#include "dialogs/folderpropertiesdialog.h"
|
|
#include "dialogs/deleteconfirmdialog.h"
|
|
#include "dialogs/sortdialog.h"
|
|
#include <utils/widgetutils.h>
|
|
#include <utils/pathutils.h>
|
|
#include <utils/clipboardutils.h>
|
|
#include "notebookmgr.h"
|
|
#include "widgetsfactory.h"
|
|
#include "navigationmodemgr.h"
|
|
|
|
#include <core/fileopenparameters.h>
|
|
#include <core/events.h>
|
|
#include <core/configmgr.h>
|
|
#include <core/coreconfig.h>
|
|
#include <core/sessionconfig.h>
|
|
|
|
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;
|
|
|
|
NotebookNodeExplorer::NodeData::NodeData()
|
|
{
|
|
}
|
|
|
|
NotebookNodeExplorer::NodeData::NodeData(Node *p_node, bool p_loaded)
|
|
: m_type(NodeType::Node),
|
|
m_node(p_node),
|
|
m_loaded(p_loaded)
|
|
{
|
|
}
|
|
|
|
NotebookNodeExplorer::NodeData::NodeData(const QSharedPointer<ExternalNode> &p_externalNode)
|
|
: m_type(NodeType::ExternalNode),
|
|
m_externalNode(p_externalNode),
|
|
m_loaded(true)
|
|
{
|
|
}
|
|
|
|
NotebookNodeExplorer::NodeData::NodeData(const NodeData &p_other)
|
|
{
|
|
m_type = p_other.m_type;
|
|
switch (m_type) {
|
|
case NodeType::Node:
|
|
m_node = p_other.m_node;
|
|
break;
|
|
|
|
case NodeType::ExternalNode:
|
|
m_externalNode = p_other.m_externalNode;
|
|
break;
|
|
|
|
default:
|
|
Q_ASSERT(false);
|
|
break;
|
|
}
|
|
|
|
m_loaded = p_other.m_loaded;
|
|
}
|
|
|
|
NotebookNodeExplorer::NodeData::~NodeData()
|
|
{
|
|
}
|
|
|
|
NotebookNodeExplorer::NodeData &NotebookNodeExplorer::NodeData::operator=(const NodeData &p_other)
|
|
{
|
|
if (&p_other == this) {
|
|
return *this;
|
|
}
|
|
|
|
m_type = p_other.m_type;
|
|
switch (m_type) {
|
|
case NodeType::Node:
|
|
m_node = p_other.m_node;
|
|
break;
|
|
|
|
case NodeType::ExternalNode:
|
|
m_externalNode = p_other.m_externalNode;
|
|
break;
|
|
|
|
default:
|
|
Q_ASSERT(false);
|
|
break;
|
|
}
|
|
|
|
m_loaded = p_other.m_loaded;
|
|
|
|
return *this;
|
|
}
|
|
|
|
bool NotebookNodeExplorer::NodeData::isValid() const
|
|
{
|
|
return m_type != NodeType::Invalid;
|
|
}
|
|
|
|
bool NotebookNodeExplorer::NodeData::isNode() const
|
|
{
|
|
return m_type == NodeType::Node;
|
|
}
|
|
|
|
bool NotebookNodeExplorer::NodeData::isExternalNode() const
|
|
{
|
|
return m_type == NodeType::ExternalNode;
|
|
}
|
|
|
|
NotebookNodeExplorer::NodeData::NodeType NotebookNodeExplorer::NodeData::getType() const
|
|
{
|
|
return m_type;
|
|
}
|
|
|
|
Node *NotebookNodeExplorer::NodeData::getNode() const
|
|
{
|
|
Q_ASSERT(isNode());
|
|
return m_node;
|
|
}
|
|
|
|
ExternalNode *NotebookNodeExplorer::NodeData::getExternalNode() const
|
|
{
|
|
Q_ASSERT(isExternalNode());
|
|
return m_externalNode.data();
|
|
}
|
|
|
|
void NotebookNodeExplorer::NodeData::clear()
|
|
{
|
|
m_type = NodeType::Invalid;
|
|
m_node = nullptr;
|
|
m_externalNode.clear();
|
|
m_loaded = false;
|
|
}
|
|
|
|
bool NotebookNodeExplorer::NodeData::matched(const Node *p_node) const
|
|
{
|
|
if (isNode() && m_node == p_node) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool NotebookNodeExplorer::NodeData::isLoaded() const
|
|
{
|
|
return m_loaded;
|
|
}
|
|
|
|
|
|
NotebookNodeExplorer::NotebookNodeExplorer(QWidget *p_parent)
|
|
: QWidget(p_parent)
|
|
{
|
|
initNodeIcons();
|
|
|
|
setupUI();
|
|
|
|
setupShortcuts();
|
|
}
|
|
|
|
void NotebookNodeExplorer::initNodeIcons() const
|
|
{
|
|
if (!s_folderNodeIcon.isNull()) {
|
|
return;
|
|
}
|
|
|
|
const QString nodeIconFgName = "widgets#notebookexplorer#node_icon#fg";
|
|
const QString invalidNodeIconFgName = "widgets#notebookexplorer#node_icon#invalid#fg";
|
|
const QString externalNodeIconFgName = "widgets#notebookexplorer#external_node_icon#fg";
|
|
|
|
const auto &themeMgr = VNoteX::getInst().getThemeMgr();
|
|
const auto fg = themeMgr.paletteColor(nodeIconFgName);
|
|
const auto invalidFg = themeMgr.paletteColor(invalidNodeIconFgName);
|
|
const auto externalFg = themeMgr.paletteColor(externalNodeIconFgName);
|
|
|
|
const QString folderIconName("folder_node.svg");
|
|
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);
|
|
}
|
|
|
|
void NotebookNodeExplorer::setupUI()
|
|
{
|
|
auto mainLayout = new QVBoxLayout(this);
|
|
mainLayout->setContentsMargins(0, 0, 0, 0);
|
|
|
|
m_splitter = new QSplitter(this);
|
|
mainLayout->addWidget(m_splitter);
|
|
|
|
setupMasterExplorer(m_splitter);
|
|
m_splitter->addWidget(m_masterExplorer);
|
|
|
|
setFocusProxy(m_masterExplorer);
|
|
}
|
|
|
|
void NotebookNodeExplorer::setupMasterExplorer(QWidget *p_parent)
|
|
{
|
|
m_masterExplorer = new TreeWidget(TreeWidget::ClickSpaceToClearSelection, p_parent);
|
|
TreeWidget::setupSingleColumnHeaderlessTree(m_masterExplorer, true, true);
|
|
TreeWidget::showHorizontalScrollbar(m_masterExplorer);
|
|
|
|
m_navigationWrapper.reset(new NavigationModeWrapper<QTreeWidget, QTreeWidgetItem>(m_masterExplorer));
|
|
NavigationModeMgr::getInst().registerNavigationTarget(m_navigationWrapper.data());
|
|
|
|
connect(m_masterExplorer, &QTreeWidget::itemExpanded,
|
|
this, &NotebookNodeExplorer::loadItemChildren);
|
|
|
|
connect(m_masterExplorer, &QTreeWidget::customContextMenuRequested,
|
|
this, [this](const QPoint &p_pos) {
|
|
if (!m_notebook) {
|
|
return;
|
|
}
|
|
|
|
auto item = m_masterExplorer->itemAt(p_pos);
|
|
auto data = getItemNodeData(item);
|
|
QScopedPointer<QMenu> menu(WidgetsFactory::createMenu());
|
|
if (!data.isValid()) {
|
|
createContextMenuOnRoot(menu.data());
|
|
} else {
|
|
if (!allSelectedItemsSameType()) {
|
|
return;
|
|
}
|
|
|
|
if (data.isNode()) {
|
|
createContextMenuOnNode(menu.data(), data.getNode());
|
|
} else if (data.isExternalNode()) {
|
|
createContextMenuOnExternalNode(menu.data(), data.getExternalNode());
|
|
}
|
|
}
|
|
|
|
if (!menu->isEmpty()) {
|
|
menu->exec(m_masterExplorer->mapToGlobal(p_pos));
|
|
}
|
|
});
|
|
|
|
connect(m_masterExplorer, &QTreeWidget::itemActivated,
|
|
this, [this](QTreeWidgetItem *p_item, int p_column) {
|
|
Q_UNUSED(p_column);
|
|
auto data = getItemNodeData(p_item);
|
|
if (!data.isValid()) {
|
|
return;
|
|
}
|
|
|
|
if (data.isNode()) {
|
|
if (checkInvalidNode(data.getNode())) {
|
|
return;
|
|
}
|
|
emit nodeActivated(data.getNode(), QSharedPointer<FileOpenParameters>::create());
|
|
} else if (data.isExternalNode()) {
|
|
// Import to config first.
|
|
if (m_autoImportExternalFiles) {
|
|
auto importedNode = importToIndex(data.getExternalNode());
|
|
if (importedNode) {
|
|
emit nodeActivated(importedNode.data(), QSharedPointer<FileOpenParameters>::create());
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Just open it.
|
|
emit fileActivated(data.getExternalNode()->fetchAbsolutePath(),
|
|
QSharedPointer<FileOpenParameters>::create());
|
|
}
|
|
});
|
|
}
|
|
|
|
void NotebookNodeExplorer::setNotebook(const QSharedPointer<Notebook> &p_notebook)
|
|
{
|
|
if (p_notebook == m_notebook) {
|
|
return;
|
|
}
|
|
|
|
if (m_notebook) {
|
|
disconnect(m_notebook.data(), nullptr, this, nullptr);
|
|
}
|
|
|
|
saveNotebookTreeState();
|
|
|
|
m_notebook = p_notebook;
|
|
|
|
if (m_notebook) {
|
|
connect(m_notebook.data(), &Notebook::nodeUpdated,
|
|
this, [this](const Node *p_node) {
|
|
updateNode(p_node->getParent());
|
|
});
|
|
}
|
|
|
|
generateNodeTree();
|
|
}
|
|
|
|
void NotebookNodeExplorer::clearExplorer()
|
|
{
|
|
m_masterExplorer->clear();
|
|
}
|
|
|
|
void NotebookNodeExplorer::generateNodeTree()
|
|
{
|
|
clearExplorer();
|
|
|
|
if (!m_notebook) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
auto rootNode = m_notebook->getRootNode();
|
|
|
|
loadRootNode(rootNode.data());
|
|
} catch (Exception &p_e) {
|
|
QString msg = tr("Failed to load nodes of notebook (%1) (%2).")
|
|
.arg(m_notebook->getName(), p_e.what());
|
|
qCritical() << msg;
|
|
MessageBoxHelper::notify(MessageBoxHelper::Critical, msg, VNoteX::getInst().getMainWindow());
|
|
}
|
|
|
|
// Restore current item.
|
|
auto currentNode = stateCache()->getCurrentItem();
|
|
|
|
if (currentNode) {
|
|
setCurrentNode(currentNode);
|
|
} else {
|
|
// Do not focus the recycle bin.
|
|
focusNormalNode();
|
|
}
|
|
|
|
stateCache()->clear();
|
|
}
|
|
|
|
void NotebookNodeExplorer::loadRootNode(const Node *p_node) const
|
|
{
|
|
Q_ASSERT(p_node->isLoaded() && p_node->isContainer());
|
|
|
|
// Render recycle bin node first.
|
|
auto recycleBinNode = m_notebook->getRecycleBinNode();
|
|
if (recycleBinNode) {
|
|
loadRecycleBinNode(recycleBinNode.data());
|
|
}
|
|
|
|
// External children.
|
|
if (m_externalFilesVisible) {
|
|
auto externalChildren = p_node->fetchExternalChildren();
|
|
// TODO: Sort external children.
|
|
for (const auto &child : externalChildren) {
|
|
auto item = new QTreeWidgetItem(m_masterExplorer);
|
|
loadNode(item, child);
|
|
}
|
|
}
|
|
|
|
// Children.
|
|
auto children = p_node->getChildren();
|
|
sortNodes(children);
|
|
for (const auto &child : children) {
|
|
if (recycleBinNode == child) {
|
|
continue;
|
|
}
|
|
|
|
auto item = new QTreeWidgetItem(m_masterExplorer);
|
|
loadNode(item, child.data(), 1);
|
|
}
|
|
}
|
|
|
|
static void clearTreeWigetItemChildren(QTreeWidgetItem *p_item)
|
|
{
|
|
auto children = p_item->takeChildren();
|
|
for (auto &child : children) {
|
|
delete child;
|
|
}
|
|
}
|
|
|
|
void NotebookNodeExplorer::loadNode(QTreeWidgetItem *p_item, Node *p_node, int p_level) const
|
|
{
|
|
if (!p_node->isLoaded()) {
|
|
p_node->load();
|
|
}
|
|
|
|
clearTreeWigetItemChildren(p_item);
|
|
|
|
fillTreeItem(p_item, p_node, p_level > 0);
|
|
|
|
loadChildren(p_item, p_node, p_level - 1);
|
|
|
|
if (stateCache()->contains(p_item) && p_item->childCount() > 0) {
|
|
if (p_item->isExpanded()) {
|
|
loadItemChildren(p_item);
|
|
} else {
|
|
// itemExpanded() will trigger loadItemChildren().
|
|
p_item->setExpanded(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void NotebookNodeExplorer::loadNode(QTreeWidgetItem *p_item, const QSharedPointer<ExternalNode> &p_node) const
|
|
{
|
|
clearTreeWigetItemChildren(p_item);
|
|
|
|
fillTreeItem(p_item, p_node);
|
|
|
|
// No children for external node.
|
|
}
|
|
|
|
void NotebookNodeExplorer::loadChildren(QTreeWidgetItem *p_item, Node *p_node, int p_level) const
|
|
{
|
|
if (p_level < 0) {
|
|
return;
|
|
}
|
|
|
|
// External children.
|
|
if (m_externalFilesVisible && p_node->isContainer()) {
|
|
auto externalChildren = p_node->fetchExternalChildren();
|
|
// TODO: Sort external children.
|
|
for (const auto &child : externalChildren) {
|
|
auto item = new QTreeWidgetItem(p_item);
|
|
loadNode(item, child);
|
|
}
|
|
}
|
|
|
|
auto children = p_node->getChildren();
|
|
sortNodes(children);
|
|
for (const auto &child : children) {
|
|
auto item = new QTreeWidgetItem(p_item);
|
|
loadNode(item, child.data(), p_level);
|
|
}
|
|
}
|
|
|
|
void NotebookNodeExplorer::loadRecycleBinNode(Node *p_node) const
|
|
{
|
|
if (!m_recycleBinNodeVisible) {
|
|
return;
|
|
}
|
|
|
|
auto item = new QTreeWidgetItem();
|
|
item->setWhatsThis(Column::Name,
|
|
tr("Recycle bin of this notebook. Deleted files could be found here. "
|
|
"It is organized in folders named by date. Nodes could be moved to "
|
|
"other folders by Cut and Paste."));
|
|
m_masterExplorer->insertTopLevelItem(0, item);
|
|
|
|
loadRecycleBinNode(item, p_node, 1);
|
|
}
|
|
|
|
void NotebookNodeExplorer::loadRecycleBinNode(QTreeWidgetItem *p_item, Node *p_node, int p_level) const
|
|
{
|
|
if (!m_recycleBinNodeVisible) {
|
|
return;
|
|
}
|
|
|
|
if (!p_node->isLoaded()) {
|
|
p_node->load();
|
|
}
|
|
|
|
clearTreeWigetItemChildren(p_item);
|
|
|
|
setItemNodeData(p_item, NodeData(p_node, true));
|
|
p_item->setText(Column::Name, tr("Recycle Bin"));
|
|
p_item->setIcon(Column::Name, getNodeItemIcon(p_node));
|
|
|
|
loadChildren(p_item, p_node, p_level - 1);
|
|
|
|
// No need to restore state.
|
|
}
|
|
|
|
void NotebookNodeExplorer::fillTreeItem(QTreeWidgetItem *p_item, Node *p_node, bool p_loaded) const
|
|
{
|
|
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())));
|
|
}
|
|
|
|
void NotebookNodeExplorer::fillTreeItem(QTreeWidgetItem *p_item, const QSharedPointer<ExternalNode> &p_node) const
|
|
{
|
|
setItemNodeData(p_item, NodeData(p_node));
|
|
p_item->setText(Column::Name, p_node->getName());
|
|
p_item->setIcon(Column::Name, getNodeItemIcon(p_node.data()));
|
|
p_item->setToolTip(Column::Name, tr("[External] %1").arg(p_node->getName()));
|
|
}
|
|
|
|
const QIcon &NotebookNodeExplorer::getNodeItemIcon(const Node *p_node) const
|
|
{
|
|
if (p_node->hasContent()) {
|
|
return p_node->exists() ? s_fileNodeIcon : s_invalidFileNodeIcon;
|
|
} else {
|
|
if (p_node->getUse() == Node::Use::RecycleBin) {
|
|
return s_recycleBinNodeIcon;
|
|
}
|
|
|
|
return p_node->exists() ? s_folderNodeIcon : s_invalidFolderNodeIcon;
|
|
}
|
|
}
|
|
|
|
const QIcon &NotebookNodeExplorer::getNodeItemIcon(const ExternalNode *p_node) const
|
|
{
|
|
return p_node->isFolder() ? s_externalFolderNodeIcon : s_externalFileNodeIcon;
|
|
}
|
|
|
|
Node *NotebookNodeExplorer::getCurrentNode() const
|
|
{
|
|
auto item = m_masterExplorer->currentItem();
|
|
if (item) {
|
|
auto data = getItemNodeData(item);
|
|
while (item && !data.isNode()) {
|
|
item = item->parent();
|
|
if (item) {
|
|
data = getItemNodeData(item);
|
|
} else {
|
|
data.clear();
|
|
}
|
|
}
|
|
|
|
if (data.isNode()) {
|
|
return data.getNode();
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void NotebookNodeExplorer::setItemNodeData(QTreeWidgetItem *p_item, const NodeData &p_data)
|
|
{
|
|
p_item->setData(Column::Name, Qt::UserRole, QVariant::fromValue(p_data));
|
|
}
|
|
|
|
NotebookNodeExplorer::NodeData NotebookNodeExplorer::getItemNodeData(const QTreeWidgetItem *p_item)
|
|
{
|
|
if (!p_item) {
|
|
return NodeData();
|
|
}
|
|
|
|
return p_item->data(Column::Name, Qt::UserRole).value<NotebookNodeExplorer::NodeData>();
|
|
}
|
|
|
|
void NotebookNodeExplorer::updateNode(Node *p_node)
|
|
{
|
|
if (p_node && p_node->getNotebook() != m_notebook) {
|
|
return;
|
|
}
|
|
|
|
auto item = findNode(p_node);
|
|
if (item) {
|
|
bool expanded = item->isExpanded();
|
|
item->setExpanded(false);
|
|
|
|
if (m_notebook->isRecycleBinNode(p_node)) {
|
|
loadRecycleBinNode(item, p_node, 1);
|
|
} else {
|
|
loadNode(item, p_node, 1);
|
|
}
|
|
|
|
item->setExpanded(expanded);
|
|
} else {
|
|
saveNotebookTreeState(false);
|
|
|
|
generateNodeTree();
|
|
}
|
|
}
|
|
|
|
QTreeWidgetItem *NotebookNodeExplorer::findNode(const Node *p_node) const
|
|
{
|
|
if (!p_node) {
|
|
return nullptr;
|
|
}
|
|
|
|
auto cnt = m_masterExplorer->topLevelItemCount();
|
|
for (int i = 0; i < cnt; ++i) {
|
|
auto item = findNode(m_masterExplorer->topLevelItem(i), p_node);
|
|
if (item) {
|
|
return item;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
QTreeWidgetItem *NotebookNodeExplorer::findNode(QTreeWidgetItem *p_item, const Node *p_node) const
|
|
{
|
|
auto data = getItemNodeData(p_item);
|
|
if (data.matched(p_node)) {
|
|
return p_item;
|
|
}
|
|
|
|
auto cnt = p_item->childCount();
|
|
for (int i = 0; i < cnt; ++i) {
|
|
auto item = findNode(p_item->child(i), p_node);
|
|
if (item) {
|
|
return item;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
QTreeWidgetItem *NotebookNodeExplorer::findNodeChild(QTreeWidgetItem *p_item, const Node *p_node) const
|
|
{
|
|
auto cnt = p_item->childCount();
|
|
for (int i = 0; i < cnt; ++i) {
|
|
auto child = p_item->child(i);
|
|
auto data = getItemNodeData(child);
|
|
if (data.matched(p_node)) {
|
|
return child;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
QTreeWidgetItem *NotebookNodeExplorer::findNodeTopLevelItem(QTreeWidget *p_tree, const Node *p_node) const
|
|
{
|
|
auto cnt = p_tree->topLevelItemCount();
|
|
for (int i = 0; i < cnt; ++i) {
|
|
auto child = p_tree->topLevelItem(i);
|
|
auto data = getItemNodeData(child);
|
|
if (data.matched(p_node)) {
|
|
return child;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void NotebookNodeExplorer::setCurrentNode(Node *p_node)
|
|
{
|
|
if (!p_node || !p_node->getParent()) {
|
|
m_masterExplorer->setCurrentItem(nullptr);
|
|
return;
|
|
}
|
|
|
|
Q_ASSERT(p_node->getNotebook() == m_notebook);
|
|
|
|
// Nodes from root to p_node.
|
|
QList<Node *> nodes;
|
|
auto node = p_node;
|
|
while (node->getParent()) {
|
|
nodes.push_front(node);
|
|
node = node->getParent();
|
|
}
|
|
|
|
QList<QTreeWidgetItem *> items;
|
|
auto nodeIt = nodes.constBegin();
|
|
auto item = findNodeTopLevelItem(m_masterExplorer, *nodeIt);
|
|
if (!item) {
|
|
return;
|
|
}
|
|
items.push_back(item);
|
|
|
|
++nodeIt;
|
|
while (nodeIt != nodes.constEnd()) {
|
|
if (!item) {
|
|
return;
|
|
}
|
|
|
|
// Find *nodeIt in children of item.
|
|
auto data = getItemNodeData(item);
|
|
Q_ASSERT(data.isNode());
|
|
if (!data.isLoaded()) {
|
|
loadNode(item, data.getNode(), 1);
|
|
}
|
|
|
|
auto childItem = findNodeChild(item, *nodeIt);
|
|
if (!childItem) {
|
|
return;
|
|
}
|
|
items.push_back(childItem);
|
|
|
|
item = childItem;
|
|
++nodeIt;
|
|
}
|
|
|
|
Q_ASSERT(getItemNodeData(item).getNode() == p_node);
|
|
|
|
// Do not expand the last item.
|
|
for (int i = 0; i < items.size() - 1; ++i) {
|
|
items[i]->setExpanded(true);
|
|
}
|
|
|
|
m_masterExplorer->setCurrentItem(item);
|
|
}
|
|
|
|
void NotebookNodeExplorer::saveNotebookTreeState(bool p_saveCurrentItem)
|
|
{
|
|
if (m_notebook) {
|
|
stateCache()->save(m_masterExplorer, p_saveCurrentItem);
|
|
}
|
|
}
|
|
|
|
QSharedPointer<QTreeWidgetStateCache<Node *>> NotebookNodeExplorer::stateCache() const
|
|
{
|
|
Q_ASSERT(m_notebook);
|
|
auto it = m_stateCache.find(m_notebook.data());
|
|
if (it == m_stateCache.end()) {
|
|
auto keyFunc = [](const QTreeWidgetItem *p_item, bool &p_ok) {
|
|
auto data = NotebookNodeExplorer::getItemNodeData(p_item);
|
|
if (data.isNode()) {
|
|
p_ok = true;
|
|
return data.getNode();
|
|
}
|
|
|
|
p_ok = false;
|
|
return static_cast<Node *>(nullptr);
|
|
};
|
|
auto cache = QSharedPointer<QTreeWidgetStateCache<Node *>>::create(keyFunc);
|
|
it = const_cast<NotebookNodeExplorer *>(this)->m_stateCache.insert(m_notebook.data(), cache);
|
|
}
|
|
|
|
return it.value();
|
|
}
|
|
|
|
void NotebookNodeExplorer::clearStateCache(const Notebook *p_notebook)
|
|
{
|
|
auto it = m_stateCache.find(p_notebook);
|
|
if (it != m_stateCache.end()) {
|
|
it.value()->clear();
|
|
}
|
|
}
|
|
|
|
void NotebookNodeExplorer::createContextMenuOnRoot(QMenu *p_menu)
|
|
{
|
|
auto act = createAction(Action::NewNote, p_menu);
|
|
p_menu->addAction(act);
|
|
|
|
act = createAction(Action::NewFolder, p_menu);
|
|
p_menu->addAction(act);
|
|
|
|
if (isPasteOnNodeAvailable(nullptr)) {
|
|
p_menu->addSeparator();
|
|
act = createAction(Action::Paste, p_menu);
|
|
p_menu->addAction(act);
|
|
}
|
|
|
|
p_menu->addSeparator();
|
|
|
|
act = createAction(Action::Reload, p_menu);
|
|
p_menu->addAction(act);
|
|
|
|
act = createAction(Action::ReloadIndex, p_menu);
|
|
p_menu->addAction(act);
|
|
|
|
act = createAction(Action::OpenLocation, p_menu);
|
|
p_menu->addAction(act);
|
|
}
|
|
|
|
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);
|
|
|
|
act = createAction(Action::ReloadIndex, p_menu);
|
|
p_menu->addAction(act);
|
|
|
|
if (selectedSize == 1) {
|
|
act = createAction(Action::EmptyRecycleBin, p_menu);
|
|
p_menu->addAction(act);
|
|
|
|
act = createAction(Action::OpenLocation, p_menu);
|
|
p_menu->addAction(act);
|
|
}
|
|
} else if (m_notebook->isNodeInRecycleBin(p_node)) {
|
|
// Node in recycle bin.
|
|
act = createAction(Action::Open, p_menu);
|
|
p_menu->addAction(act);
|
|
|
|
addOpenWithMenu(p_menu);
|
|
|
|
p_menu->addSeparator();
|
|
|
|
if (selectedSize == 1 && p_node->isContainer()) {
|
|
act = createAction(Action::ExpandAll, p_menu);
|
|
p_menu->addAction(act);
|
|
}
|
|
|
|
p_menu->addSeparator();
|
|
|
|
act = createAction(Action::Cut, p_menu);
|
|
p_menu->addAction(act);
|
|
|
|
act = createAction(Action::DeleteFromRecycleBin, p_menu);
|
|
p_menu->addAction(act);
|
|
|
|
p_menu->addSeparator();
|
|
|
|
act = createAction(Action::Reload, p_menu);
|
|
p_menu->addAction(act);
|
|
|
|
act = createAction(Action::ReloadIndex, p_menu);
|
|
p_menu->addAction(act);
|
|
|
|
if (selectedSize == 1) {
|
|
p_menu->addSeparator();
|
|
|
|
act = createAction(Action::CopyPath, p_menu);
|
|
p_menu->addAction(act);
|
|
|
|
act = createAction(Action::OpenLocation, p_menu);
|
|
p_menu->addAction(act);
|
|
}
|
|
} else {
|
|
act = createAction(Action::Open, p_menu);
|
|
p_menu->addAction(act);
|
|
|
|
addOpenWithMenu(p_menu);
|
|
|
|
p_menu->addSeparator();
|
|
|
|
if (selectedSize == 1 && p_node->isContainer()) {
|
|
act = createAction(Action::ExpandAll, p_menu);
|
|
p_menu->addAction(act);
|
|
}
|
|
|
|
p_menu->addSeparator();
|
|
|
|
act = createAction(Action::NewNote, p_menu);
|
|
p_menu->addAction(act);
|
|
|
|
act = createAction(Action::NewFolder, p_menu);
|
|
p_menu->addAction(act);
|
|
|
|
p_menu->addSeparator();
|
|
|
|
act = createAction(Action::Copy, p_menu);
|
|
p_menu->addAction(act);
|
|
|
|
act = createAction(Action::Cut, p_menu);
|
|
p_menu->addAction(act);
|
|
|
|
if (selectedSize == 1 && isPasteOnNodeAvailable(p_node)) {
|
|
act = createAction(Action::Paste, p_menu);
|
|
p_menu->addAction(act);
|
|
}
|
|
|
|
act = createAction(Action::Delete, p_menu);
|
|
p_menu->addAction(act);
|
|
|
|
act = createAction(Action::RemoveFromConfig, p_menu);
|
|
p_menu->addAction(act);
|
|
|
|
p_menu->addSeparator();
|
|
|
|
act = createAction(Action::Reload, p_menu);
|
|
p_menu->addAction(act);
|
|
|
|
act = createAction(Action::ReloadIndex, p_menu);
|
|
p_menu->addAction(act);
|
|
|
|
act = createAction(Action::Sort, p_menu);
|
|
p_menu->addAction(act);
|
|
|
|
{
|
|
p_menu->addSeparator();
|
|
|
|
act = createAction(Action::PinToQuickAccess, p_menu);
|
|
p_menu->addAction(act);
|
|
}
|
|
|
|
if (selectedSize == 1) {
|
|
p_menu->addSeparator();
|
|
|
|
act = createAction(Action::CopyPath, p_menu);
|
|
p_menu->addAction(act);
|
|
|
|
act = createAction(Action::OpenLocation, p_menu);
|
|
p_menu->addAction(act);
|
|
|
|
act = createAction(Action::Properties, p_menu);
|
|
p_menu->addAction(act);
|
|
}
|
|
}
|
|
}
|
|
|
|
void NotebookNodeExplorer::createContextMenuOnExternalNode(QMenu *p_menu, const ExternalNode *p_node)
|
|
{
|
|
Q_UNUSED(p_node);
|
|
|
|
const int selectedSize = m_masterExplorer->selectedItems().size();
|
|
QAction *act = nullptr;
|
|
|
|
act = createAction(Action::Open, p_menu);
|
|
p_menu->addAction(act);
|
|
|
|
addOpenWithMenu(p_menu);
|
|
|
|
p_menu->addSeparator();
|
|
|
|
act = createAction(Action::ImportToConfig, p_menu);
|
|
p_menu->addAction(act);
|
|
|
|
{
|
|
p_menu->addSeparator();
|
|
|
|
act = createAction(Action::PinToQuickAccess, p_menu);
|
|
p_menu->addAction(act);
|
|
}
|
|
|
|
if (selectedSize == 1) {
|
|
p_menu->addSeparator();
|
|
|
|
act = createAction(Action::CopyPath, p_menu);
|
|
p_menu->addAction(act);
|
|
|
|
act = createAction(Action::OpenLocation, p_menu);
|
|
p_menu->addAction(act);
|
|
}
|
|
}
|
|
|
|
static QIcon generateMenuActionIcon(const QString &p_name)
|
|
{
|
|
const auto &themeMgr = VNoteX::getInst().getThemeMgr();
|
|
return IconUtils::fetchIconWithDisabledState(themeMgr.getIconFile(p_name));
|
|
}
|
|
|
|
QAction *NotebookNodeExplorer::createAction(Action p_act, QObject *p_parent)
|
|
{
|
|
QAction *act = nullptr;
|
|
switch (p_act) {
|
|
case Action::NewNote:
|
|
act = new QAction(generateMenuActionIcon("new_note.svg"),
|
|
tr("New N&ote"),
|
|
p_parent);
|
|
connect(act, &QAction::triggered,
|
|
this, []() {
|
|
emit VNoteX::getInst().newNoteRequested();
|
|
});
|
|
break;
|
|
|
|
case Action::NewFolder:
|
|
act = new QAction(generateMenuActionIcon("new_folder.svg"),
|
|
tr("New &Folder"),
|
|
p_parent);
|
|
connect(act, &QAction::triggered,
|
|
this, []() {
|
|
emit VNoteX::getInst().newFolderRequested();
|
|
});
|
|
break;
|
|
|
|
case Action::Properties:
|
|
act = new QAction(generateMenuActionIcon("properties.svg"),
|
|
tr("&Properties (Rename)"),
|
|
p_parent);
|
|
connect(act, &QAction::triggered,
|
|
this, [this]() {
|
|
auto node = getCurrentNode();
|
|
if (checkInvalidNode(node)) {
|
|
return;
|
|
}
|
|
|
|
int ret = QDialog::Rejected;
|
|
if (node->hasContent()) {
|
|
NotePropertiesDialog dialog(node, VNoteX::getInst().getMainWindow());
|
|
ret = dialog.exec();
|
|
} else {
|
|
FolderPropertiesDialog dialog(node, VNoteX::getInst().getMainWindow());
|
|
ret = dialog.exec();
|
|
}
|
|
|
|
if (ret == QDialog::Accepted) {
|
|
setCurrentNode(node);
|
|
}
|
|
});
|
|
break;
|
|
|
|
case Action::OpenLocation:
|
|
act = new QAction(tr("Open &Location"), p_parent);
|
|
connect(act, &QAction::triggered,
|
|
this, [this]() {
|
|
auto item = m_masterExplorer->currentItem();
|
|
if (!item) {
|
|
if (m_notebook) {
|
|
auto locationPath = m_notebook->getRootFolderAbsolutePath();
|
|
WidgetUtils::openUrlByDesktop(QUrl::fromLocalFile(locationPath));
|
|
}
|
|
return;
|
|
}
|
|
auto data = getItemNodeData(item);
|
|
QString locationPath;
|
|
if (data.isNode()) {
|
|
auto node = data.getNode();
|
|
if (checkInvalidNode(node)) {
|
|
return;
|
|
}
|
|
|
|
locationPath = node->fetchAbsolutePath();
|
|
if (!node->isContainer()) {
|
|
locationPath = PathUtils::parentDirPath(locationPath);
|
|
}
|
|
} else if (data.isExternalNode()) {
|
|
auto externalNode = data.getExternalNode();
|
|
locationPath = externalNode->fetchAbsolutePath();
|
|
if (!externalNode->isFolder()) {
|
|
locationPath = PathUtils::parentDirPath(locationPath);
|
|
}
|
|
}
|
|
|
|
if (!locationPath.isEmpty()) {
|
|
WidgetUtils::openUrlByDesktop(QUrl::fromLocalFile(locationPath));
|
|
}
|
|
});
|
|
break;
|
|
|
|
case Action::CopyPath:
|
|
act = new QAction(tr("Cop&y Path"), p_parent);
|
|
connect(act, &QAction::triggered,
|
|
this, [this]() {
|
|
auto item = m_masterExplorer->currentItem();
|
|
if (!item) {
|
|
return;
|
|
}
|
|
auto data = getItemNodeData(item);
|
|
QString nodePath;
|
|
if (data.isNode()) {
|
|
auto node = data.getNode();
|
|
if (checkInvalidNode(node)) {
|
|
return;
|
|
}
|
|
nodePath = node->fetchAbsolutePath();
|
|
} else if (data.isExternalNode()) {
|
|
nodePath = data.getExternalNode()->fetchAbsolutePath();
|
|
}
|
|
if (!nodePath.isEmpty()) {
|
|
ClipboardUtils::setTextToClipboard(nodePath);
|
|
VNoteX::getInst().showStatusMessageShort(tr("Copied path: %1").arg(nodePath));
|
|
}
|
|
});
|
|
break;
|
|
|
|
case Action::Copy:
|
|
act = new QAction(tr("&Copy"), p_parent);
|
|
connect(act, &QAction::triggered,
|
|
this, [this]() {
|
|
copySelectedNodes(false);
|
|
});
|
|
break;
|
|
|
|
case Action::Cut:
|
|
act = new QAction(tr("C&ut"), p_parent);
|
|
connect(act, &QAction::triggered,
|
|
this, [this]() {
|
|
copySelectedNodes(true);
|
|
});
|
|
break;
|
|
|
|
case Action::Paste:
|
|
act = new QAction(tr("&Paste"), p_parent);
|
|
connect(act, &QAction::triggered,
|
|
this, [this]() {
|
|
pasteNodesFromClipboard();
|
|
});
|
|
break;
|
|
|
|
case Action::EmptyRecycleBin:
|
|
act = new QAction(tr("&Empty"), p_parent);
|
|
connect(act, &QAction::triggered,
|
|
this, [this]() {
|
|
auto rbNode = m_notebook->getRecycleBinNode().data();
|
|
auto rbNodePath = rbNode->fetchAbsolutePath();
|
|
int ret = MessageBoxHelper::questionOkCancel(MessageBoxHelper::Warning,
|
|
tr("Empty the recycle bin of this notebook?"),
|
|
tr("All files in recycle bin will be deleted permanently."),
|
|
tr("Location of recycle bin: %1").arg(rbNodePath));
|
|
if (ret != QMessageBox::Ok) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
m_notebook->emptyNode(rbNode, true);
|
|
} catch (Exception &p_e) {
|
|
MessageBoxHelper::notify(MessageBoxHelper::Critical,
|
|
tr("Failed to empty recycle bin (%1) (%2).").arg(rbNodePath, p_e.what()),
|
|
VNoteX::getInst().getMainWindow());
|
|
}
|
|
|
|
updateNode(rbNode);
|
|
});
|
|
break;
|
|
|
|
case Action::Delete:
|
|
act = new QAction(tr("&Delete"), p_parent);
|
|
connect(act, &QAction::triggered,
|
|
this, [this]() {
|
|
removeSelectedNodes(false);
|
|
});
|
|
break;
|
|
|
|
case Action::DeleteFromRecycleBin:
|
|
// It is fine to have &D with Action::Delete since they won't be at the same context.
|
|
act = new QAction(tr("&Delete From Recycle Bin"), p_parent);
|
|
connect(act, &QAction::triggered,
|
|
this, [this]() {
|
|
removeSelectedNodes(true);
|
|
});
|
|
break;
|
|
|
|
case Action::RemoveFromConfig:
|
|
act = new QAction(tr("&Remove From Index"), p_parent);
|
|
connect(act, &QAction::triggered,
|
|
this, &NotebookNodeExplorer::removeSelectedNodesFromConfig);
|
|
break;
|
|
|
|
case Action::Sort:
|
|
act = new QAction(generateMenuActionIcon("sort.svg"), tr("&Sort"), p_parent);
|
|
connect(act, &QAction::triggered,
|
|
this, &NotebookNodeExplorer::manualSort);
|
|
break;
|
|
|
|
case Action::Reload:
|
|
act = new QAction(tr("Re&load"), p_parent);
|
|
connect(act, &QAction::triggered,
|
|
this, [this]() {
|
|
auto node = currentExploredFolderNode();
|
|
updateNode(node);
|
|
});
|
|
break;
|
|
|
|
case Action::ReloadIndex:
|
|
act = new QAction(tr("Relo&ad Index From Disk"), p_parent);
|
|
connect(act, &QAction::triggered,
|
|
this, [this]() {
|
|
if (!m_notebook) {
|
|
return;
|
|
}
|
|
|
|
auto event = QSharedPointer<Event>::create();
|
|
emit nodeAboutToReload(m_notebook->getRootNode().data(), event);
|
|
if (!event->m_response.toBool()) {
|
|
return;
|
|
}
|
|
|
|
reload();
|
|
});
|
|
break;
|
|
|
|
case Action::ImportToConfig:
|
|
act = new QAction(tr("&Import To Index"), p_parent);
|
|
connect(act, &QAction::triggered,
|
|
this, [this]() {
|
|
auto nodes = getSelectedNodes().second;
|
|
importToIndex(nodes);
|
|
});
|
|
break;
|
|
|
|
case Action::Open:
|
|
act = new QAction(tr("&Open"), p_parent);
|
|
connect(act, &QAction::triggered,
|
|
this, &NotebookNodeExplorer::openSelectedNodes);
|
|
break;
|
|
|
|
case Action::ExpandAll:
|
|
act = new QAction(tr("&Expand All\t*"), p_parent);
|
|
connect(act, &QAction::triggered,
|
|
this, &NotebookNodeExplorer::expandCurrentNodeAll);
|
|
break;
|
|
|
|
case Action::PinToQuickAccess:
|
|
act = new QAction(tr("Pin To &Quick Access"), p_parent);
|
|
connect(act, &QAction::triggered,
|
|
this, [this]() {
|
|
auto nodes = getSelectedNodes();
|
|
QStringList files;
|
|
for (const auto &node : nodes.first) {
|
|
if (node->hasContent()) {
|
|
files.push_back(node->fetchAbsolutePath());
|
|
}
|
|
}
|
|
for (const auto &node : nodes.second) {
|
|
if (!node->isFolder()) {
|
|
files.push_back(node->fetchAbsolutePath());
|
|
}
|
|
}
|
|
if (!files.isEmpty()) {
|
|
emit VNoteX::getInst().pinToQuickAccessRequested(files);
|
|
}
|
|
});
|
|
break;
|
|
}
|
|
|
|
return act;
|
|
}
|
|
|
|
void NotebookNodeExplorer::copySelectedNodes(bool p_move)
|
|
{
|
|
auto nodes = getSelectedNodes().first;
|
|
if (nodes.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
filterAwayChildrenNodes(nodes);
|
|
|
|
ClipboardData cdata(VNoteX::getInst().getInstanceId(),
|
|
p_move ? ClipboardData::MoveNode : ClipboardData::CopyNode);
|
|
for (auto node : nodes) {
|
|
if (checkInvalidNode(node)) {
|
|
continue;
|
|
}
|
|
|
|
auto item = QSharedPointer<NodeClipboardDataItem>::create(node->getNotebook()->getId(),
|
|
node->fetchPath());
|
|
cdata.addItem(item);
|
|
}
|
|
|
|
auto text = cdata.toJsonText();
|
|
ClipboardUtils::setTextToClipboard(text);
|
|
|
|
size_t nrItems = cdata.getData().size();
|
|
VNoteX::getInst().showStatusMessageShort(tr("Copied %n item(s)", "", static_cast<int>(nrItems)));
|
|
}
|
|
|
|
QPair<QVector<Node *>, QVector<ExternalNode *>> NotebookNodeExplorer::getSelectedNodes() const
|
|
{
|
|
QPair<QVector<Node *>, QVector<ExternalNode *>> nodes;
|
|
|
|
auto items = m_masterExplorer->selectedItems();
|
|
for (auto &item : items) {
|
|
auto data = getItemNodeData(item);
|
|
if (data.isNode()) {
|
|
nodes.first.push_back(data.getNode());
|
|
} else if (data.isExternalNode()) {
|
|
nodes.second.push_back(data.getExternalNode());
|
|
}
|
|
}
|
|
|
|
return nodes;
|
|
}
|
|
|
|
QSharedPointer<ClipboardData> NotebookNodeExplorer::tryFetchClipboardData()
|
|
{
|
|
auto text = ClipboardUtils::getTextFromClipboard();
|
|
return ClipboardData::fromJsonText(text);
|
|
}
|
|
|
|
static bool isValidClipboardData(const ClipboardData *p_data)
|
|
{
|
|
if (!p_data) {
|
|
return false;
|
|
}
|
|
|
|
if (p_data->getInstanceId() != VNoteX::getInst().getInstanceId()) {
|
|
return false;
|
|
}
|
|
|
|
if (p_data->getData().isEmpty()) {
|
|
return false;
|
|
}
|
|
|
|
auto act = p_data->getAction();
|
|
if (act != ClipboardData::CopyNode && act != ClipboardData::MoveNode) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool NotebookNodeExplorer::isPasteOnNodeAvailable(const Node *p_node) const
|
|
{
|
|
Q_UNUSED(p_node);
|
|
auto cdata = tryFetchClipboardData();
|
|
return isValidClipboardData(cdata.data());
|
|
}
|
|
|
|
static QSharedPointer<Node> getNodeFromClipboardDataItem(const NodeClipboardDataItem *p_item)
|
|
{
|
|
Q_ASSERT(p_item);
|
|
auto notebook = VNoteX::getInst().getNotebookMgr().findNotebookById(p_item->m_notebookId);
|
|
if (!notebook) {
|
|
Exception::throwOne(Exception::Type::InvalidArgument,
|
|
QString("failed to find notebook by ID (%1)").arg(p_item->m_notebookId));
|
|
return nullptr;
|
|
}
|
|
|
|
auto node = notebook->loadNodeByPath(p_item->m_nodeRelativePath);
|
|
Q_ASSERT(!node || node->fetchPath() == p_item->m_nodeRelativePath);
|
|
return node;
|
|
}
|
|
|
|
void NotebookNodeExplorer::pasteNodesFromClipboard()
|
|
{
|
|
// Identify the dest node.
|
|
auto destNode = getCurrentNode();
|
|
if (!destNode) {
|
|
destNode = m_notebook->getRootNode().data();
|
|
} else {
|
|
// Current node may be a file node.
|
|
if (!destNode->isContainer()) {
|
|
destNode = destNode->getParent();
|
|
} else if (checkInvalidNode(destNode)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
Q_ASSERT(destNode && destNode->isContainer());
|
|
|
|
// Fetch source nodes from clipboard.
|
|
auto cdata = tryFetchClipboardData();
|
|
if (!isValidClipboardData(cdata.data())) {
|
|
MessageBoxHelper::notify(MessageBoxHelper::Warning,
|
|
tr("Invalid clipboard data to paste."),
|
|
VNoteX::getInst().getMainWindow());
|
|
return;
|
|
}
|
|
|
|
QVector<QSharedPointer<Node>> srcNodes;
|
|
auto items = cdata->getData();
|
|
for (auto &item : items) {
|
|
auto nodeItem = dynamic_cast<NodeClipboardDataItem *>(item.data());
|
|
Q_ASSERT(nodeItem);
|
|
auto src = getNodeFromClipboardDataItem(nodeItem);
|
|
if (!src) {
|
|
continue;
|
|
} else if (src == destNode) {
|
|
MessageBoxHelper::notify(MessageBoxHelper::Warning,
|
|
tr("Destination is detected in sources (%1). Operation is cancelled.")
|
|
.arg(destNode->fetchAbsolutePath()),
|
|
VNoteX::getInst().getMainWindow());
|
|
return;
|
|
}
|
|
|
|
srcNodes.push_back(src);
|
|
}
|
|
|
|
bool isMove = cdata->getAction() == ClipboardData::MoveNode;
|
|
QVector<const Node *> pastedNodes;
|
|
QSet<Node *> nodesNeedUpdate;
|
|
for (auto srcNode : srcNodes) {
|
|
Q_ASSERT(srcNode->exists());
|
|
|
|
if (isMove) {
|
|
// Notice the view area to close any opened view windows.
|
|
auto event = QSharedPointer<Event>::create();
|
|
emit nodeAboutToMove(srcNode.data(), event);
|
|
if (!event->m_response.toBool()) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
auto srcPath = srcNode->fetchAbsolutePath();
|
|
auto srcParentNode = srcNode->getParent();
|
|
|
|
try {
|
|
auto notebook = destNode->getNotebook();
|
|
auto pastedNode = notebook->copyNodeAsChildOf(srcNode, destNode, isMove);
|
|
pastedNodes.push_back(pastedNode.data());
|
|
} catch (Exception &p_e) {
|
|
MessageBoxHelper::notify(MessageBoxHelper::Critical,
|
|
tr("Failed to copy source (%1) to destination (%2) (%3).")
|
|
.arg(srcPath, destNode->fetchAbsolutePath(), p_e.what()),
|
|
VNoteX::getInst().getMainWindow());
|
|
}
|
|
|
|
if (isMove) {
|
|
nodesNeedUpdate.insert(srcParentNode);
|
|
}
|
|
}
|
|
|
|
for (auto node : nodesNeedUpdate) {
|
|
updateNode(node);
|
|
|
|
// Deleted src nodes may be the current node in cache. Clear the cache.
|
|
clearStateCache(node->getNotebook());
|
|
}
|
|
|
|
// Update and expand dest node. Select all pasted nodes.
|
|
updateAndExpandNode(destNode);
|
|
selectNodes(pastedNodes);
|
|
|
|
if (isMove) {
|
|
ClipboardUtils::clearClipboard();
|
|
}
|
|
|
|
VNoteX::getInst().showStatusMessageShort(tr("Pasted %n item(s)", "", pastedNodes.size()));
|
|
}
|
|
|
|
void NotebookNodeExplorer::setNodeExpanded(const Node *p_node, bool p_expanded)
|
|
{
|
|
auto item = findNode(p_node);
|
|
if (item) {
|
|
item->setExpanded(p_expanded);
|
|
}
|
|
}
|
|
|
|
void NotebookNodeExplorer::selectNodes(const QVector<const Node *> &p_nodes)
|
|
{
|
|
bool firstItem = true;
|
|
for (auto node : p_nodes) {
|
|
auto item = findNode(node);
|
|
if (item) {
|
|
auto flags = firstItem ? QItemSelectionModel::ClearAndSelect : QItemSelectionModel::Select;
|
|
m_masterExplorer->setCurrentItem(item, 0, flags);
|
|
firstItem = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
void NotebookNodeExplorer::removeSelectedNodes(bool p_skipRecycleBin)
|
|
{
|
|
QString text;
|
|
QString info;
|
|
if (p_skipRecycleBin) {
|
|
text = tr("Delete these folders and notes permanently?");
|
|
info = tr("Files will be deleted permanently and could not be found even "
|
|
"in operating system's recycle bin.");
|
|
} else {
|
|
text = tr("Delete these folders and notes?");
|
|
info = tr("Deleted files could be found in the recycle bin of notebook.");
|
|
}
|
|
|
|
auto nodes = confirmSelectedNodes(tr("Confirm Deletion"), text, info);
|
|
removeNodes(nodes, p_skipRecycleBin, false);
|
|
}
|
|
|
|
QVector<Node *> NotebookNodeExplorer::confirmSelectedNodes(const QString &p_title,
|
|
const QString &p_text,
|
|
const QString &p_info) const
|
|
{
|
|
auto nodes = getSelectedNodes().first;
|
|
if (nodes.isEmpty()) {
|
|
return nodes;
|
|
}
|
|
|
|
QVector<ConfirmItemInfo> items;
|
|
for (const auto &node : nodes) {
|
|
items.push_back(ConfirmItemInfo(getNodeItemIcon(node),
|
|
node->getName(),
|
|
node->fetchAbsolutePath(),
|
|
node->fetchAbsolutePath(),
|
|
(void *)node));
|
|
}
|
|
|
|
DeleteConfirmDialog dialog(p_title,
|
|
p_text,
|
|
p_info,
|
|
items,
|
|
DeleteConfirmDialog::Flag::None,
|
|
false,
|
|
VNoteX::getInst().getMainWindow());
|
|
|
|
QVector<Node *> nodesToDelete;
|
|
if (dialog.exec()) {
|
|
items = dialog.getConfirmedItems();
|
|
for (const auto &item : items) {
|
|
nodesToDelete.push_back(static_cast<Node *>(item.m_data));
|
|
}
|
|
}
|
|
|
|
return nodesToDelete;
|
|
}
|
|
|
|
void NotebookNodeExplorer::removeNodes(QVector<Node *> p_nodes,
|
|
bool p_skipRecycleBin,
|
|
bool p_configOnly)
|
|
{
|
|
if (p_nodes.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
filterAwayChildrenNodes(p_nodes);
|
|
|
|
int nrDeleted = 0;
|
|
QSet<Node *> nodesNeedUpdate;
|
|
for (auto node : p_nodes) {
|
|
auto srcName = node->getName();
|
|
auto srcPath = node->fetchAbsolutePath();
|
|
auto srcParentNode = node->getParent();
|
|
try {
|
|
auto event = QSharedPointer<Event>::create();
|
|
emit nodeAboutToRemove(node, event);
|
|
if (!event->m_response.toBool()) {
|
|
continue;
|
|
}
|
|
|
|
if (p_configOnly || p_skipRecycleBin) {
|
|
m_notebook->removeNode(node, false, p_configOnly);
|
|
} else {
|
|
m_notebook->moveNodeToRecycleBin(node);
|
|
}
|
|
|
|
++nrDeleted;
|
|
} catch (Exception &p_e) {
|
|
MessageBoxHelper::notify(MessageBoxHelper::Critical,
|
|
tr("Failed to delete/remove item (%1) (%2) (%3).")
|
|
.arg(srcName, srcPath, p_e.what()),
|
|
VNoteX::getInst().getMainWindow());
|
|
}
|
|
|
|
nodesNeedUpdate.insert(srcParentNode);
|
|
}
|
|
|
|
for (auto node : nodesNeedUpdate) {
|
|
updateNode(node);
|
|
}
|
|
|
|
if (!p_configOnly && !p_skipRecycleBin && m_recycleBinNodeVisible) {
|
|
updateNode(m_notebook->getRecycleBinNode().data());
|
|
}
|
|
|
|
VNoteX::getInst().showStatusMessageShort(tr("Deleted/Removed %n item(s)", "", nrDeleted));
|
|
}
|
|
|
|
void NotebookNodeExplorer::removeSelectedNodesFromConfig()
|
|
{
|
|
auto nodes = confirmSelectedNodes(tr("Confirm Removal"),
|
|
tr("Remove these folders and notes from index?"),
|
|
tr("Files are not touched but just removed from notebook index."));
|
|
removeNodes(nodes, false, true);
|
|
}
|
|
|
|
void NotebookNodeExplorer::filterAwayChildrenNodes(QVector<Node *> &p_nodes)
|
|
{
|
|
for (int i = p_nodes.size() - 1; i >= 0; --i) {
|
|
// Check if j is i's ancestor.
|
|
for (int j = p_nodes.size() - 1; j >= 0; --j) {
|
|
if (i == j) {
|
|
continue;
|
|
}
|
|
|
|
if (Node::isAncestor(p_nodes[j], p_nodes[i])) {
|
|
p_nodes.remove(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void NotebookNodeExplorer::updateAndExpandNode(Node *p_node)
|
|
{
|
|
setNodeExpanded(p_node, false);
|
|
updateNode(p_node);
|
|
setNodeExpanded(p_node, true);
|
|
}
|
|
|
|
bool NotebookNodeExplorer::allSelectedItemsSameType() const
|
|
{
|
|
auto items = m_masterExplorer->selectedItems();
|
|
if (items.size() < 2) {
|
|
return true;
|
|
}
|
|
|
|
auto type = getItemNodeData(items.first()).getType();
|
|
for (int i = 1; i < items.size(); ++i) {
|
|
auto itype = getItemNodeData(items[i]).getType();
|
|
if (itype != type) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (type == NodeData::NodeType::Node) {
|
|
bool hasNormalNode = false;
|
|
bool hasNodeInRecycleBin = false;
|
|
for (auto &item : items) {
|
|
auto node = getItemNodeData(item).getNode();
|
|
if (m_notebook->isRecycleBinNode(node)) {
|
|
return false;
|
|
} else if (m_notebook->isNodeInRecycleBin(node)) {
|
|
if (hasNormalNode) {
|
|
return false;
|
|
}
|
|
|
|
hasNodeInRecycleBin = true;
|
|
} else {
|
|
if (hasNodeInRecycleBin) {
|
|
return false;
|
|
}
|
|
|
|
hasNormalNode = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void NotebookNodeExplorer::reload()
|
|
{
|
|
updateNode(nullptr);
|
|
}
|
|
|
|
void NotebookNodeExplorer::focusNormalNode()
|
|
{
|
|
auto item = m_masterExplorer->currentItem();
|
|
if (item && (!m_recycleBinNodeVisible || item != m_masterExplorer->topLevelItem(0))) {
|
|
// Not recycle bin.
|
|
return;
|
|
}
|
|
|
|
m_masterExplorer->setCurrentItem(m_masterExplorer->topLevelItem(m_recycleBinNodeVisible ? 1 : 0));
|
|
}
|
|
|
|
void NotebookNodeExplorer::sortNodes(QVector<QSharedPointer<Node>> &p_nodes) const
|
|
{
|
|
if (m_viewOrder == ViewOrder::OrderedByConfiguration) {
|
|
return;
|
|
}
|
|
|
|
// Put containers first.
|
|
int firstFileIndex = p_nodes.size();
|
|
for (int i = 0; i < p_nodes.size(); ++i) {
|
|
if (p_nodes[i]->isContainer()) {
|
|
// If it is a container, load it to set its created time and modified time.
|
|
p_nodes[i]->load();
|
|
} else {
|
|
firstFileIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Sort containers.
|
|
sortNodes(p_nodes, 0, firstFileIndex, m_viewOrder);
|
|
|
|
// Sort non-containers.
|
|
sortNodes(p_nodes, firstFileIndex, p_nodes.size(), m_viewOrder);
|
|
}
|
|
|
|
void NotebookNodeExplorer::sortNodes(QVector<QSharedPointer<Node>> &p_nodes, int p_start, int p_end, int p_viewOrder) const
|
|
{
|
|
if (p_start >= p_end) {
|
|
return;
|
|
}
|
|
|
|
bool reversed = false;
|
|
switch (p_viewOrder) {
|
|
case ViewOrder::OrderedByNameReversed:
|
|
reversed = true;
|
|
Q_FALLTHROUGH();
|
|
case ViewOrder::OrderedByName:
|
|
std::sort(p_nodes.begin() + p_start, p_nodes.begin() + p_end, [reversed](const QSharedPointer<Node> &p_a, const QSharedPointer<Node> p_b) {
|
|
if (reversed) {
|
|
return p_b->getName() < p_a->getName();
|
|
} else {
|
|
return p_a->getName() < p_b->getName();
|
|
}
|
|
});
|
|
break;
|
|
|
|
case ViewOrder::OrderedByCreatedTimeReversed:
|
|
reversed = true;
|
|
Q_FALLTHROUGH();
|
|
case ViewOrder::OrderedByCreatedTime:
|
|
std::sort(p_nodes.begin() + p_start, p_nodes.begin() + p_end, [reversed](const QSharedPointer<Node> &p_a, const QSharedPointer<Node> p_b) {
|
|
if (reversed) {
|
|
return p_b->getCreatedTimeUtc() < p_a->getCreatedTimeUtc();
|
|
} else {
|
|
return p_a->getCreatedTimeUtc() < p_b->getCreatedTimeUtc();
|
|
}
|
|
});
|
|
break;
|
|
|
|
case ViewOrder::OrderedByModifiedTimeReversed:
|
|
reversed = true;
|
|
Q_FALLTHROUGH();
|
|
case ViewOrder::OrderedByModifiedTime:
|
|
std::sort(p_nodes.begin() + p_start, p_nodes.begin() + p_end, [reversed](const QSharedPointer<Node> &p_a, const QSharedPointer<Node> p_b) {
|
|
if (reversed) {
|
|
return p_b->getModifiedTimeUtc() < p_a->getModifiedTimeUtc();
|
|
} else {
|
|
return p_a->getModifiedTimeUtc() < p_b->getModifiedTimeUtc();
|
|
}
|
|
});
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void NotebookNodeExplorer::setRecycleBinNodeVisible(bool p_visible)
|
|
{
|
|
if (m_recycleBinNodeVisible == p_visible) {
|
|
return;
|
|
}
|
|
|
|
m_recycleBinNodeVisible = p_visible;
|
|
reload();
|
|
}
|
|
|
|
void NotebookNodeExplorer::setExternalFilesVisible(bool p_visible)
|
|
{
|
|
if (m_externalFilesVisible == p_visible) {
|
|
return;
|
|
}
|
|
|
|
m_externalFilesVisible = p_visible;
|
|
reload();
|
|
}
|
|
|
|
void NotebookNodeExplorer::setAutoImportExternalFiles(bool p_enabled)
|
|
{
|
|
if (m_autoImportExternalFiles == p_enabled) {
|
|
return;
|
|
}
|
|
|
|
m_autoImportExternalFiles = p_enabled;
|
|
}
|
|
|
|
void NotebookNodeExplorer::manualSort()
|
|
{
|
|
auto node = getCurrentNode();
|
|
if (!node) {
|
|
return;
|
|
}
|
|
|
|
auto parentNode = node->getParent();
|
|
bool isNotebook = parentNode->isRoot();
|
|
|
|
// Check whether sort files or folders based on current node type.
|
|
bool sortFolders = node->isContainer();
|
|
|
|
SortDialog sortDlg(sortFolders ? tr("Sort Folders") : tr("Sort Notes"),
|
|
tr("Sort nodes under %1 (%2) in the configuration file.").arg(
|
|
isNotebook ? tr("notebook") : tr("folder"),
|
|
isNotebook ? m_notebook->getName() : parentNode->getName()),
|
|
VNoteX::getInst().getMainWindow());
|
|
|
|
QVector<int> selectedIdx;
|
|
|
|
// Update the tree.
|
|
{
|
|
auto treeWidget = sortDlg.getTreeWidget();
|
|
treeWidget->clear();
|
|
treeWidget->setColumnCount(2);
|
|
treeWidget->setHeaderLabels({tr("Name"), tr("Created Time"), tr("Modified Time")});
|
|
|
|
const auto &children = parentNode->getChildrenRef();
|
|
for (int i = 0; i < children.size(); ++i) {
|
|
const auto &child = children[i];
|
|
if (m_notebook->isRecycleBinNode(child.data())) {
|
|
continue;
|
|
}
|
|
|
|
bool selected = sortFolders ? child->isContainer() : !child->isContainer();
|
|
if (selected) {
|
|
selectedIdx.push_back(i);
|
|
|
|
QStringList cols {child->getName(),
|
|
Utils::dateTimeString(child->getCreatedTimeUtc().toLocalTime()),
|
|
Utils::dateTimeString(child->getModifiedTimeUtc().toLocalTime())};
|
|
auto item = new QTreeWidgetItem(treeWidget, cols);
|
|
item->setData(0, Qt::UserRole, i);
|
|
}
|
|
}
|
|
|
|
sortDlg.updateTreeWidget();
|
|
}
|
|
|
|
if (sortDlg.exec() == QDialog::Accepted) {
|
|
const auto data = sortDlg.getSortedData();
|
|
Q_ASSERT(data.size() == selectedIdx.size());
|
|
QVector<int> sortedIdx(data.size(), -1);
|
|
for (int i = 0; i < data.size(); ++i) {
|
|
sortedIdx[i] = data[i].toInt();
|
|
}
|
|
parentNode->sortChildren(selectedIdx, sortedIdx);
|
|
updateNode(parentNode);
|
|
}
|
|
}
|
|
|
|
Node *NotebookNodeExplorer::currentExploredFolderNode() const
|
|
{
|
|
if (!m_notebook) {
|
|
return nullptr;
|
|
}
|
|
|
|
auto node = getCurrentNode();
|
|
if (node) {
|
|
if (!node->isContainer()) {
|
|
node = node->getParent();
|
|
}
|
|
Q_ASSERT(node && node->isContainer());
|
|
} else {
|
|
node = m_notebook->getRootNode().data();
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
Node *NotebookNodeExplorer::currentExploredNode() const
|
|
{
|
|
if (!m_notebook) {
|
|
return nullptr;
|
|
}
|
|
|
|
return getCurrentNode();
|
|
}
|
|
|
|
void NotebookNodeExplorer::setViewOrder(int p_order)
|
|
{
|
|
if (m_viewOrder == p_order) {
|
|
return;
|
|
}
|
|
|
|
m_viewOrder = p_order;
|
|
reload();
|
|
}
|
|
|
|
void NotebookNodeExplorer::openSelectedNodes()
|
|
{
|
|
// Support nodes and external nodes.
|
|
// Do nothing for folders.
|
|
auto selectedNodes = getSelectedNodes();
|
|
for (const auto &externalNode : selectedNodes.second) {
|
|
if (!externalNode->isFolder()) {
|
|
emit fileActivated(externalNode->fetchAbsolutePath(), QSharedPointer<FileOpenParameters>::create());
|
|
}
|
|
}
|
|
|
|
for (const auto &node : selectedNodes.first) {
|
|
if (checkInvalidNode(node)) {
|
|
continue;
|
|
}
|
|
|
|
if (node->hasContent()) {
|
|
emit nodeActivated(node, QSharedPointer<FileOpenParameters>::create());
|
|
}
|
|
}
|
|
}
|
|
|
|
QStringList NotebookNodeExplorer::getSelectedNodesPath() const
|
|
{
|
|
QStringList files;
|
|
|
|
// Support nodes and external nodes.
|
|
auto selectedNodes = getSelectedNodes();
|
|
for (const auto &externalNode : selectedNodes.second) {
|
|
files << externalNode->fetchAbsolutePath();
|
|
}
|
|
|
|
for (const auto &node : selectedNodes.first) {
|
|
if (checkInvalidNode(node)) {
|
|
continue;
|
|
}
|
|
|
|
files << node->fetchAbsolutePath();
|
|
}
|
|
|
|
return files;
|
|
}
|
|
|
|
QSharedPointer<Node> NotebookNodeExplorer::importToIndex(const ExternalNode *p_node)
|
|
{
|
|
auto node = m_notebook->addAsNode(p_node->getNode(),
|
|
p_node->isFolder() ? Node::Flag::Container : Node::Flag::Content,
|
|
p_node->getName(),
|
|
NodeParameters());
|
|
updateNode(p_node->getNode());
|
|
if (node) {
|
|
setCurrentNode(node.data());
|
|
}
|
|
return node;
|
|
}
|
|
|
|
void NotebookNodeExplorer::importToIndex(const QVector<ExternalNode *> &p_nodes)
|
|
{
|
|
QSet<Node *> nodesToUpdate;
|
|
Node *currentNode = nullptr;
|
|
|
|
for (auto externalNode : p_nodes) {
|
|
auto node = m_notebook->addAsNode(externalNode->getNode(),
|
|
externalNode->isFolder() ? Node::Flag::Container : Node::Flag::Content,
|
|
externalNode->getName(),
|
|
NodeParameters());
|
|
nodesToUpdate.insert(externalNode->getNode());
|
|
currentNode = node.data();
|
|
}
|
|
|
|
for (auto node : nodesToUpdate) {
|
|
updateNode(node);
|
|
}
|
|
if (currentNode) {
|
|
setCurrentNode(currentNode);
|
|
}
|
|
}
|
|
|
|
bool NotebookNodeExplorer::checkInvalidNode(Node *p_node) const
|
|
{
|
|
if (!p_node) {
|
|
return true;
|
|
}
|
|
|
|
bool nodeExists = p_node->exists();
|
|
if (nodeExists) {
|
|
p_node->checkExists();
|
|
nodeExists = p_node->exists();
|
|
}
|
|
|
|
if (!nodeExists) {
|
|
MessageBoxHelper::notify(MessageBoxHelper::Warning,
|
|
tr("Invalid node (%1).").arg(p_node->getName()),
|
|
tr("Please check if the node exists on the disk."),
|
|
p_node->fetchAbsolutePath(),
|
|
VNoteX::getInst().getMainWindow());
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void NotebookNodeExplorer::expandCurrentNodeAll()
|
|
{
|
|
auto item = m_masterExplorer->currentItem();
|
|
if (!item || item->childCount() == 0) {
|
|
return;
|
|
}
|
|
auto data = getItemNodeData(item);
|
|
if (!data.isNode()) {
|
|
return;
|
|
}
|
|
|
|
expandItemRecursively(item);
|
|
}
|
|
|
|
void NotebookNodeExplorer::expandItemRecursively(QTreeWidgetItem *p_item)
|
|
{
|
|
if (!p_item) {
|
|
return;
|
|
}
|
|
|
|
p_item->setExpanded(true);
|
|
const int cnt = p_item->childCount();
|
|
if (cnt == 0) {
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < cnt; ++i) {
|
|
expandItemRecursively(p_item->child(i));
|
|
}
|
|
}
|
|
|
|
void NotebookNodeExplorer::addOpenWithMenu(QMenu *p_menu)
|
|
{
|
|
auto subMenu = p_menu->addMenu(tr("Open &With"));
|
|
|
|
{
|
|
const auto &sessionConfig = ConfigMgr::getInst().getSessionConfig();
|
|
for (const auto &pro : sessionConfig.getExternalPrograms()) {
|
|
QAction *act = subMenu->addAction(pro.m_name);
|
|
connect(act, &QAction::triggered,
|
|
this, [this, act]() {
|
|
openSelectedNodesWithExternalProgram(act->data().toString());
|
|
});
|
|
act->setData(pro.m_command);
|
|
WidgetUtils::addActionShortcutText(act, pro.m_shortcut);
|
|
}
|
|
}
|
|
|
|
subMenu->addSeparator();
|
|
|
|
{
|
|
auto defaultAct = subMenu->addAction(tr("System Default Program"),
|
|
this,
|
|
&NotebookNodeExplorer::openSelectedNodesWithDefaultProgram);
|
|
const auto &coreConfig = ConfigMgr::getInst().getCoreConfig();
|
|
WidgetUtils::addActionShortcutText(defaultAct, coreConfig.getShortcut(CoreConfig::OpenWithDefaultProgram));
|
|
}
|
|
|
|
subMenu->addAction(tr("Add External Program"),
|
|
this,
|
|
[]() {
|
|
const auto file = DocsUtils::getDocFile(QStringLiteral("external_programs.md"));
|
|
if (!file.isEmpty()) {
|
|
auto paras = QSharedPointer<FileOpenParameters>::create();
|
|
paras->m_readOnly = true;
|
|
emit VNoteX::getInst().openFileRequested(file, paras);
|
|
}
|
|
});
|
|
}
|
|
|
|
void NotebookNodeExplorer::setupShortcuts()
|
|
{
|
|
const auto &coreConfig = ConfigMgr::getInst().getCoreConfig();
|
|
|
|
// OpenWithDefaultProgram.
|
|
{
|
|
auto shortcut = WidgetUtils::createShortcut(coreConfig.getShortcut(CoreConfig::OpenWithDefaultProgram), this);
|
|
if (shortcut) {
|
|
connect(shortcut, &QShortcut::activated,
|
|
this, &NotebookNodeExplorer::openSelectedNodesWithDefaultProgram);
|
|
}
|
|
}
|
|
|
|
const auto &sessionConfig = ConfigMgr::getInst().getSessionConfig();
|
|
for (const auto &pro : sessionConfig.getExternalPrograms()) {
|
|
auto shortcut = WidgetUtils::createShortcut(pro.m_shortcut, this);
|
|
const auto &command = pro.m_command;
|
|
if (shortcut) {
|
|
connect(shortcut, &QShortcut::activated,
|
|
this, [this, command]() {
|
|
openSelectedNodesWithExternalProgram(command);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
void NotebookNodeExplorer::openSelectedNodesWithDefaultProgram()
|
|
{
|
|
const auto files = getSelectedNodesPath();
|
|
for (const auto &file : files) {
|
|
if (file.isEmpty()) {
|
|
continue;
|
|
}
|
|
WidgetUtils::openUrlByDesktop(QUrl::fromLocalFile(file));
|
|
}
|
|
}
|
|
|
|
void NotebookNodeExplorer::openSelectedNodesWithExternalProgram(const QString &p_command)
|
|
{
|
|
const auto files = getSelectedNodesPath();
|
|
for (const auto &file : files) {
|
|
if (file.isEmpty()) {
|
|
continue;
|
|
}
|
|
|
|
auto command = p_command;
|
|
command.replace(QStringLiteral("%1"), QString("\"%1\"").arg(file));
|
|
ProcessUtils::startDetached(command);
|
|
}
|
|
}
|
|
|
|
void NotebookNodeExplorer::loadItemChildren(QTreeWidgetItem *p_item) const
|
|
{
|
|
auto cnt = p_item->childCount();
|
|
for (int i = 0; i < cnt; ++i) {
|
|
auto child = p_item->child(i);
|
|
auto data = getItemNodeData(child);
|
|
if (data.isNode() && !data.isLoaded()) {
|
|
loadNode(child, data.getNode(), 1);
|
|
}
|
|
}
|
|
}
|