diff --git a/src/core/configmgr.cpp b/src/core/configmgr.cpp index 021ee0d3..10531a5f 100644 --- a/src/core/configmgr.cpp +++ b/src/core/configmgr.cpp @@ -25,7 +25,7 @@ using namespace vnotex; #ifndef QT_NO_DEBUG - #define VX_DEBUG_WEB + // #define VX_DEBUG_WEB #endif const QString ConfigMgr::c_orgName = "VNote"; diff --git a/src/core/mainconfig.cpp b/src/core/mainconfig.cpp index e4055c48..9fda8075 100644 --- a/src/core/mainconfig.cpp +++ b/src/core/mainconfig.cpp @@ -119,5 +119,7 @@ QString MainConfig::getVersion(const QJsonObject &p_jobj) void MainConfig::doVersionSpecificOverride() { // In a new version, we may want to change one value by force. + // For 3.12.0. m_editorConfig->getTextEditorConfig().m_highlightWhitespace = false; + m_editorConfig->getMarkdownEditorConfig().m_imageAlignCenterEnabled = false; } diff --git a/src/core/markdowneditorconfig.h b/src/core/markdowneditorconfig.h index 17cb8b28..d45deb67 100644 --- a/src/core/markdowneditorconfig.h +++ b/src/core/markdowneditorconfig.h @@ -211,7 +211,7 @@ namespace vnotex // Whether enable image width constraint. bool m_constrainImageWidthEnabled = true; - bool m_imageAlignCenterEnabled = true; + bool m_imageAlignCenterEnabled = false; // Whether enable in-place preview width constraint. bool m_constrainInplacePreviewWidthEnabled = false; diff --git a/src/core/sessionconfig.cpp b/src/core/sessionconfig.cpp index d3eeb324..05cb4d33 100644 --- a/src/core/sessionconfig.cpp +++ b/src/core/sessionconfig.cpp @@ -222,6 +222,7 @@ QJsonObject SessionConfig::saveStateAndGeometry() const 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); + writeByteArray(obj, QStringLiteral("notebook_explorer_state"), m_mainWindowStateGeometry.m_notebookExplorerState); return obj; } @@ -353,6 +354,7 @@ void SessionConfig::loadStateAndGeometry(const QJsonObject &p_session) 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")); + m_mainWindowStateGeometry.m_notebookExplorerState = readByteArray(obj, QStringLiteral("notebook_explorer_state")); } QByteArray SessionConfig::getViewAreaSessionAndClear() diff --git a/src/core/sessionconfig.h b/src/core/sessionconfig.h index 97af711d..3e3bb04d 100644 --- a/src/core/sessionconfig.h +++ b/src/core/sessionconfig.h @@ -37,7 +37,8 @@ namespace vnotex return m_mainState == p_other.m_mainState && m_mainGeometry == p_other.m_mainGeometry && m_visibleDocksBeforeExpand == p_other.m_visibleDocksBeforeExpand - && m_tagExplorerState == p_other.m_tagExplorerState; + && m_tagExplorerState == p_other.m_tagExplorerState + && m_notebookExplorerState == p_other.m_notebookExplorerState; } QByteArray m_mainState; @@ -47,6 +48,8 @@ namespace vnotex QStringList m_visibleDocksBeforeExpand; QByteArray m_tagExplorerState; + + QByteArray m_notebookExplorerState; }; enum OpenGL diff --git a/src/core/widgetconfig.cpp b/src/core/widgetconfig.cpp index 64c9cc4c..3a85d107 100644 --- a/src/core/widgetconfig.cpp +++ b/src/core/widgetconfig.cpp @@ -31,6 +31,7 @@ void WidgetConfig::init(const QJsonObject &p_app, { m_nodeExplorerViewOrder = READINT(QStringLiteral("node_explorer_view_order")); + m_nodeExplorerExploreMode = READINT(QStringLiteral("node_explorer_explore_mode")); m_nodeExplorerExternalFilesVisible = READBOOL(QStringLiteral("node_explorer_external_files_visible")); m_nodeExplorerAutoImportExternalFilesEnabled = READBOOL(QStringLiteral("node_explorer_auto_import_external_files_enabled")); m_nodeExplorerCloseBeforeOpenWithEnabled = READBOOL(QStringLiteral("node_explorer_close_before_open_with_enabled")); @@ -54,6 +55,7 @@ QJsonObject WidgetConfig::toJson() const obj[QStringLiteral("find_and_replace_options")] = static_cast(m_findAndReplaceOptions); obj[QStringLiteral("node_explorer_view_order")] = m_nodeExplorerViewOrder; + obj[QStringLiteral("node_explorer_explore_mode")] = m_nodeExplorerExploreMode; obj[QStringLiteral("node_explorer_external_files_visible")] = m_nodeExplorerExternalFilesVisible; obj[QStringLiteral("node_explorer_auto_import_external_files_enabled")] = m_nodeExplorerAutoImportExternalFilesEnabled; obj[QStringLiteral("node_explorer_close_before_open_with_enabled")] = m_nodeExplorerCloseBeforeOpenWithEnabled; @@ -106,6 +108,16 @@ void WidgetConfig::setNodeExplorerViewOrder(int p_viewOrder) updateConfig(m_nodeExplorerViewOrder, p_viewOrder, this); } +int WidgetConfig::getNodeExplorerExploreMode() const +{ + return m_nodeExplorerExploreMode; +} + +void WidgetConfig::setNodeExplorerExploreMode(int p_mode) +{ + updateConfig(m_nodeExplorerExploreMode, p_mode, this); +} + bool WidgetConfig::isNodeExplorerExternalFilesVisible() const { return m_nodeExplorerExternalFilesVisible; diff --git a/src/core/widgetconfig.h b/src/core/widgetconfig.h index 91f369cc..53464a9e 100644 --- a/src/core/widgetconfig.h +++ b/src/core/widgetconfig.h @@ -30,6 +30,9 @@ namespace vnotex int getNodeExplorerViewOrder() const; void setNodeExplorerViewOrder(int p_viewOrder); + int getNodeExplorerExploreMode() const; + void setNodeExplorerExploreMode(int p_mode); + bool isNodeExplorerExternalFilesVisible() const; void setNodeExplorerExternalFilesVisible(bool p_visible); @@ -60,6 +63,8 @@ namespace vnotex int m_nodeExplorerViewOrder = 0; + int m_nodeExplorerExploreMode = 1; + bool m_nodeExplorerExternalFilesVisible = true; bool m_nodeExplorerAutoImportExternalFilesEnabled = true; diff --git a/src/data/core/vnotex.json b/src/data/core/vnotex.json index a44164a9..41224f74 100644 --- a/src/data/core/vnotex.json +++ b/src/data/core/vnotex.json @@ -335,7 +335,7 @@ "section_number_style" : "digdotdigdot", "//comment" : "Whether enable image width constraint", "constrain_image_width" : true, - "image_align_center" : true, + "image_align_center" : false, "//comment" : "Whether enable in-place preview width constraint", "constrain_inplace_preview_width" : false, "//comment" : "Zoom factor in read mode", @@ -383,12 +383,14 @@ "outline_section_number_enabled" : false, "//comment" : "Default find options in FindAndReplace", "find_and_replace_options" : 16, - "//comment" : "View order of the node explorer", + "//comment" : "View order of the node explorer (NotebookNodeExplorer::ViewOrder)", "node_explorer_view_order" : 0, "node_explorer_external_files_visible" : true, "node_explorer_auto_import_external_files_enabled" : true, "//comment" : "Whether close the file before opening it with external program", "node_explorer_close_before_open_with_enabled" : true, + "//comment" : "0 - combined/1 - separate_single/2 - separate_double", + "node_explorer_explore_mode" : 1, "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"], diff --git a/src/data/extra/themes/moonlight/text-editor.theme b/src/data/extra/themes/moonlight/text-editor.theme index ea4ffb92..6134af5a 100644 --- a/src/data/extra/themes/moonlight/text-editor.theme +++ b/src/data/extra/themes/moonlight/text-editor.theme @@ -30,7 +30,7 @@ }, "IndicatorsBorder" : { "text-color" : "#8a93a6", - "background-color" : "#2d323b" + "background-color" : "#3c414d" }, "CurrentLineNumber" : { "text-color" : "#ccd1d8" diff --git a/src/export/exportdata.cpp b/src/export/exportdata.cpp index d548b58d..3b4f1032 100644 --- a/src/export/exportdata.cpp +++ b/src/export/exportdata.cpp @@ -42,6 +42,44 @@ bool ExportHtmlOption::operator==(const ExportHtmlOption &p_other) const && m_scrollable == p_other.m_scrollable; } +static QJsonObject pageLayoutToJsonObject(const QPageLayout &p_layout) +{ + QJsonObject obj; + obj["page_size"] = static_cast(p_layout.pageSize().id()); + obj["orientation"] = static_cast(p_layout.orientation()); + obj["units"] = static_cast(p_layout.units()); + { + QStringList marginsStr; + const auto margins = p_layout.margins(); + marginsStr << QString::number(margins.left()); + marginsStr << QString::number(margins.top()); + marginsStr << QString::number(margins.right()); + marginsStr << QString::number(margins.bottom()); + obj["margins"] = marginsStr.join(QLatin1Char(',')); + } + return obj; +} + +static void jsonObjectToPageLayout(const QJsonObject &p_obj, QPageLayout &p_layout) +{ + const int pageSize = p_obj["page_size"].toInt(static_cast(QPageSize::A4)); + p_layout.setPageSize(QPageSize(static_cast(pageSize))); + + const int orientation = p_obj["orientation"].toInt(static_cast(QPageLayout::Portrait)); + p_layout.setOrientation(static_cast(orientation)); + + const int units = p_obj["units"].toInt(static_cast(QPageLayout::Millimeter)); + p_layout.setUnits(static_cast(units)); + + auto marginsStr = p_obj["margins"].toString().split(QLatin1Char(',')); + if (marginsStr.size() == 4) { + p_layout.setMargins(QMarginsF(marginsStr[0].toDouble(), + marginsStr[1].toDouble(), + marginsStr[2].toDouble(), + marginsStr[3].toDouble())); + } +} + ExportPdfOption::ExportPdfOption() : m_layout(new QPageLayout(QPageSize(QPageSize::A4), QPageLayout::Portrait, @@ -58,6 +96,7 @@ QJsonObject ExportPdfOption::toJson() const obj["all_in_one"] = m_allInOne; obj["wkhtmltopdf_exe_path"] = m_wkhtmltopdfExePath; obj["wkhtmltopdf_args"] = m_wkhtmltopdfArgs; + obj["layout"] = pageLayoutToJsonObject(*m_layout); return obj; } @@ -72,6 +111,7 @@ void ExportPdfOption::fromJson(const QJsonObject &p_obj) m_allInOne = p_obj["all_in_one"].toBool(); m_wkhtmltopdfExePath = p_obj["wkhtmltopdf_exe_path"].toString(); m_wkhtmltopdfArgs = p_obj["wkhtmltopdf_args"].toString(); + jsonObjectToPageLayout(p_obj["layout"].toObject(), *m_layout); } bool ExportPdfOption::operator==(const ExportPdfOption &p_other) const diff --git a/src/fakeaccessible.cpp b/src/fakeaccessible.cpp index 9b119676..8aaf30e1 100644 --- a/src/fakeaccessible.cpp +++ b/src/fakeaccessible.cpp @@ -10,6 +10,7 @@ QAccessibleInterface *FakeAccessible::accessibleFactory(const QString &p_classNa // Try to fix non-responsible issue caused by Youdao Dict. if (p_className.startsWith(QStringLiteral("vnotex::")) || p_className.startsWith(QStringLiteral("vte::"))) { + // Qt's docs: All interfaces are managed by an internal cache and should not be deleted. return new FakeAccessibleInterface(p_obj); } diff --git a/src/widgets/mainwindow.cpp b/src/widgets/mainwindow.cpp index 9f1710e6..791e6f7e 100644 --- a/src/widgets/mainwindow.cpp +++ b/src/widgets/mainwindow.cpp @@ -427,6 +427,7 @@ void MainWindow::saveStateAndGeometry() sg.m_mainGeometry = saveGeometry(); sg.m_visibleDocksBeforeExpand = m_visibleDocksBeforeExpand; sg.m_tagExplorerState = m_tagExplorer->saveState(); + sg.m_notebookExplorerState = m_notebookExplorer->saveState(); auto& sessionConfig = ConfigMgr::getInst().getSessionConfig(); sessionConfig.setMainWindowStateGeometry(sg); @@ -457,6 +458,10 @@ void MainWindow::loadStateAndGeometry(bool p_stateOnly) if (!sg.m_tagExplorerState.isEmpty()) { m_tagExplorer->restoreState(sg.m_tagExplorerState); } + + if (!sg.m_notebookExplorerState.isEmpty()) { + m_notebookExplorer->restoreState(sg.m_notebookExplorerState); + } } void MainWindow::resetStateAndGeometry() diff --git a/src/widgets/notebookexplorer.cpp b/src/widgets/notebookexplorer.cpp index a24fe802..0a87455e 100644 --- a/src/widgets/notebookexplorer.cpp +++ b/src/widgets/notebookexplorer.cpp @@ -82,6 +82,7 @@ void NotebookExplorer::setupUI() const auto &widgetConfig = ConfigMgr::getInst().getWidgetConfig(); m_nodeExplorer = new NotebookNodeExplorer(this); m_nodeExplorer->setViewOrder(widgetConfig.getNodeExplorerViewOrder()); + m_nodeExplorer->setExploreMode(widgetConfig.getNodeExplorerExploreMode()); m_nodeExplorer->setExternalFilesVisible(widgetConfig.isNodeExplorerExternalFilesVisible()); connect(m_nodeExplorer, &NotebookNodeExplorer::nodeActivated, &VNoteX::getInst(), &VNoteX::openNodeRequested); @@ -113,11 +114,8 @@ TitleBar *NotebookExplorer::setupTitleBar(QWidget *p_parent) { auto viewMenu = WidgetsFactory::createMenu(titleBar); + setupViewMenu(viewMenu); titleBar->addActionButton(QStringLiteral("view.svg"), tr("View"), viewMenu); - connect(viewMenu, &QMenu::aboutToShow, - this, [this, viewMenu]() { - setupViewMenu(viewMenu); - }); } { @@ -205,6 +203,8 @@ TitleBar *NotebookExplorer::setupTitleBar(QWidget *p_parent) act->setChecked(widgetConfig.getNodeExplorerCloseBeforeOpenWithEnabled()); } + setupExploreModeMenu(titleBar); + return titleBar; } @@ -383,10 +383,6 @@ const QSharedPointer &NotebookExplorer::currentNotebook() const void NotebookExplorer::setupViewMenu(QMenu *p_menu) { - if (!p_menu->isEmpty()) { - return; - } - auto ag = new QActionGroup(p_menu); auto act = ag->addAction(tr("View By Configuration")); @@ -467,6 +463,45 @@ void NotebookExplorer::setupRecycleBinMenu(QMenu *p_menu) }); } +void NotebookExplorer::setupExploreModeMenu(TitleBar *p_titleBar) +{ + auto menu = p_titleBar->addMenuSubMenu(tr("Explore Mode")); + + auto ag = new QActionGroup(menu); + + auto act = ag->addAction(tr("Combined")); + act->setCheckable(true); + act->setChecked(true); + act->setData(NotebookNodeExplorer::ExploreMode::Combined); + menu->addAction(act); + + act = ag->addAction(tr("Separate, Single Column")); + act->setCheckable(true); + act->setChecked(true); + act->setData(NotebookNodeExplorer::ExploreMode::SeparateSingle); + menu->addAction(act); + + act = ag->addAction(tr("Separate, Double Columns")); + act->setCheckable(true); + act->setChecked(true); + act->setData(NotebookNodeExplorer::ExploreMode::SeparateDouble); + menu->addAction(act); + + int mode = ConfigMgr::getInst().getWidgetConfig().getNodeExplorerExploreMode(); + for (const auto &act : ag->actions()) { + if (act->data().toInt() == mode) { + act->setChecked(true); + } + } + + connect(ag, &QActionGroup::triggered, + this, [this](QAction *action) { + int mode = action->data().toInt(); + ConfigMgr::getInst().getWidgetConfig().setNodeExplorerExploreMode(mode); + m_nodeExplorer->setExploreMode(mode); + }); +} + void NotebookExplorer::saveSession() { updateSession(); @@ -560,3 +595,13 @@ void NotebookExplorer::rebuildDatabase() } } } + +QByteArray NotebookExplorer::saveState() const +{ + return m_nodeExplorer->saveState(); +} + +void NotebookExplorer::restoreState(const QByteArray &p_data) +{ + m_nodeExplorer->restoreState(p_data); +} diff --git a/src/widgets/notebookexplorer.h b/src/widgets/notebookexplorer.h index 7218d634..baf73805 100644 --- a/src/widgets/notebookexplorer.h +++ b/src/widgets/notebookexplorer.h @@ -30,6 +30,10 @@ namespace vnotex Node *currentExploredNode() const; + QByteArray saveState() const; + + void restoreState(const QByteArray &p_data); + public slots: void newNotebook(); @@ -71,6 +75,8 @@ namespace vnotex void setupRecycleBinMenu(QMenu *p_menu); + void setupExploreModeMenu(TitleBar *p_titleBar); + void saveSession(); void loadSession(); diff --git a/src/widgets/notebooknodeexplorer.cpp b/src/widgets/notebooknodeexplorer.cpp index cd8f17ee..13b40fca 100644 --- a/src/widgets/notebooknodeexplorer.cpp +++ b/src/widgets/notebooknodeexplorer.cpp @@ -19,6 +19,7 @@ #include #include #include "treewidget.h" +#include "listwidget.h" #include "dialogs/notepropertiesdialog.h" #include "dialogs/folderpropertiesdialog.h" #include "dialogs/deleteconfirmdialog.h" @@ -159,12 +160,30 @@ bool NotebookNodeExplorer::NodeData::matched(const Node *p_node) const return false; } +bool NotebookNodeExplorer::NodeData::matched(const QString &p_name) const +{ + if (isNode()) { + return m_node->getName() == p_name; + } else { + return m_externalNode->getName() == p_name; + } +} + bool NotebookNodeExplorer::NodeData::isLoaded() const { return m_loaded; } +void NotebookNodeExplorer::CacheData::clear() +{ + if (m_masterStateCache) { + m_masterStateCache->clear(); + } + m_currentSlaveName.clear(); +} + + NotebookNodeExplorer::NotebookNodeExplorer(QWidget *p_parent) : QWidget(p_parent) { @@ -221,68 +240,123 @@ void NotebookNodeExplorer::setupMasterExplorer(QWidget *p_parent) TreeWidget::setupSingleColumnHeaderlessTree(m_masterExplorer, true, true); TreeWidget::showHorizontalScrollbar(m_masterExplorer); - m_navigationWrapper.reset(new NavigationModeWrapper(m_masterExplorer)); - NavigationModeMgr::getInst().registerNavigationTarget(m_navigationWrapper.data()); + m_masterNavigationWrapper.reset(new NavigationModeWrapper(m_masterExplorer)); + NavigationModeMgr::getInst().registerNavigationTarget(m_masterNavigationWrapper.data()); connect(m_masterExplorer, &QTreeWidget::itemExpanded, - this, &NotebookNodeExplorer::loadItemChildren); + this, &NotebookNodeExplorer::loadMasterItemChildren); connect(m_masterExplorer, &QTreeWidget::customContextMenuRequested, - this, [this](const QPoint &p_pos) { + this, [this](const QPoint &pos) { if (!m_notebook) { return; } - auto item = m_masterExplorer->itemAt(p_pos); - auto data = getItemNodeData(item); + auto item = m_masterExplorer->itemAt(pos); QScopedPointer menu(WidgetsFactory::createMenu()); - if (!data.isValid()) { - createContextMenuOnRoot(menu.data()); + if (!item) { + createMasterContextMenuOnRoot(menu.data()); } else { - if (!allSelectedItemsSameType()) { + if (!isMasterAllSelectedItemsSameType()) { return; } + auto data = getItemNodeData(item); if (data.isNode()) { - createContextMenuOnNode(menu.data(), data.getNode()); + createContextMenuOnNode(menu.data(), data.getNode(), true); } else if (data.isExternalNode()) { - createContextMenuOnExternalNode(menu.data(), data.getExternalNode().data()); + createContextMenuOnExternalNode(menu.data(), data.getExternalNode().data(), true); } } if (!menu->isEmpty()) { - menu->exec(m_masterExplorer->mapToGlobal(p_pos)); + menu->exec(m_masterExplorer->mapToGlobal(pos)); } }); connect(m_masterExplorer, &QTreeWidget::itemActivated, this, [this](QTreeWidgetItem *p_item, int p_column) { Q_UNUSED(p_column); + if (!isCombinedExploreMode()) { + return; + } auto data = getItemNodeData(p_item); - if (!data.isValid()) { + activateItemNode(data); + }); +} + +void NotebookNodeExplorer::activateItemNode(const NodeData &p_data) +{ + if (!p_data.isValid()) { + return; + } + + if (p_data.isNode()) { + if (checkInvalidNode(p_data.getNode())) { + return; + } + emit nodeActivated(p_data.getNode(), QSharedPointer::create()); + } else if (p_data.isExternalNode()) { + // Import to config first. + if (m_autoImportExternalFiles) { + auto importedNode = importToIndex(p_data.getExternalNode()); + if (importedNode) { + emit nodeActivated(importedNode.data(), QSharedPointer::create()); + } + return; + } + + // Just open it. + emit fileActivated(p_data.getExternalNode()->fetchAbsolutePath(), QSharedPointer::create()); + } +} + +void NotebookNodeExplorer::setupSlaveExplorer() +{ + Q_ASSERT(!m_slaveExplorer); + m_slaveExplorer = new ListWidget(m_splitter); + m_splitter->addWidget(m_slaveExplorer); + + m_slaveExplorer->setContextMenuPolicy(Qt::CustomContextMenu); + m_slaveExplorer->setSelectionMode(QAbstractItemView::ExtendedSelection); + + connect(m_slaveExplorer, &QListWidget::customContextMenuRequested, + this, [this](const QPoint &pos) { + Q_ASSERT(!isCombinedExploreMode()); + if (!m_notebook) { return; } - if (data.isNode()) { - if (checkInvalidNode(data.getNode())) { - return; - } - emit nodeActivated(data.getNode(), QSharedPointer::create()); - } else if (data.isExternalNode()) { - // Import to config first. - if (m_autoImportExternalFiles) { - auto importedNode = importToIndex(data.getExternalNode()); - if (importedNode) { - emit nodeActivated(importedNode.data(), QSharedPointer::create()); - } + auto item = m_slaveExplorer->itemAt(pos); + QScopedPointer menu(WidgetsFactory::createMenu()); + if (!item) { + createSlaveContextMenuOnMasterNode(menu.data()); + } else { + if (!isSlaveAllSelectedItemsSameType()) { return; } - // Just open it. - emit fileActivated(data.getExternalNode()->fetchAbsolutePath(), - QSharedPointer::create()); + auto data = getItemNodeData(item); + if (data.isNode()) { + createContextMenuOnNode(menu.data(), data.getNode(), false); + } else if (data.isExternalNode()) { + createContextMenuOnExternalNode(menu.data(), data.getExternalNode().data(), false); + } + } + + if (!menu->isEmpty()) { + menu->exec(m_slaveExplorer->mapToGlobal(pos)); } }); + connect(m_slaveExplorer, &QListWidget::itemActivated, + this, [this](QListWidgetItem *p_item) { + Q_ASSERT(!isCombinedExploreMode()); + auto data = getItemNodeData(p_item); + activateItemNode(data); + }); + + m_slaveNavigationWrapper.reset(new NavigationModeWrapper(m_slaveExplorer)); + NavigationModeMgr::getInst().registerNavigationTarget(m_slaveNavigationWrapper.data()); } void NotebookNodeExplorer::setNotebook(const QSharedPointer &p_notebook) @@ -295,7 +369,7 @@ void NotebookNodeExplorer::setNotebook(const QSharedPointer &p_noteboo disconnect(m_notebook.data(), nullptr, this, nullptr); } - saveNotebookTreeState(); + cacheState(true); m_notebook = p_notebook; @@ -306,17 +380,12 @@ void NotebookNodeExplorer::setNotebook(const QSharedPointer &p_noteboo }); } - generateNodeTree(); + generateMasterNodeTree(); } -void NotebookNodeExplorer::clearExplorer() +void NotebookNodeExplorer::generateMasterNodeTree() { m_masterExplorer->clear(); -} - -void NotebookNodeExplorer::generateNodeTree() -{ - clearExplorer(); if (!m_notebook) { return; @@ -334,15 +403,25 @@ void NotebookNodeExplorer::generateNodeTree() } // Restore current item. - auto currentNode = stateCache()->getCurrentItem(); - - if (currentNode) { - setCurrentNode(currentNode); - } else { + bool restored = false; + auto &cacheData = getCache(); + auto curMasterNode = cacheData.m_masterStateCache->getCurrentItem(); + if (curMasterNode) { + restored = true; + setCurrentMasterNode(curMasterNode); + } else if (!isCombinedExploreMode()) { + // Manually update slave explorer on first run of generation. + updateSlaveExplorer(); + } + if (!cacheData.m_currentSlaveName.isEmpty() && !isCombinedExploreMode()) { + restored = true; + setCurrentSlaveNode(cacheData.m_currentSlaveName); + } + if (!restored) { focusNormalNode(); } - stateCache()->clear(); + cacheData.clear(); } void NotebookNodeExplorer::loadRootNode(const Node *p_node) const @@ -354,8 +433,12 @@ void NotebookNodeExplorer::loadRootNode(const Node *p_node) const auto externalChildren = p_node->fetchExternalChildren(); // TODO: Sort external children. for (const auto &child : externalChildren) { + if (!belongsToMasterExplorer(child.data())) { + continue; + } + auto item = new QTreeWidgetItem(m_masterExplorer); - loadNode(item, child); + loadMasterExternalNode(item, child); } } @@ -363,12 +446,16 @@ void NotebookNodeExplorer::loadRootNode(const Node *p_node) const auto children = p_node->getChildren(); sortNodes(children); for (const auto &child : children) { + if (!belongsToMasterExplorer(child.data())) { + continue; + } + auto item = new QTreeWidgetItem(m_masterExplorer); - loadNode(item, child.data(), 1); + loadMasterNode(item, child.data(), 1); } } -static void clearTreeWigetItemChildren(QTreeWidgetItem *p_item) +static void clearTreeWidgetItemChildren(QTreeWidgetItem *p_item) { auto children = p_item->takeChildren(); for (auto &child : children) { @@ -376,38 +463,38 @@ static void clearTreeWigetItemChildren(QTreeWidgetItem *p_item) } } -void NotebookNodeExplorer::loadNode(QTreeWidgetItem *p_item, Node *p_node, int p_level) const +void NotebookNodeExplorer::loadMasterNode(QTreeWidgetItem *p_item, Node *p_node, int p_level) const { if (!p_node->isLoaded()) { p_node->load(); } - clearTreeWigetItemChildren(p_item); + clearTreeWidgetItemChildren(p_item); - fillTreeItem(p_item, p_node, p_level > 0); + fillMasterItem(p_item, p_node, p_level > 0); - loadChildren(p_item, p_node, p_level - 1); + loadMasterNodeChildren(p_item, p_node, p_level - 1); - if (stateCache()->contains(p_item) && p_item->childCount() > 0) { + if (getCache().m_masterStateCache->contains(p_item) && p_item->childCount() > 0) { if (p_item->isExpanded()) { - loadItemChildren(p_item); + loadMasterItemChildren(p_item); } else { - // itemExpanded() will trigger loadItemChildren(). + // itemExpanded() will trigger loadMasterItemChildren(). p_item->setExpanded(true); } } } -void NotebookNodeExplorer::loadNode(QTreeWidgetItem *p_item, const QSharedPointer &p_node) const +void NotebookNodeExplorer::loadMasterExternalNode(QTreeWidgetItem *p_item, const QSharedPointer &p_node) const { - clearTreeWigetItemChildren(p_item); + clearTreeWidgetItemChildren(p_item); - fillTreeItem(p_item, p_node); + fillMasterItem(p_item, p_node); // No children for external node. } -void NotebookNodeExplorer::loadChildren(QTreeWidgetItem *p_item, Node *p_node, int p_level) const +void NotebookNodeExplorer::loadMasterNodeChildren(QTreeWidgetItem *p_item, Node *p_node, int p_level) const { if (p_level < 0) { return; @@ -418,36 +505,60 @@ void NotebookNodeExplorer::loadChildren(QTreeWidgetItem *p_item, Node *p_node, i auto externalChildren = p_node->fetchExternalChildren(); // TODO: Sort external children. for (const auto &child : externalChildren) { + if (!belongsToMasterExplorer(child.data())) { + continue; + } + auto item = new QTreeWidgetItem(p_item); - loadNode(item, child); + loadMasterExternalNode(item, child); } } auto children = p_node->getChildren(); sortNodes(children); for (const auto &child : children) { + if (!belongsToMasterExplorer(child.data())) { + continue; + } + auto item = new QTreeWidgetItem(p_item); - loadNode(item, child.data(), p_level); + loadMasterNode(item, child.data(), p_level); } } -void NotebookNodeExplorer::fillTreeItem(QTreeWidgetItem *p_item, Node *p_node, bool p_loaded) const +void NotebookNodeExplorer::fillMasterItem(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->setIcon(Column::Name, getIcon(p_node)); p_item->setToolTip(Column::Name, generateToolTip(p_node)); } -void NotebookNodeExplorer::fillTreeItem(QTreeWidgetItem *p_item, const QSharedPointer &p_node) const +void NotebookNodeExplorer::fillMasterItem(QTreeWidgetItem *p_item, const QSharedPointer &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->setIcon(Column::Name, getIcon(p_node.data())); p_item->setToolTip(Column::Name, tr("[External] %1").arg(p_node->getName())); } -const QIcon &NotebookNodeExplorer::getNodeItemIcon(const Node *p_node) const +void NotebookNodeExplorer::fillSlaveItem(QListWidgetItem *p_item, Node *p_node) const +{ + setItemNodeData(p_item, NodeData(p_node, true)); + p_item->setText(p_node->getName()); + p_item->setIcon(getIcon(p_node)); + p_item->setToolTip(generateToolTip(p_node)); +} + +void NotebookNodeExplorer::fillSlaveItem(QListWidgetItem *p_item, const QSharedPointer &p_node) const +{ + setItemNodeData(p_item, NodeData(p_node)); + p_item->setText(p_node->getName()); + p_item->setIcon(getIcon(p_node.data())); + p_item->setToolTip(tr("[External] %1").arg(p_node->getName())); +} + +const QIcon &NotebookNodeExplorer::getIcon(const Node *p_node) const { if (p_node->hasContent()) { return p_node->exists() ? s_nodeIcons[NodeIcon::FileNode] : s_nodeIcons[NodeIcon::InvalidFileNode]; @@ -456,12 +567,12 @@ const QIcon &NotebookNodeExplorer::getNodeItemIcon(const Node *p_node) const } } -const QIcon &NotebookNodeExplorer::getNodeItemIcon(const ExternalNode *p_node) const +const QIcon &NotebookNodeExplorer::getIcon(const ExternalNode *p_node) const { return p_node->isFolder() ? s_nodeIcons[NodeIcon::ExternalFolderNode] : s_nodeIcons[NodeIcon::ExternalFileNode]; } -Node *NotebookNodeExplorer::getCurrentNode() const +Node *NotebookNodeExplorer::getCurrentMasterNode() const { auto item = m_masterExplorer->currentItem(); if (item) { @@ -483,6 +594,37 @@ Node *NotebookNodeExplorer::getCurrentNode() const return nullptr; } +Node *NotebookNodeExplorer::getCurrentSlaveNode() const +{ + auto item = m_slaveExplorer->currentItem(); + if (item) { + auto data = getItemNodeData(item); + if (data.isNode()) { + return data.getNode(); + } + } + + return nullptr; +} + +NotebookNodeExplorer::NodeData NotebookNodeExplorer::getCurrentMasterNodeData() const +{ + auto item = m_masterExplorer->currentItem(); + if (item) { + return getItemNodeData(item); + } + return NodeData(); +} + +NotebookNodeExplorer::NodeData NotebookNodeExplorer::getCurrentSlaveNodeData() const +{ + auto item = m_slaveExplorer->currentItem(); + if (item) { + return getItemNodeData(item); + } + return NodeData(); +} + void NotebookNodeExplorer::setItemNodeData(QTreeWidgetItem *p_item, const NodeData &p_data) { p_item->setData(Column::Name, Qt::UserRole, QVariant::fromValue(p_data)); @@ -497,26 +639,50 @@ NotebookNodeExplorer::NodeData NotebookNodeExplorer::getItemNodeData(const QTree return p_item->data(Column::Name, Qt::UserRole).value(); } +void NotebookNodeExplorer::setItemNodeData(QListWidgetItem *p_item, const NodeData &p_data) +{ + p_item->setData(Qt::UserRole, QVariant::fromValue(p_data)); +} + +NotebookNodeExplorer::NodeData NotebookNodeExplorer::getItemNodeData(const QListWidgetItem *p_item) +{ + if (!p_item) { + return NodeData(); + } + + return p_item->data(Qt::UserRole).value(); +} + void NotebookNodeExplorer::updateNode(Node *p_node) { if (p_node && p_node->getNotebook() != m_notebook) { return; } - auto item = findNode(p_node); + if (p_node && !belongsToMasterExplorer(p_node)) { + updateSlaveExplorer(); + return; + } + + auto item = findMasterNode(p_node); if (item) { bool expanded = item->isExpanded(); item->setExpanded(false); - loadNode(item, p_node, 1); + loadMasterNode(item, p_node, 1); item->setExpanded(expanded); - } else { - saveNotebookTreeState(false); - generateNodeTree(); + if (!isCombinedExploreMode()) { + updateSlaveExplorer(); + } + } else { + cacheState(false); + + generateMasterNodeTree(); } } -QTreeWidgetItem *NotebookNodeExplorer::findNode(const Node *p_node) const +// TODO: we could do it faster by going from the root node directly. +QTreeWidgetItem *NotebookNodeExplorer::findMasterNode(const Node *p_node) const { if (!p_node) { return nullptr; @@ -524,7 +690,7 @@ QTreeWidgetItem *NotebookNodeExplorer::findNode(const Node *p_node) const auto cnt = m_masterExplorer->topLevelItemCount(); for (int i = 0; i < cnt; ++i) { - auto item = findNode(m_masterExplorer->topLevelItem(i), p_node); + auto item = findMasterNode(m_masterExplorer->topLevelItem(i), p_node); if (item) { return item; } @@ -533,7 +699,7 @@ QTreeWidgetItem *NotebookNodeExplorer::findNode(const Node *p_node) const return nullptr; } -QTreeWidgetItem *NotebookNodeExplorer::findNode(QTreeWidgetItem *p_item, const Node *p_node) const +QTreeWidgetItem *NotebookNodeExplorer::findMasterNode(QTreeWidgetItem *p_item, const Node *p_node) const { auto data = getItemNodeData(p_item); if (data.matched(p_node)) { @@ -542,7 +708,7 @@ QTreeWidgetItem *NotebookNodeExplorer::findNode(QTreeWidgetItem *p_item, const N auto cnt = p_item->childCount(); for (int i = 0; i < cnt; ++i) { - auto item = findNode(p_item->child(i), p_node); + auto item = findMasterNode(p_item->child(i), p_node); if (item) { return item; } @@ -551,54 +717,55 @@ QTreeWidgetItem *NotebookNodeExplorer::findNode(QTreeWidgetItem *p_item, const N return nullptr; } -QTreeWidgetItem *NotebookNodeExplorer::findNodeChild(QTreeWidgetItem *p_item, const Node *p_node) const +QListWidgetItem *NotebookNodeExplorer::findSlaveNode(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); + for (int i = 0; i < m_slaveExplorer->count(); ++i) { + auto data = getItemNodeData(m_slaveExplorer->item(i)); if (data.matched(p_node)) { - return child; + return m_slaveExplorer->item(i); } } - - 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()) { + if (!p_node) { m_masterExplorer->setCurrentItem(nullptr); return; } Q_ASSERT(p_node->getNotebook() == m_notebook); - // Nodes from root to p_node. + if (belongsToMasterExplorer(p_node)) { + setCurrentMasterNode(p_node); + } else { + setCurrentMasterNode(p_node->getParent()); + + setCurrentSlaveNode(p_node); + } +} + +void NotebookNodeExplorer::setCurrentMasterNode(Node *p_node) +{ + if (!p_node || p_node->isRoot()) { + m_masterExplorer->setCurrentItem(nullptr); + return; + } + + Q_ASSERT(p_node && belongsToMasterExplorer(p_node)); + + // (rootNode, p_node]. QList nodes; auto node = p_node; - while (node->getParent()) { + while (!node->isRoot()) { nodes.push_front(node); node = node->getParent(); } QList items; auto nodeIt = nodes.constBegin(); - auto item = findNodeTopLevelItem(m_masterExplorer, *nodeIt); + auto item = findMasterNodeInTopLevelItems(m_masterExplorer, *nodeIt); if (!item) { return; } @@ -614,16 +781,15 @@ void NotebookNodeExplorer::setCurrentNode(Node *p_node) auto data = getItemNodeData(item); Q_ASSERT(data.isNode()); if (!data.isLoaded()) { - loadNode(item, data.getNode(), 1); + loadMasterNode(item, data.getNode(), 1); } - auto childItem = findNodeChild(item, *nodeIt); + auto childItem = findMasterNodeInDirectChildren(item, *nodeIt); if (!childItem) { return; } - items.push_back(childItem); - item = childItem; + items.push_back(item); ++nodeIt; } @@ -637,18 +803,100 @@ void NotebookNodeExplorer::setCurrentNode(Node *p_node) m_masterExplorer->setCurrentItem(item); } -void NotebookNodeExplorer::saveNotebookTreeState(bool p_saveCurrentItem) +QTreeWidgetItem *NotebookNodeExplorer::findMasterNodeInDirectChildren(QTreeWidgetItem *p_item, const Node *p_node) const { - if (m_notebook) { - stateCache()->save(m_masterExplorer, p_saveCurrentItem); + 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::findMasterNodeInTopLevelItems(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::setCurrentSlaveNode(const Node *p_node) +{ + if (!p_node) { + m_slaveExplorer->setCurrentItem(nullptr); + return; + } + + Q_ASSERT(!belongsToMasterExplorer(p_node)); + + ListWidget::forEachItem(m_slaveExplorer, [this, p_node](QListWidgetItem *item) { + auto data = getItemNodeData(item); + if (data.matched(p_node)) { + m_slaveExplorer->setCurrentItem(item); + return false; + } + return true; + }); + + if (m_slaveExplorer->currentItem() && m_masterExplorer->hasFocus()) { + // To get focus after creating a new note. + m_slaveExplorer->setFocus(); } } -QSharedPointer> NotebookNodeExplorer::stateCache() const +void NotebookNodeExplorer::setCurrentSlaveNode(const QString &p_name) +{ + if (p_name.isEmpty()) { + m_slaveExplorer->setCurrentItem(nullptr); + return; + } + + ListWidget::forEachItem(m_slaveExplorer, [this, &p_name](QListWidgetItem *item) { + auto data = getItemNodeData(item); + if (data.matched(p_name)) { + m_slaveExplorer->setCurrentItem(item); + return false; + } + return true; + }); +} + +void NotebookNodeExplorer::cacheState(bool p_saveCurrent) +{ + if (m_notebook) { + auto &cacheData = getCache(); + cacheData.m_masterStateCache->save(m_masterExplorer, p_saveCurrent); + if (p_saveCurrent && !isCombinedExploreMode()) { + cacheData.m_currentSlaveName.clear(); + auto item = m_slaveExplorer->currentItem(); + if (item) { + auto data = getItemNodeData(item); + if (data.isNode()) { + cacheData.m_currentSlaveName = data.getNode()->getName(); + } else { + cacheData.m_currentSlaveName = data.getExternalNode()->getName(); + } + } + } + } +} + +NotebookNodeExplorer::CacheData &NotebookNodeExplorer::getCache() const { Q_ASSERT(m_notebook); - auto it = m_stateCache.find(m_notebook.data()); - if (it == m_stateCache.end()) { + auto it = const_cast(this)->m_cache.find(m_notebook.data()); + if (it == const_cast(this)->m_cache.end()) { auto keyFunc = [](const QTreeWidgetItem *p_item, bool &p_ok) { auto data = NotebookNodeExplorer::getItemNodeData(p_item); if (data.isNode()) { @@ -659,22 +907,22 @@ QSharedPointer> NotebookNodeExplorer::stateCache() p_ok = false; return static_cast(nullptr); }; - auto cache = QSharedPointer>::create(keyFunc); - it = const_cast(this)->m_stateCache.insert(m_notebook.data(), cache); + it = const_cast(this)->m_cache.insert(m_notebook.data(), CacheData()); + it.value().m_masterStateCache = QSharedPointer>::create(keyFunc); } return it.value(); } -void NotebookNodeExplorer::clearStateCache(const Notebook *p_notebook) +void NotebookNodeExplorer::clearCache(const Notebook *p_notebook) { - auto it = m_stateCache.find(p_notebook); - if (it != m_stateCache.end()) { - it.value()->clear(); + auto it = m_cache.find(p_notebook); + if (it != m_cache.end()) { + it.value().clear(); } } -void NotebookNodeExplorer::createContextMenuOnRoot(QMenu *p_menu) +void NotebookNodeExplorer::createMasterContextMenuOnRoot(QMenu *p_menu) { createAndAddAction(Action::NewNote, p_menu); @@ -694,100 +942,123 @@ void NotebookNodeExplorer::createContextMenuOnRoot(QMenu *p_menu) createAndAddAction(Action::OpenLocation, p_menu); } -void NotebookNodeExplorer::createContextMenuOnNode(QMenu *p_menu, const Node *p_node) +void NotebookNodeExplorer::createContextMenuOnNode(QMenu *p_menu, const Node *p_node, bool p_master) { - const int selectedSize = m_masterExplorer->selectedItems().size(); + const int selectedSize = p_master ? m_masterExplorer->selectedItems().size() : m_slaveExplorer->selectedItems().size(); - createAndAddAction(Action::Open, p_menu); + createAndAddAction(Action::Open, p_menu, p_master); - addOpenWithMenu(p_menu); + addOpenWithMenu(p_menu, p_master); p_menu->addSeparator(); if (selectedSize == 1 && p_node->isContainer()) { - createAndAddAction(Action::ExpandAll, p_menu); + createAndAddAction(Action::ExpandAll, p_menu, p_master); } p_menu->addSeparator(); - createAndAddAction(Action::NewNote, p_menu); + createAndAddAction(Action::NewNote, p_menu, p_master); - createAndAddAction(Action::NewFolder, p_menu); + createAndAddAction(Action::NewFolder, p_menu, p_master); p_menu->addSeparator(); - createAndAddAction(Action::Copy, p_menu); + createAndAddAction(Action::Copy, p_menu, p_master); - createAndAddAction(Action::Cut, p_menu); + createAndAddAction(Action::Cut, p_menu, p_master); if (selectedSize == 1 && isPasteOnNodeAvailable(p_node)) { - createAndAddAction(Action::Paste, p_menu); + createAndAddAction(Action::Paste, p_menu, p_master); } - createAndAddAction(Action::Delete, p_menu); + createAndAddAction(Action::Delete, p_menu, p_master); - createAndAddAction(Action::RemoveFromConfig, p_menu); + createAndAddAction(Action::RemoveFromConfig, p_menu, p_master); p_menu->addSeparator(); - createAndAddAction(Action::Reload, p_menu); + createAndAddAction(Action::Reload, p_menu, p_master); - createAndAddAction(Action::ReloadIndex, p_menu); - - createAndAddAction(Action::Sort, p_menu); + createAndAddAction(Action::Sort, p_menu, p_master); if (selectedSize == 1 && m_notebook->tag() && !p_node->isContainer()) { p_menu->addSeparator(); - createAndAddAction(Action::Tag, p_menu); + createAndAddAction(Action::Tag, p_menu, p_master); } p_menu->addSeparator(); - createAndAddAction(Action::PinToQuickAccess, p_menu); + createAndAddAction(Action::PinToQuickAccess, p_menu, p_master); if (selectedSize == 1) { - createAndAddAction(Action::CopyPath, p_menu); + createAndAddAction(Action::CopyPath, p_menu, p_master); - createAndAddAction(Action::OpenLocation, p_menu); + createAndAddAction(Action::OpenLocation, p_menu, p_master); - createAndAddAction(Action::Properties, p_menu); + createAndAddAction(Action::Properties, p_menu, p_master); } } -void NotebookNodeExplorer::createContextMenuOnExternalNode(QMenu *p_menu, const ExternalNode *p_node) +void NotebookNodeExplorer::createContextMenuOnExternalNode(QMenu *p_menu, const ExternalNode *p_node, bool p_master) { Q_UNUSED(p_node); - const int selectedSize = m_masterExplorer->selectedItems().size(); - createAndAddAction(Action::Open, p_menu); + const int selectedSize = p_master ? m_masterExplorer->selectedItems().size() : m_slaveExplorer->selectedItems().size(); - addOpenWithMenu(p_menu); + createAndAddAction(Action::Open, p_menu, p_master); + + addOpenWithMenu(p_menu, p_master); p_menu->addSeparator(); - createAndAddAction(Action::ImportToConfig, p_menu); + createAndAddAction(Action::ImportToConfig, p_menu, p_master); p_menu->addSeparator(); - createAndAddAction(Action::PinToQuickAccess, p_menu); + createAndAddAction(Action::PinToQuickAccess, p_menu, p_master); if (selectedSize == 1) { - createAndAddAction(Action::CopyPath, p_menu); + createAndAddAction(Action::CopyPath, p_menu, p_master); - createAndAddAction(Action::OpenLocation, p_menu); + createAndAddAction(Action::OpenLocation, p_menu, p_master); } } +void NotebookNodeExplorer::createSlaveContextMenuOnMasterNode(QMenu *p_menu) +{ + auto masterNode = getSlaveExplorerMasterNode(); + if (!masterNode) { + // Current master node may be an external node. + return; + } + + createAndAddAction(Action::NewNote, p_menu, false); + + createAndAddAction(Action::NewFolder, p_menu, false); + + if (isPasteOnNodeAvailable(masterNode)) { + p_menu->addSeparator(); + createAndAddAction(Action::Paste, p_menu, false); + } + + p_menu->addSeparator(); + + createAndAddAction(Action::Reload, p_menu, false); + + createAndAddAction(Action::OpenLocation, p_menu, false); +} + 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 *NotebookNodeExplorer::createAction(Action p_act, QObject *p_parent, bool p_master) { QAction *act = nullptr; switch (p_act) { @@ -816,8 +1087,8 @@ QAction *NotebookNodeExplorer::createAction(Action p_act, QObject *p_parent) tr("&Properties (Rename)"), p_parent); connect(act, &QAction::triggered, - this, [this]() { - auto node = getCurrentNode(); + this, [this, p_master]() { + auto node = p_master ? getCurrentMasterNode() : getCurrentSlaveNode(); if (checkInvalidNode(node)) { return; } @@ -841,6 +1112,7 @@ QAction *NotebookNodeExplorer::createAction(Action p_act, QObject *p_parent) act = new QAction(tr("Open Locat&ion"), p_parent); connect(act, &QAction::triggered, this, [this]() { + // Always use the master node no matter it is in master/slave explorer. auto item = m_masterExplorer->currentItem(); if (!item) { if (m_notebook) { @@ -849,24 +1121,23 @@ QAction *NotebookNodeExplorer::createAction(Action p_act, QObject *p_parent) } 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()) { - const auto &externalNode = data.getExternalNode(); - locationPath = externalNode->fetchAbsolutePath(); - if (!externalNode->isFolder()) { - locationPath = PathUtils::parentDirPath(locationPath); - } + auto data = getItemNodeData(item); + Node *node = nullptr; + if (data.isNode()) { + node = data.getNode(); + } else { + // Open the parent folder of the external node. + node = data.getExternalNode()->getNode(); + } + + if (checkInvalidNode(node)) { + return; + } + + auto locationPath = node->fetchAbsolutePath(); + if (!node->isContainer()) { + locationPath = PathUtils::parentDirPath(locationPath); } if (!locationPath.isEmpty()) { @@ -878,12 +1149,12 @@ QAction *NotebookNodeExplorer::createAction(Action p_act, QObject *p_parent) 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) { + this, [this, p_master]() { + NodeData data = p_master ? getCurrentMasterNodeData() : getCurrentSlaveNodeData(); + if (!data.isValid()) { return; } - auto data = getItemNodeData(item); + QString nodePath; if (data.isNode()) { auto node = data.getNode(); @@ -904,43 +1175,47 @@ QAction *NotebookNodeExplorer::createAction(Action p_act, QObject *p_parent) case Action::Copy: act = new QAction(tr("&Copy"), p_parent); connect(act, &QAction::triggered, - this, [this]() { - copySelectedNodes(false); + this, [this, p_master]() { + copySelectedNodes(false, p_master); }); break; case Action::Cut: act = new QAction(tr("C&ut"), p_parent); connect(act, &QAction::triggered, - this, [this]() { - copySelectedNodes(true); + this, [this, p_master]() { + copySelectedNodes(true, p_master); }); break; case Action::Paste: act = new QAction(tr("&Paste"), p_parent); connect(act, &QAction::triggered, - this, [this]() { - pasteNodesFromClipboard(); - }); + this, &NotebookNodeExplorer::pasteNodesFromClipboard); break; case Action::Delete: act = new QAction(tr("&Delete"), p_parent); connect(act, &QAction::triggered, - this, &NotebookNodeExplorer::removeSelectedNodes); + this, [this, p_master]() { + removeSelectedNodes(p_master); + }); break; case Action::RemoveFromConfig: act = new QAction(tr("&Remove From Index"), p_parent); connect(act, &QAction::triggered, - this, &NotebookNodeExplorer::removeSelectedNodesFromConfig); + this, [this, p_master]() { + removeSelectedNodesFromConfig(p_master); + }); break; case Action::Sort: act = new QAction(generateMenuActionIcon("sort.svg"), tr("&Sort"), p_parent); connect(act, &QAction::triggered, - this, &NotebookNodeExplorer::manualSort); + this, [this, p_master]() { + manualSort(p_master); + }); break; case Action::Reload: @@ -953,7 +1228,7 @@ QAction *NotebookNodeExplorer::createAction(Action p_act, QObject *p_parent) break; case Action::ReloadIndex: - act = new QAction(tr("Relo&ad Index From Disk"), p_parent); + act = new QAction(tr("Relo&ad Index Of Notebook From Disk"), p_parent); connect(act, &QAction::triggered, this, [this]() { if (!m_notebook) { @@ -966,6 +1241,7 @@ QAction *NotebookNodeExplorer::createAction(Action p_act, QObject *p_parent) return; } + m_notebook->reloadNodes(); reload(); }); break; @@ -973,8 +1249,8 @@ QAction *NotebookNodeExplorer::createAction(Action p_act, QObject *p_parent) case Action::ImportToConfig: act = new QAction(tr("&Import To Index"), p_parent); connect(act, &QAction::triggered, - this, [this]() { - auto nodes = getSelectedNodes().second; + this, [this, p_master]() { + auto nodes = p_master ? getMasterSelectedNodesAndExternalNodes().second : getSlaveSelectedNodesAndExternalNodes().second; importToIndex(nodes); }); break; @@ -982,13 +1258,43 @@ QAction *NotebookNodeExplorer::createAction(Action p_act, QObject *p_parent) case Action::Open: act = new QAction(tr("&Open"), p_parent); connect(act, &QAction::triggered, - this, &NotebookNodeExplorer::openSelectedNodes); + this, [this, p_master]() { + // Support nodes and external nodes. + // Do nothing for folders. + auto selectedNodes = p_master ? getMasterSelectedNodesAndExternalNodes() : getSlaveSelectedNodesAndExternalNodes(); + for (const auto &externalNode : selectedNodes.second) { + if (!externalNode->isFolder()) { + emit fileActivated(externalNode->fetchAbsolutePath(), QSharedPointer::create()); + } + } + + for (const auto &node : selectedNodes.first) { + if (checkInvalidNode(node)) { + continue; + } + + if (node->hasContent()) { + emit nodeActivated(node, QSharedPointer::create()); + } + } + }); break; case Action::ExpandAll: act = new QAction(tr("&Expand All\t*"), p_parent); connect(act, &QAction::triggered, - this, &NotebookNodeExplorer::expandCurrentNodeAll); + this, [this]() { + auto item = m_masterExplorer->currentItem(); + if (!item || item->childCount() == 0) { + return; + } + auto data = getItemNodeData(item); + if (!data.isNode()) { + return; + } + + TreeWidget::expandRecursively(item); + }); break; case Action::PinToQuickAccess: @@ -996,8 +1302,8 @@ QAction *NotebookNodeExplorer::createAction(Action p_act, QObject *p_parent) tr("Pin To &Quick Access"), p_parent); connect(act, &QAction::triggered, - this, [this]() { - auto nodes = getSelectedNodes(); + this, [this, p_master]() { + auto nodes = p_master ? getMasterSelectedNodesAndExternalNodes() : getSlaveSelectedNodesAndExternalNodes(); QStringList files; bool hasFilteredAway = false; for (const auto &node : nodes.first) { @@ -1026,21 +1332,15 @@ QAction *NotebookNodeExplorer::createAction(Action p_act, QObject *p_parent) 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()) { + this, [this, p_master]() { + Q_ASSERT(m_notebook->tag()); + auto node = p_master ? getCurrentMasterNode() : getCurrentSlaveNode(); + Q_ASSERT(node); + if (checkInvalidNode(node)) { return; } - auto data = getItemNodeData(item); - if (data.isNode()) { - auto node = data.getNode(); - if (checkInvalidNode(node)) { - return; - } - - ViewTagsDialog dialog(node, VNoteX::getInst().getMainWindow()); - dialog.exec(); - } + ViewTagsDialog dialog(node, VNoteX::getInst().getMainWindow()); + dialog.exec(); }); break; @@ -1052,16 +1352,16 @@ QAction *NotebookNodeExplorer::createAction(Action p_act, QObject *p_parent) return act; } -QAction *NotebookNodeExplorer::createAndAddAction(Action p_act, QMenu *p_menu) +QAction *NotebookNodeExplorer::createAndAddAction(Action p_act, QMenu *p_menu, bool p_master) { - auto act = createAction(p_act, p_menu); + auto act = createAction(p_act, p_menu, p_master); p_menu->addAction(act); return act; } -void NotebookNodeExplorer::copySelectedNodes(bool p_move) +void NotebookNodeExplorer::copySelectedNodes(bool p_move, bool p_master) { - auto nodes = getSelectedNodes().first; + auto nodes = p_master ? getMasterSelectedNodesAndExternalNodes().first : getSlaveSelectedNodesAndExternalNodes().first; if (nodes.isEmpty()) { return; } @@ -1087,7 +1387,7 @@ void NotebookNodeExplorer::copySelectedNodes(bool p_move) VNoteX::getInst().showStatusMessageShort(tr("Copied %n item(s)", "", static_cast(nrItems))); } -QPair, QVector>> NotebookNodeExplorer::getSelectedNodes() const +QPair, QVector>> NotebookNodeExplorer::getMasterSelectedNodesAndExternalNodes() const { QPair, QVector>> nodes; @@ -1104,6 +1404,23 @@ QPair, QVector>> NotebookNodeExplor return nodes; } +QPair, QVector>> NotebookNodeExplorer::getSlaveSelectedNodesAndExternalNodes() const +{ + QPair, QVector>> nodes; + + auto items = m_slaveExplorer->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 NotebookNodeExplorer::tryFetchClipboardData() { auto text = ClipboardUtils::getTextFromClipboard(); @@ -1157,8 +1474,11 @@ static QSharedPointer getNodeFromClipboardDataItem(const NodeClipboardData void NotebookNodeExplorer::pasteNodesFromClipboard() { // Identify the dest node. - auto destNode = getCurrentNode(); + auto destNode = getCurrentMasterNode(); if (!destNode) { + if (!m_notebook) { + return; + } destNode = m_notebook->getRootNode().data(); } else { // Current node may be a file node. @@ -1237,7 +1557,7 @@ void NotebookNodeExplorer::pasteNodesFromClipboard() updateNode(node); // Deleted src nodes may be the current node in cache. Clear the cache. - clearStateCache(node->getNotebook()); + clearCache(node->getNotebook()); } // Update and expand dest node. Select all pasted nodes. @@ -1251,9 +1571,9 @@ void NotebookNodeExplorer::pasteNodesFromClipboard() VNoteX::getInst().showStatusMessageShort(tr("Pasted %n item(s)", "", pastedNodes.size())); } -void NotebookNodeExplorer::setNodeExpanded(const Node *p_node, bool p_expanded) +void NotebookNodeExplorer::setMasterNodeExpanded(const Node *p_node, bool p_expanded) { - auto item = findNode(p_node); + auto item = findMasterNode(p_node); if (item) { item->setExpanded(p_expanded); } @@ -1261,37 +1581,55 @@ void NotebookNodeExplorer::setNodeExpanded(const Node *p_node, bool p_expanded) void NotebookNodeExplorer::selectNodes(const QVector &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; + if (p_nodes.isEmpty()) { + return; + } + + // All the nodes should either belong to master or slave explorer. + if (belongsToMasterExplorer(p_nodes[0])) { + bool firstItem = true; + for (auto node : p_nodes) { + auto item = findMasterNode(node); + if (item) { + auto flags = firstItem ? QItemSelectionModel::ClearAndSelect : QItemSelectionModel::Select; + m_masterExplorer->setCurrentItem(item, 0, flags); + firstItem = false; + } + } + } else { + bool firstItem = true; + for (auto node : p_nodes) { + auto item = findSlaveNode(node); + if (item) { + auto flags = firstItem ? QItemSelectionModel::ClearAndSelect : QItemSelectionModel::Select; + m_slaveExplorer->setCurrentItem(item, flags); + firstItem = false; + } } } } -void NotebookNodeExplorer::removeSelectedNodes() +void NotebookNodeExplorer::removeSelectedNodes(bool p_master) { const QString text = tr("Delete these folders and notes?"); const QString info = tr("Deleted files could be found in the recycle bin of notebook."); - auto nodes = confirmSelectedNodes(tr("Confirm Deletion"), text, info); + auto nodes = confirmSelectedNodes(tr("Confirm Deletion"), text, info, p_master); removeNodes(nodes, false); } QVector NotebookNodeExplorer::confirmSelectedNodes(const QString &p_title, const QString &p_text, - const QString &p_info) const + const QString &p_info, + bool p_master) const { - auto nodes = getSelectedNodes().first; + auto nodes = p_master ? getMasterSelectedNodesAndExternalNodes().first : getSlaveSelectedNodesAndExternalNodes().first; if (nodes.isEmpty()) { return nodes; } QVector items; for (const auto &node : nodes) { - items.push_back(ConfirmItemInfo(getNodeItemIcon(node), + items.push_back(ConfirmItemInfo(getIcon(node), node->getName(), node->fetchAbsolutePath(), node->fetchAbsolutePath(), @@ -1362,11 +1700,12 @@ void NotebookNodeExplorer::removeNodes(QVector p_nodes, bool p_configOnl VNoteX::getInst().showStatusMessageShort(tr("Deleted/Removed %n item(s)", "", nrDeleted)); } -void NotebookNodeExplorer::removeSelectedNodesFromConfig() +void NotebookNodeExplorer::removeSelectedNodesFromConfig(bool p_master) { 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.")); + tr("Files are not touched but just removed from notebook index."), + p_master); removeNodes(nodes, true); } @@ -1389,12 +1728,12 @@ void NotebookNodeExplorer::filterAwayChildrenNodes(QVector &p_nodes) void NotebookNodeExplorer::updateAndExpandNode(Node *p_node) { - setNodeExpanded(p_node, false); + setMasterNodeExpanded(p_node, false); updateNode(p_node); - setNodeExpanded(p_node, true); + setMasterNodeExpanded(p_node, true); } -bool NotebookNodeExplorer::allSelectedItemsSameType() const +bool NotebookNodeExplorer::isMasterAllSelectedItemsSameType() const { auto items = m_masterExplorer->selectedItems(); if (items.size() < 2) { @@ -1412,6 +1751,24 @@ bool NotebookNodeExplorer::allSelectedItemsSameType() const return true; } +bool NotebookNodeExplorer::isSlaveAllSelectedItemsSameType() const +{ + auto items = m_slaveExplorer->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; + } + } + + return true; +} + void NotebookNodeExplorer::reload() { updateNode(nullptr); @@ -1419,12 +1776,13 @@ void NotebookNodeExplorer::reload() void NotebookNodeExplorer::focusNormalNode() { - auto item = m_masterExplorer->currentItem(); - if (item) { - return; + if (isCombinedExploreMode()) { + m_masterExplorer->setCurrentItem(m_masterExplorer->topLevelItem(0)); + } else { + if (m_slaveExplorer->count() > 0) { + m_slaveExplorer->setCurrentRow(0); + } } - - m_masterExplorer->setCurrentItem(m_masterExplorer->topLevelItem(0)); } void NotebookNodeExplorer::sortNodes(QVector> &p_nodes) const @@ -1452,7 +1810,7 @@ void NotebookNodeExplorer::sortNodes(QVector> &p_nodes) con sortNodes(p_nodes, firstFileIndex, p_nodes.size(), m_viewOrder); } -void NotebookNodeExplorer::sortNodes(QVector> &p_nodes, int p_start, int p_end, int p_viewOrder) const +void NotebookNodeExplorer::sortNodes(QVector> &p_nodes, int p_start, int p_end, ViewOrder p_viewOrder) const { if (p_start >= p_end) { return; @@ -1523,9 +1881,9 @@ void NotebookNodeExplorer::setAutoImportExternalFiles(bool p_enabled) m_autoImportExternalFiles = p_enabled; } -void NotebookNodeExplorer::manualSort() +void NotebookNodeExplorer::manualSort(bool p_master) { - auto node = getCurrentNode(); + auto node = p_master ? getCurrentMasterNode() : getCurrentSlaveNode(); if (!node) { return; } @@ -1590,7 +1948,7 @@ Node *NotebookNodeExplorer::currentExploredFolderNode() const return nullptr; } - auto node = getCurrentNode(); + auto node = getCurrentMasterNode(); if (node) { if (!node->isContainer()) { node = node->getParent(); @@ -1609,7 +1967,15 @@ Node *NotebookNodeExplorer::currentExploredNode() const return nullptr; } - return getCurrentNode(); + if (isCombinedExploreMode()) { + return getCurrentMasterNode(); + } else { + auto node = getCurrentSlaveNode(); + if (!node) { + node = getCurrentMasterNode(); + } + return node; + } } void NotebookNodeExplorer::setViewOrder(int p_order) @@ -1618,38 +1984,63 @@ void NotebookNodeExplorer::setViewOrder(int p_order) return; } - m_viewOrder = p_order; - reload(); + if (p_order >= 0 && p_order < ViewOrder::ViewOrderMax) { + m_viewOrder = static_cast(p_order); + reload(); + } } -void NotebookNodeExplorer::openSelectedNodes() +void NotebookNodeExplorer::setExploreMode(int p_mode) { - // 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::create()); - } + if (m_exploreMode == p_mode) { + return; } - for (const auto &node : selectedNodes.first) { - if (checkInvalidNode(node)) { - continue; + if (p_mode >= 0 && p_mode < ExploreMode::ExploreModeMax) { + m_exploreMode = static_cast(p_mode); + switch (m_exploreMode) { + case ExploreMode::Combined: + setFocusProxy(m_masterExplorer); + + Q_ASSERT(m_slaveExplorer); + m_slaveExplorer->clear(); + m_slaveExplorer->hide(); + + disconnect(m_masterExplorer, &QTreeWidget::currentItemChanged, + this, &NotebookNodeExplorer::updateSlaveExplorer); + break; + + case ExploreMode::SeparateSingle: + Q_FALLTHROUGH(); + case ExploreMode::SeparateDouble: + if (!m_slaveExplorer) { + setupSlaveExplorer(); + } + + setFocusProxy(m_slaveExplorer); + + m_slaveExplorer->show(); + m_splitter->setOrientation(m_exploreMode == ExploreMode::SeparateSingle ? Qt::Vertical : Qt::Horizontal); + + connect(m_masterExplorer, &QTreeWidget::currentItemChanged, + this, &NotebookNodeExplorer::updateSlaveExplorer); + break; + + default: + Q_ASSERT(false); + return; } - if (node->hasContent()) { - emit nodeActivated(node, QSharedPointer::create()); - } + reload(); } } -QStringList NotebookNodeExplorer::getSelectedNodesPath() const +QStringList NotebookNodeExplorer::getSelectedNodesPath(bool p_master) const { QStringList files; // Support nodes and external nodes. - auto selectedNodes = getSelectedNodes(); + auto selectedNodes = p_master ? getMasterSelectedNodesAndExternalNodes() : getSlaveSelectedNodesAndExternalNodes(); for (const auto &externalNode : selectedNodes.second) { files << externalNode->fetchAbsolutePath(); } @@ -1658,7 +2049,6 @@ QStringList NotebookNodeExplorer::getSelectedNodesPath() const if (checkInvalidNode(node)) { continue; } - files << node->fetchAbsolutePath(); } @@ -1724,38 +2114,7 @@ bool NotebookNodeExplorer::checkInvalidNode(Node *p_node) const 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) +void NotebookNodeExplorer::addOpenWithMenu(QMenu *p_menu, bool p_master) { auto subMenu = p_menu->addMenu(tr("Open &With")); @@ -1764,8 +2123,8 @@ void NotebookNodeExplorer::addOpenWithMenu(QMenu *p_menu) for (const auto &pro : sessionConfig.getExternalPrograms()) { QAction *act = subMenu->addAction(pro.m_name); connect(act, &QAction::triggered, - this, [this, act]() { - openSelectedNodesWithExternalProgram(act->data().toString()); + this, [this, act, p_master]() { + openSelectedNodesWithCommand(act->data().toString(), p_master); }); act->setData(pro.m_command); WidgetUtils::addActionShortcutText(act, pro.m_shortcut); @@ -1775,9 +2134,11 @@ void NotebookNodeExplorer::addOpenWithMenu(QMenu *p_menu) subMenu->addSeparator(); { - auto defaultAct = subMenu->addAction(tr("System Default Program"), - this, - &NotebookNodeExplorer::openSelectedNodesWithDefaultProgram); + auto defaultAct = subMenu->addAction(tr("System Default Program")); + connect(defaultAct, &QAction::triggered, + this, [this, defaultAct, p_master]() { + openSelectedNodesWithCommand(QString(), p_master); + }); const auto &coreConfig = ConfigMgr::getInst().getCoreConfig(); WidgetUtils::addActionShortcutText(defaultAct, coreConfig.getShortcut(CoreConfig::OpenWithDefaultProgram)); } @@ -1803,7 +2164,13 @@ void NotebookNodeExplorer::setupShortcuts() auto shortcut = WidgetUtils::createShortcut(coreConfig.getShortcut(CoreConfig::OpenWithDefaultProgram), this); if (shortcut) { connect(shortcut, &QShortcut::activated, - this, &NotebookNodeExplorer::openSelectedNodesWithDefaultProgram); + this, [this]() { + bool isMaster = true; + if (!isCombinedExploreMode()) { + isMaster = m_masterExplorer->hasFocus(); + } + openSelectedNodesWithCommand(QString(), isMaster); + }); } } @@ -1814,37 +2181,20 @@ void NotebookNodeExplorer::setupShortcuts() if (shortcut) { connect(shortcut, &QShortcut::activated, this, [this, command]() { - openSelectedNodesWithExternalProgram(command); + bool isMaster = true; + if (!isCombinedExploreMode()) { + isMaster = m_masterExplorer->hasFocus(); + } + openSelectedNodesWithCommand(command, isMaster); }); } } } -void NotebookNodeExplorer::openSelectedNodesWithDefaultProgram() +void NotebookNodeExplorer::openSelectedNodesWithCommand(const QString &p_command, bool p_master) { const bool closeBefore = ConfigMgr::getInst().getWidgetConfig().getNodeExplorerCloseBeforeOpenWithEnabled(); - const auto files = getSelectedNodesPath(); - for (const auto &file : files) { - if (file.isEmpty()) { - continue; - } - - if (closeBefore) { - auto event = QSharedPointer::create(); - emit closeFileRequested(file, event); - if (!event->m_response.toBool()) { - continue; - } - } - - WidgetUtils::openUrlByDesktop(QUrl::fromLocalFile(file)); - } -} - -void NotebookNodeExplorer::openSelectedNodesWithExternalProgram(const QString &p_command) -{ - const bool closeBefore = ConfigMgr::getInst().getWidgetConfig().getNodeExplorerCloseBeforeOpenWithEnabled(); - const auto files = getSelectedNodesPath(); + const auto files = getSelectedNodesPath(p_master); for (const auto &file : files) { if (file.isEmpty()) { continue; @@ -1858,20 +2208,24 @@ void NotebookNodeExplorer::openSelectedNodesWithExternalProgram(const QString &p } } - auto command = p_command; - command.replace(QStringLiteral("%1"), QString("\"%1\"").arg(file)); - ProcessUtils::startDetached(command); + if (p_command.isEmpty()) { + WidgetUtils::openUrlByDesktop(QUrl::fromLocalFile(file)); + } else { + auto command = p_command; + command.replace(QStringLiteral("%1"), QString("\"%1\"").arg(file)); + ProcessUtils::startDetached(command); + } } } -void NotebookNodeExplorer::loadItemChildren(QTreeWidgetItem *p_item) const +void NotebookNodeExplorer::loadMasterItemChildren(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); + loadMasterNode(child, data.getNode(), 1); } } } @@ -1895,3 +2249,115 @@ QString NotebookNodeExplorer::generateToolTip(const Node *p_node) tip += tr("Modified Time: %1").arg(Utils::dateTimeString(p_node->getModifiedTimeUtc().toLocalTime())); return tip; } + +QByteArray NotebookNodeExplorer::saveState() const +{ + return m_splitter->saveState(); +} + +void NotebookNodeExplorer::restoreState(const QByteArray &p_data) +{ + m_splitter->restoreState(p_data); +} + +bool NotebookNodeExplorer::belongsToMasterExplorer(const Node *p_node) const +{ + switch (m_exploreMode) { + case ExploreMode::Combined: + return true; + + case ExploreMode::SeparateSingle: + Q_FALLTHROUGH(); + case ExploreMode::SeparateDouble: + return p_node ? p_node->isContainer() : true; + break; + + default: + Q_ASSERT(false); + break; + } + + return true; +} + +bool NotebookNodeExplorer::belongsToMasterExplorer(const ExternalNode *p_node) const +{ + if (isCombinedExploreMode()) { + return true; + } else { + return p_node ? p_node->isFolder() : false; + } +} + +void NotebookNodeExplorer::updateSlaveExplorer() +{ + Q_ASSERT(!isCombinedExploreMode()); + m_slaveExplorer->clear(); + + const Node *masterNode = nullptr; + auto item = m_masterExplorer->currentItem(); + if (item) { + const int selectedSize = m_masterExplorer->selectedItems().size(); + if (selectedSize > 1) { + return; + } + + masterNode = getCurrentMasterNode(); + } else { + // Root node. + masterNode = m_notebook ? m_notebook->getRootNode().data() : nullptr; + } + + if (!masterNode) { + return; + } + + Q_ASSERT(masterNode->isContainer() && masterNode->isLoaded()); + + // External children. + if (m_externalFilesVisible) { + auto externalChildren = masterNode->fetchExternalChildren(); + // TODO: Sort external children. + for (const auto &child : externalChildren) { + if (child->isFolder()) { + continue; + } + + auto item = new QListWidgetItem(m_slaveExplorer); + fillSlaveItem(item, child); + } + } + + auto children = masterNode->getChildren(); + sortNodes(children); + for (const auto &child : children) { + if (child->isContainer()) { + continue; + } + + Q_ASSERT(child->isLoaded()); + auto item = new QListWidgetItem(m_slaveExplorer); + fillSlaveItem(item, child.data()); + } +} + +bool NotebookNodeExplorer::isCombinedExploreMode() const +{ + return m_exploreMode == ExploreMode::Combined; +} + +Node *NotebookNodeExplorer::getSlaveExplorerMasterNode() const +{ + Q_ASSERT(!isCombinedExploreMode()); + auto item = m_masterExplorer->currentItem(); + if (item) { + const int selectedSize = m_masterExplorer->selectedItems().size(); + if (selectedSize > 1) { + return nullptr; + } + return getCurrentMasterNode(); + } else { + // Root node. + return (m_notebook ? m_notebook->getRootNode().data() : nullptr); + } +} diff --git a/src/widgets/notebooknodeexplorer.h b/src/widgets/notebooknodeexplorer.h index 227b51d3..d051b536 100644 --- a/src/widgets/notebooknodeexplorer.h +++ b/src/widgets/notebooknodeexplorer.h @@ -12,8 +12,6 @@ #include "navigationmodewrapper.h" class QSplitter; -class QTreeWidget; -class QTreeWidgetItem; class QMenu; namespace vnotex @@ -21,6 +19,7 @@ namespace vnotex class Notebook; class Node; class TreeWidget; + class ListWidget; struct FileOpenParameters; class Event; class ExternalNode; @@ -65,6 +64,8 @@ namespace vnotex bool matched(const Node *p_node) const; + bool matched(const QString &p_name) const; + bool isLoaded() const; private: @@ -89,22 +90,26 @@ namespace vnotex ViewOrderMax }; + enum ExploreMode + { + Combined = 0, + SeparateSingle, + SeparateDouble, + ExploreModeMax + }; + explicit NotebookNodeExplorer(QWidget *p_parent = nullptr); void setNotebook(const QSharedPointer &p_notebook); - Node *getCurrentNode() const; - - // Update the tree of @p_node. - // If @p_node is null, update the whole tree. - void updateNode(Node *p_node); - void setCurrentNode(Node *p_node); void reload(); void setViewOrder(int p_order); + void setExploreMode(int p_mode); + void setExternalFilesVisible(bool p_visible); void setAutoImportExternalFiles(bool p_enabled); @@ -113,6 +118,10 @@ namespace vnotex Node *currentExploredNode() const; + QByteArray saveState() const; + + void restoreState(const QByteArray &p_data); + static QString generateToolTip(const Node *p_node); signals: @@ -156,81 +165,103 @@ namespace vnotex Tag }; + struct CacheData + { + void clear(); + + QSharedPointer> m_masterStateCache; + + QString m_currentSlaveName; + }; + void setupUI(); void setupShortcuts(); void setupMasterExplorer(QWidget *p_parent = nullptr); - void clearExplorer(); + void setupSlaveExplorer(); - void generateNodeTree(); + void generateMasterNodeTree(); void loadRootNode(const Node *p_node) const; - void loadNode(QTreeWidgetItem *p_item, Node *p_node, int p_level) const; + void loadMasterNode(QTreeWidgetItem *p_item, Node *p_node, int p_level) const; - void loadChildren(QTreeWidgetItem *p_item, Node *p_node, int p_level) const; + void loadMasterNodeChildren(QTreeWidgetItem *p_item, Node *p_node, int p_level) const; - void loadItemChildren(QTreeWidgetItem *p_item) const; + void loadMasterItemChildren(QTreeWidgetItem *p_item) const; - void loadNode(QTreeWidgetItem *p_item, const QSharedPointer &p_node) const; + void loadMasterExternalNode(QTreeWidgetItem *p_item, const QSharedPointer &p_node) const; - void fillTreeItem(QTreeWidgetItem *p_item, Node *p_node, bool p_loaded) const; + void fillMasterItem(QTreeWidgetItem *p_item, Node *p_node, bool p_loaded) const; - void fillTreeItem(QTreeWidgetItem *p_item, const QSharedPointer &p_node) const; + void fillMasterItem(QTreeWidgetItem *p_item, const QSharedPointer &p_node) const; - const QIcon &getNodeItemIcon(const Node *p_node) const; + void fillSlaveItem(QListWidgetItem *p_item, Node *p_node) const; - const QIcon &getNodeItemIcon(const ExternalNode *p_node) const; + void fillSlaveItem(QListWidgetItem *p_item, const QSharedPointer &p_node) const; + + const QIcon &getIcon(const Node *p_node) const; + + const QIcon &getIcon(const ExternalNode *p_node) const; void initNodeIcons() const; - QTreeWidgetItem *findNode(const Node *p_node) const; + QTreeWidgetItem *findMasterNode(const Node *p_node) const; - QTreeWidgetItem *findNode(QTreeWidgetItem *p_item, const Node *p_node) const; + QTreeWidgetItem *findMasterNode(QTreeWidgetItem *p_item, const Node *p_node) const; - QTreeWidgetItem *findNodeChild(QTreeWidgetItem *p_item, const Node *p_node) const; + QTreeWidgetItem *findMasterNodeInDirectChildren(QTreeWidgetItem *p_item, const Node *p_node) const; - QTreeWidgetItem *findNodeTopLevelItem(QTreeWidget *p_tree, const Node *p_node) const; + QTreeWidgetItem *findMasterNodeInTopLevelItems(QTreeWidget *p_tree, const Node *p_node) const; - void saveNotebookTreeState(bool p_saveCurrentItem = true); + QListWidgetItem *findSlaveNode(const Node *p_node) const; - QSharedPointer> stateCache() const; + void cacheState(bool p_saveCurrent); - void clearStateCache(const Notebook *p_notebook); + // Get cache data of current notebook. + CacheData &getCache() const; - void createContextMenuOnRoot(QMenu *p_menu); + void clearCache(const Notebook *p_notebook); - void createContextMenuOnNode(QMenu *p_menu, const Node *p_node); + void createMasterContextMenuOnRoot(QMenu *p_menu); - void createContextMenuOnExternalNode(QMenu *p_menu, const ExternalNode *p_node); + void createContextMenuOnNode(QMenu *p_menu, const Node *p_node, bool p_master); + + void createContextMenuOnExternalNode(QMenu *p_menu, const ExternalNode *p_node, bool p_master); + + void createSlaveContextMenuOnMasterNode(QMenu *p_menu); // Factory function to create action. - QAction *createAction(Action p_act, QObject *p_parent); + QAction *createAction(Action p_act, QObject *p_parent, bool p_master); - QAction *createAndAddAction(Action p_act, QMenu *p_menu); + QAction *createAndAddAction(Action p_act, QMenu *p_menu, bool p_master = true); - void copySelectedNodes(bool p_move); + void copySelectedNodes(bool p_move, bool p_master); void pasteNodesFromClipboard(); - QPair, QVector>> getSelectedNodes() const; + QPair, QVector>> getMasterSelectedNodesAndExternalNodes() const; - void removeSelectedNodes(); + QPair, QVector>> getSlaveSelectedNodesAndExternalNodes() const; - void removeSelectedNodesFromConfig(); + void removeSelectedNodes(bool p_master); + + void removeSelectedNodesFromConfig(bool p_master); QVector confirmSelectedNodes(const QString &p_title, const QString &p_text, - const QString &p_info) const; + const QString &p_info, + bool p_master) const; static QSharedPointer tryFetchClipboardData(); bool isPasteOnNodeAvailable(const Node *p_node) const; - void setNodeExpanded(const Node *p_node, bool p_expanded); + void setMasterNodeExpanded(const Node *p_node, bool p_expanded); + // Select both master and slave nodes. void selectNodes(const QVector &p_nodes); void removeNodes(QVector p_nodes, bool p_configOnly); @@ -240,19 +271,19 @@ namespace vnotex void updateAndExpandNode(Node *p_node); // Check if all selected items are the same type for operations. - bool allSelectedItemsSameType() const; + bool isMasterAllSelectedItemsSameType() const; + + bool isSlaveAllSelectedItemsSameType() const; void focusNormalNode(); void sortNodes(QVector> &p_nodes) const; // [p_start, p_end). - void sortNodes(QVector> &p_nodes, int p_start, int p_end, int p_viewOrder) const; + void sortNodes(QVector> &p_nodes, int p_start, int p_end, ViewOrder p_viewOrder) const; // Sort nodes in config file. - void manualSort(); - - void openSelectedNodes(); + void manualSort(bool p_master); QSharedPointer importToIndex(QSharedPointer p_node); @@ -262,33 +293,67 @@ namespace vnotex // Return true if it is invalid. bool checkInvalidNode(Node *p_node) const; - void expandCurrentNodeAll(); + void addOpenWithMenu(QMenu *p_menu, bool p_master); - void expandItemRecursively(QTreeWidgetItem *p_item); + QStringList getSelectedNodesPath(bool p_master) const; - void addOpenWithMenu(QMenu *p_menu); + void openSelectedNodesWithCommand(const QString &p_command, bool p_master); - QStringList getSelectedNodesPath() const; + bool belongsToMasterExplorer(const Node *p_node) const; - void openSelectedNodesWithDefaultProgram(); + bool belongsToMasterExplorer(const ExternalNode *p_node) const; - void openSelectedNodesWithExternalProgram(const QString &p_command); + void updateSlaveExplorer(); + + Node *getCurrentMasterNode() const; + + Node *getCurrentSlaveNode() const; + + NodeData getCurrentMasterNodeData() const; + + NodeData getCurrentSlaveNodeData() const; + + Node *getSlaveExplorerMasterNode() const; + + bool isCombinedExploreMode() const; + + // Update the tree of @p_node if there is any. Or update the node itself if it is in slave explorer. + // If @p_node is null, update the whole tree. + void updateNode(Node *p_node); + + void setCurrentMasterNode(Node *p_node); + + void setCurrentSlaveNode(const Node *p_node); + + void setCurrentSlaveNode(const QString &p_name); + + void activateItemNode(const NodeData &p_data); static NotebookNodeExplorer::NodeData getItemNodeData(const QTreeWidgetItem *p_item); + static NotebookNodeExplorer::NodeData getItemNodeData(const QListWidgetItem *p_item); + static void setItemNodeData(QTreeWidgetItem *p_item, const NodeData &p_data); + static void setItemNodeData(QListWidgetItem *p_item, const NodeData &p_data); + QSplitter *m_splitter = nullptr; TreeWidget *m_masterExplorer = nullptr; + ListWidget *m_slaveExplorer = nullptr; + QSharedPointer m_notebook; - QHash>> m_stateCache; + QHash m_cache; - QScopedPointer> m_navigationWrapper; + QScopedPointer> m_masterNavigationWrapper; - int m_viewOrder = ViewOrder::OrderedByConfiguration; + QScopedPointer> m_slaveNavigationWrapper; + + ViewOrder m_viewOrder = ViewOrder::OrderedByConfiguration; + + ExploreMode m_exploreMode = ExploreMode::Combined; bool m_externalFilesVisible = true; diff --git a/src/widgets/treewidget.cpp b/src/widgets/treewidget.cpp index 3b986b39..baf78c97 100644 --- a/src/widgets/treewidget.cpp +++ b/src/widgets/treewidget.cpp @@ -265,3 +265,20 @@ void TreeWidget::unmark(QTreeWidgetItem *p_item, int p_column) p_item->setData(p_column, Qt::ForegroundRole, QVariant()); p_item->setData(p_column, Qt::BackgroundRole, QVariant()); } + +void TreeWidget::expandRecursively(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) { + expandRecursively(p_item->child(i)); + } +} diff --git a/src/widgets/treewidget.h b/src/widgets/treewidget.h index 74184cc5..7dc05472 100644 --- a/src/widgets/treewidget.h +++ b/src/widgets/treewidget.h @@ -42,6 +42,8 @@ namespace vnotex // @p_func: return false to abort the iteration. static void forEachItem(const QTreeWidget *p_widget, const std::function &p_func); + static void expandRecursively(QTreeWidgetItem *p_item); + signals: // Emit when single item is selected and Drag&Drop to move internally. void itemMoved(QTreeWidgetItem *p_item);