From 517d977266583a263a7a4de2a4c66d0fadcb138b Mon Sep 17 00:00:00 2001 From: Le Tan Date: Wed, 24 Feb 2021 20:46:05 +0800 Subject: [PATCH] NotebookExplorer: support view order (#1700) --- src/core/notebook/node.cpp | 2 + src/core/widgetconfig.cpp | 13 +++++ src/core/widgetconfig.h | 5 ++ src/data/core/core.qrc | 1 + src/data/core/icons/view.svg | 15 ++++++ src/data/core/vnotex.json | 4 +- src/export/exportdata.cpp | 27 +++++++++- src/export/webviewexporter.cpp | 5 ++ src/widgets/dialogs/exportdialog.cpp | 10 +++- src/widgets/notebookexplorer.cpp | 74 +++++++++++++++++++++++++ src/widgets/notebookexplorer.h | 4 ++ src/widgets/notebooknodeexplorer.cpp | 81 +++++++++++++++++++++++++++- src/widgets/notebooknodeexplorer.h | 17 ++++++ src/widgets/titlebar.cpp | 57 +++++++++++--------- src/widgets/titlebar.h | 2 + 15 files changed, 286 insertions(+), 31 deletions(-) create mode 100644 src/data/core/icons/view.svg diff --git a/src/core/notebook/node.cpp b/src/core/notebook/node.cpp index acefbd72..47e4aada 100644 --- a/src/core/notebook/node.cpp +++ b/src/core/notebook/node.cpp @@ -259,6 +259,8 @@ QDir Node::toDir() const if (isContainer()) { return QDir(fetchAbsolutePath()); } + Q_ASSERT(false); + return QDir(); } void Node::load() diff --git a/src/core/widgetconfig.cpp b/src/core/widgetconfig.cpp index fc15272c..1dd2c3a0 100644 --- a/src/core/widgetconfig.cpp +++ b/src/core/widgetconfig.cpp @@ -22,6 +22,8 @@ void WidgetConfig::init(const QJsonObject &p_app, } m_findAndReplaceOptions = static_cast(READINT(QStringLiteral("find_and_replace_options"))); + + m_noteExplorerViewOrder = READINT(QStringLiteral("note_explorer_view_order")); } QJsonObject WidgetConfig::toJson() const @@ -29,6 +31,7 @@ QJsonObject WidgetConfig::toJson() const QJsonObject obj; obj[QStringLiteral("outline_auto_expanded_level")] = m_outlineAutoExpandedLevel; obj[QStringLiteral("find_and_replace_options")] = static_cast(m_findAndReplaceOptions); + obj[QStringLiteral("note_explorer_view_order")] = m_noteExplorerViewOrder; return obj; } @@ -51,3 +54,13 @@ void WidgetConfig::setFindAndReplaceOptions(FindOptions p_options) { updateConfig(m_findAndReplaceOptions, p_options, this); } + +int WidgetConfig::getNoteExplorerViewOrder() const +{ + return m_noteExplorerViewOrder; +} + +void WidgetConfig::setNoteExplorerViewOrder(int p_viewOrder) +{ + updateConfig(m_noteExplorerViewOrder, p_viewOrder, this); +} diff --git a/src/core/widgetconfig.h b/src/core/widgetconfig.h index f17f5956..fa2631d9 100644 --- a/src/core/widgetconfig.h +++ b/src/core/widgetconfig.h @@ -24,10 +24,15 @@ namespace vnotex FindOptions getFindAndReplaceOptions() const; void setFindAndReplaceOptions(FindOptions p_options); + int getNoteExplorerViewOrder() const; + void setNoteExplorerViewOrder(int p_viewOrder); + private: int m_outlineAutoExpandedLevel = 6; FindOptions m_findAndReplaceOptions = FindOption::None; + + int m_noteExplorerViewOrder = 0; }; } diff --git a/src/data/core/core.qrc b/src/data/core/core.qrc index 8d26618e..db9cfab6 100644 --- a/src/data/core/core.qrc +++ b/src/data/core/core.qrc @@ -20,6 +20,7 @@ icons/help.svg icons/menu.svg icons/settings.svg + icons/view.svg icons/settings_menu.svg icons/whatsthis.svg icons/help_menu.svg diff --git a/src/data/core/icons/view.svg b/src/data/core/icons/view.svg new file mode 100644 index 00000000..176f2647 --- /dev/null +++ b/src/data/core/icons/view.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + diff --git a/src/data/core/vnotex.json b/src/data/core/vnotex.json index d028e209..8d1a306d 100644 --- a/src/data/core/vnotex.json +++ b/src/data/core/vnotex.json @@ -285,6 +285,8 @@ "//comment" : "Level of the heading in outline that should expand to automatically (1-6)", "outline_auto_expanded_level" : 6, "//comment" : "Default find options in FindAndReplace", - "find_and_replace_options" : 16 + "find_and_replace_options" : 16, + "//comment" : "View order of the note explorer", + "note_explorer_view_order" : 0 } } diff --git a/src/export/exportdata.cpp b/src/export/exportdata.cpp index 9ed952ef..aca49c09 100644 --- a/src/export/exportdata.cpp +++ b/src/export/exportdata.cpp @@ -80,6 +80,7 @@ bool ExportPdfOption::operator==(const ExportPdfOption &p_other) const QJsonObject ExportOption::toJson() const { QJsonObject obj; + obj["target_format"] = static_cast(m_targetFormat); obj["use_transparent_bg"] = m_useTransparentBg; obj["output_dir"] = m_outputDir; obj["recursive"] = m_recursive; @@ -95,6 +96,29 @@ void ExportOption::fromJson(const QJsonObject &p_obj) return; } + { + int fmt = p_obj["target_format"].toInt(); + switch (fmt) { + case static_cast(ExportFormat::Markdown): + m_targetFormat = ExportFormat::Markdown; + break; + + case static_cast(ExportFormat::PDF): + m_targetFormat = ExportFormat::PDF; + break; + + case static_cast(ExportFormat::Custom): + m_targetFormat = ExportFormat::Custom; + break; + + case static_cast(ExportFormat::HTML): + Q_FALLTHROUGH(); + default: + m_targetFormat = ExportFormat::HTML; + break; + } + } + m_useTransparentBg = p_obj["use_transparent_bg"].toBool(); m_outputDir = p_obj["output_dir"].toString(); m_recursive = p_obj["recursive"].toBool(); @@ -105,7 +129,8 @@ void ExportOption::fromJson(const QJsonObject &p_obj) bool ExportOption::operator==(const ExportOption &p_other) const { - bool ret = m_useTransparentBg == p_other.m_useTransparentBg + bool ret = m_targetFormat == p_other.m_targetFormat + && m_useTransparentBg == p_other.m_useTransparentBg && m_outputDir == p_other.m_outputDir && m_recursive == p_other.m_recursive && m_exportAttachments == p_other.m_exportAttachments; diff --git a/src/export/webviewexporter.cpp b/src/export/webviewexporter.cpp index 35197a34..f36929bc 100644 --- a/src/export/webviewexporter.cpp +++ b/src/export/webviewexporter.cpp @@ -478,6 +478,11 @@ bool WebViewExporter::doExportPdf(const ExportPdfOption &p_pdfOption, const QStr bool WebViewExporter::doExportWkhtmltopdf(const ExportPdfOption &p_pdfOption, const QString &p_outputFile, const QUrl &p_baseUrl) { + if (p_pdfOption.m_wkhtmltopdfExePath.isEmpty()) { + qWarning() << "invalid wkhtmltopdf executable path"; + return false; + } + ExportState state = ExportState::Busy; connect(m_viewer->adapter(), &MarkdownViewerAdapter::contentReady, diff --git a/src/widgets/dialogs/exportdialog.cpp b/src/widgets/dialogs/exportdialog.cpp index caac9f34..13d679ff 100644 --- a/src/widgets/dialogs/exportdialog.cpp +++ b/src/widgets/dialogs/exportdialog.cpp @@ -359,6 +359,7 @@ void ExportDialog::startExport() // On start. { + clearInformationText(); m_exportedFile.clear(); m_exportOngoing = true; updateUIOnExport(); @@ -407,7 +408,14 @@ void ExportDialog::updateUIOnExport() int ExportDialog::doExport(ExportOption p_option) { - // TODO: Check ExportOption. + if (p_option.m_targetFormat == ExportFormat::PDF && p_option.m_pdfOption.m_useWkhtmltopdf) { + // Check wkhtmltopdf executable. + const auto &wkExePath = p_option.m_pdfOption.m_wkhtmltopdfExePath; + if (wkExePath.isEmpty() || !QFileInfo::exists(wkExePath)) { + appendLog(tr("Please specify a valid wkhtmltopdf executable file (%1)").arg(wkExePath)); + return 0; + } + } int exportedFilesCount = 0; diff --git a/src/widgets/notebookexplorer.cpp b/src/widgets/notebookexplorer.cpp index 5338c0e1..40ad73ac 100644 --- a/src/widgets/notebookexplorer.cpp +++ b/src/widgets/notebookexplorer.cpp @@ -2,6 +2,9 @@ #include #include +#include +#include +#include #include "titlebar.h" #include "dialogs/newnotebookdialog.h" @@ -24,10 +27,12 @@ #include "messageboxhelper.h" #include #include +#include #include #include #include #include "navigationmodemgr.h" +#include "widgetsfactory.h" using namespace vnotex; @@ -77,6 +82,15 @@ TitleBar *NotebookExplorer::setupTitleBar(QWidget *p_parent) p_parent); titleBar->setWhatsThis(tr("This title bar contains buttons and menu to manage notebooks and notes.")); + { + auto viewMenu = WidgetsFactory::createMenu(titleBar); + titleBar->addActionButton(QStringLiteral("view.svg"), tr("View"), viewMenu); + connect(viewMenu, &QMenu::aboutToShow, + this, [this, viewMenu]() { + setupViewMenu(viewMenu); + }); + } + titleBar->addMenuAction(QStringLiteral("manage_notebooks.svg"), tr("&Manage Notebooks"), titleBar, @@ -300,3 +314,63 @@ const QSharedPointer &NotebookExplorer::currentNotebook() const { return m_currentNotebook; } + +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")); + act->setCheckable(true); + act->setChecked(true); + act->setData(NotebookNodeExplorer::ViewOrder::OrderedByConfiguration); + p_menu->addAction(act); + + act = ag->addAction(tr("View By Name")); + act->setCheckable(true); + act->setData(NotebookNodeExplorer::ViewOrder::OrderedByName); + p_menu->addAction(act); + + act = ag->addAction(tr("View By Name (Reversed)")); + act->setCheckable(true); + act->setData(NotebookNodeExplorer::ViewOrder::OrderedByNameReversed); + p_menu->addAction(act); + + act = ag->addAction(tr("View By Created Time")); + act->setCheckable(true); + act->setData(NotebookNodeExplorer::ViewOrder::OrderedByCreatedTime); + p_menu->addAction(act); + + act = ag->addAction(tr("View By Created Time (Reversed)")); + act->setCheckable(true); + act->setData(NotebookNodeExplorer::ViewOrder::OrderedByCreatedTimeReversed); + p_menu->addAction(act); + + act = ag->addAction(tr("View By Modified Time")); + act->setCheckable(true); + act->setData(NotebookNodeExplorer::ViewOrder::OrderedByModifiedTime); + p_menu->addAction(act); + + act = ag->addAction(tr("View By Modified Time (Reversed)")); + act->setCheckable(true); + act->setData(NotebookNodeExplorer::ViewOrder::OrderedByModifiedTimeReversed); + p_menu->addAction(act); + + int viewOrder = ConfigMgr::getInst().getWidgetConfig().getNoteExplorerViewOrder(); + for (const auto &act : ag->actions()) { + if (act->data().toInt() == viewOrder) { + act->setChecked(true); + } + } + + connect(ag, &QActionGroup::triggered, + this, [this](QAction *p_action) { + int order = p_action->data().toInt(); + ConfigMgr::getInst().getWidgetConfig().setNoteExplorerViewOrder(order); + + m_nodeExplorer->reload(); + }); +} diff --git a/src/widgets/notebookexplorer.h b/src/widgets/notebookexplorer.h index 231d3042..7024a62f 100644 --- a/src/widgets/notebookexplorer.h +++ b/src/widgets/notebookexplorer.h @@ -6,6 +6,8 @@ #include "global.h" +class QMenu; + namespace vnotex { class Notebook; @@ -62,6 +64,8 @@ namespace vnotex Node *checkNotebookAndGetCurrentExploredFolderNode() const; + void setupViewMenu(QMenu *p_menu); + NotebookSelector *m_selector = nullptr; NotebookNodeExplorer *m_nodeExplorer = nullptr; diff --git a/src/widgets/notebooknodeexplorer.cpp b/src/widgets/notebooknodeexplorer.cpp index 0410a128..479863c8 100644 --- a/src/widgets/notebooknodeexplorer.cpp +++ b/src/widgets/notebooknodeexplorer.cpp @@ -22,6 +22,8 @@ #include #include +#include +#include using namespace vnotex; @@ -333,7 +335,9 @@ void NotebookNodeExplorer::loadRootNode(const Node *p_node) const loadRecycleBinNode(recycleBinNode.data()); } - for (auto &child : p_node->getChildren()) { + auto children = p_node->getChildren(); + sortNodes(children); + for (auto &child : children) { if (recycleBinNode == child) { continue; } @@ -374,7 +378,9 @@ void NotebookNodeExplorer::loadChildren(QTreeWidgetItem *p_item, Node *p_node, i return; } - for (auto &child : p_node->getChildren()) { + auto children = p_node->getChildren(); + sortNodes(children); + for (auto &child : children) { auto item = new QTreeWidgetItem(p_item); loadNode(item, child.data(), p_level); } @@ -1316,3 +1322,74 @@ void NotebookNodeExplorer::focusNormalNode() m_masterExplorer->setCurrentItem(m_masterExplorer->topLevelItem(1)); } } + +void NotebookNodeExplorer::sortNodes(QVector> &p_nodes) const +{ + int viewOrder = ConfigMgr::getInst().getWidgetConfig().getNoteExplorerViewOrder(); + if (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()) { + firstFileIndex = i; + break; + } + } + + // Sort containers. + sortNodes(p_nodes, 0, firstFileIndex, viewOrder); + + // Sort non-containers. + sortNodes(p_nodes, firstFileIndex, p_nodes.size(), viewOrder); +} + +void NotebookNodeExplorer::sortNodes(QVector> &p_nodes, int p_start, int p_end, int p_viewOrder) const +{ + 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 &p_a, const QSharedPointer 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 &p_a, const QSharedPointer 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 &p_a, const QSharedPointer p_b) { + if (reversed) { + return p_b->getModifiedTimeUtc() < p_a->getModifiedTimeUtc(); + } else { + return p_a->getModifiedTimeUtc() < p_b->getModifiedTimeUtc(); + } + }); + break; + + default: + break; + } +} diff --git a/src/widgets/notebooknodeexplorer.h b/src/widgets/notebooknodeexplorer.h index f6716acb..b623dbd8 100644 --- a/src/widgets/notebooknodeexplorer.h +++ b/src/widgets/notebooknodeexplorer.h @@ -76,6 +76,18 @@ namespace vnotex bool m_loaded = false; }; + enum ViewOrder + { + OrderedByConfiguration = 0, + OrderedByName, + OrderedByNameReversed, + OrderedByCreatedTime, + OrderedByCreatedTimeReversed, + OrderedByModifiedTime, + OrderedByModifiedTimeReversed, + ViewOrderMax + }; + explicit NotebookNodeExplorer(QWidget *p_parent = nullptr); void setNotebook(const QSharedPointer &p_notebook); @@ -191,6 +203,11 @@ namespace vnotex // Skip the recycle bin node if possible. 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; + static NotebookNodeExplorer::NodeData getItemNodeData(const QTreeWidgetItem *p_item); static void setItemNodeData(QTreeWidgetItem *p_item, const NodeData &p_data); diff --git a/src/widgets/titlebar.cpp b/src/widgets/titlebar.cpp index 116c9043..5ea4d9da 100644 --- a/src/widgets/titlebar.cpp +++ b/src/widgets/titlebar.cpp @@ -45,16 +45,13 @@ void TitleBar::setupUI(const QString &p_title, TitleBar::Actions p_actionFlags) mainLayout->addStretch(); { - setupActionButtons(p_actionFlags); - m_buttonWidget = new QWidget(this); mainLayout->addWidget(m_buttonWidget); auto btnLayout = new QHBoxLayout(m_buttonWidget); btnLayout->setContentsMargins(0, 0, 0, 0); - for (auto btn : m_actionButtons) { - btnLayout->addWidget(btn); - } + + setupActionButtons(p_actionFlags); setActionButtonsVisible(false); } @@ -79,32 +76,17 @@ QToolButton *TitleBar::newActionButton(const QString &p_iconName, const QString void TitleBar::setupActionButtons(TitleBar::Actions p_actionFlags) { + if (p_actionFlags & Action::Menu) { + m_menu = WidgetsFactory::createMenu(this); + addActionButton("menu.svg", tr("Menu"), m_menu); + } + if (p_actionFlags & Action::Settings) { - auto btn = newActionButton("settings.svg", tr("Settings"), this); + auto btn = addActionButton("settings.svg", tr("Settings")); connect(btn, &QToolButton::triggered, this, []() { // TODO. }); - m_actionButtons.push_back(btn); - } - - if (p_actionFlags & Action::Menu) { - auto btn = newActionButton("menu.svg", tr("Menu"), this); - btn->setPopupMode(QToolButton::InstantPopup); - m_actionButtons.push_back(btn); - - m_menu = WidgetsFactory::createMenu(this); - btn->setMenu(m_menu); - connect(m_menu, &QMenu::aboutToShow, - this, [this]() { - m_alwaysShowActionButtons = true; - setActionButtonsVisible(true); - }); - connect(m_menu, &QMenu::aboutToHide, - this, [this]() { - m_alwaysShowActionButtons = false; - setActionButtonsVisible(false); - }); } } @@ -160,12 +142,35 @@ QToolButton *TitleBar::addActionButton(const QString &p_iconName, const QString layout->addWidget(btn); } else { int idx = m_actionButtons.size() - 1; + if (idx < 0) { + idx = 0; + } m_actionButtons.insert(idx, btn); layout->insertWidget(idx, btn); } return btn; } +QToolButton *TitleBar::addActionButton(const QString &p_iconName, const QString &p_text, QMenu *p_menu) +{ + p_menu->setParent(this); + + auto btn = addActionButton(p_iconName, p_text); + btn->setPopupMode(QToolButton::InstantPopup); + btn->setMenu(p_menu); + connect(p_menu, &QMenu::aboutToShow, + this, [this]() { + m_alwaysShowActionButtons = true; + setActionButtonsVisible(true); + }); + connect(p_menu, &QMenu::aboutToHide, + this, [this]() { + m_alwaysShowActionButtons = false; + setActionButtonsVisible(false); + }); + return btn; +} + QHBoxLayout *TitleBar::actionButtonLayout() const { return static_cast(m_buttonWidget->layout()); diff --git a/src/widgets/titlebar.h b/src/widgets/titlebar.h index 00016b63..38097b78 100644 --- a/src/widgets/titlebar.h +++ b/src/widgets/titlebar.h @@ -28,6 +28,8 @@ namespace vnotex QToolButton *addActionButton(const QString &p_iconName, const QString &p_text); + QToolButton *addActionButton(const QString &p_iconName, const QString &p_text, QMenu *p_menu); + // Add action to the menu. QAction *addMenuAction(const QString &p_iconName, const QString &p_text);