diff --git a/src/commandlineoptions.cpp b/src/commandlineoptions.cpp index 8177c85b..f7958e48 100644 --- a/src/commandlineoptions.cpp +++ b/src/commandlineoptions.cpp @@ -27,7 +27,7 @@ CommandLineOptions::ParseResult CommandLineOptions::parse(const QStringList &p_a { QCommandLineOption webRemoteDebuggingPortOpt("remote-debugging-port", MainWindow::tr("WebEngine remote debugging port."), - "port_number"); + MainWindow::tr("port_number")); webRemoteDebuggingPortOpt.setFlags(QCommandLineOption::HiddenFromHelp); parser.addOption(webRemoteDebuggingPortOpt); diff --git a/src/core/coreconfig.cpp b/src/core/coreconfig.cpp index db8585c9..840117e1 100644 --- a/src/core/coreconfig.cpp +++ b/src/core/coreconfig.cpp @@ -88,6 +88,8 @@ void CoreConfig::init(const QJsonObject &p_app, } loadFileTypeSuffixes(appObj, userObj); + + loadUnitedEntry(appObj, userObj); } QJsonObject CoreConfig::toJson() const @@ -105,6 +107,7 @@ QJsonObject CoreConfig::toJson() const obj[QStringLiteral("per_notebook_history")] = m_perNotebookHistoryEnabled; obj[QStringLiteral("line_ending")] = lineEndingPolicyToString(m_lineEnding); obj[QStringLiteral("file_type_suffixes")] = saveFileTypeSuffixes(); + obj[QStringLiteral("united_entry")] = saveUnitedEntry(); return obj; } @@ -297,6 +300,25 @@ QJsonArray CoreConfig::saveFileTypeSuffixes() const return arr; } +void CoreConfig::loadUnitedEntry(const QJsonObject &p_app, const QJsonObject &p_user) +{ + QJsonObject unitedObj; + if (p_user.contains(QStringLiteral("united_entry"))) { + unitedObj = p_user[QStringLiteral("united_entry")].toObject(); + } else { + unitedObj = p_app[QStringLiteral("united_entry")].toObject(); + } + + m_unitedEntryAlias = unitedObj[QStringLiteral("alias")].toArray(); +} + +QJsonObject CoreConfig::saveUnitedEntry() const +{ + QJsonObject unitedObj; + unitedObj[QStringLiteral("alias")] = m_unitedEntryAlias; + return unitedObj; +} + const QVector &CoreConfig::getFileTypeSuffixes() const { return m_fileTypeSuffixes; @@ -321,3 +343,13 @@ const QStringList *CoreConfig::findFileTypeSuffix(const QString &p_name) const return nullptr; } + +const QJsonArray &CoreConfig::getUnitedEntryAlias() const +{ + return m_unitedEntryAlias; +} + +void CoreConfig::setUnitedEntryAlias(const QJsonArray &p_alias) +{ + updateConfig(m_unitedEntryAlias, p_alias, this); +} diff --git a/src/core/coreconfig.h b/src/core/coreconfig.h index fb02854a..188fa446 100644 --- a/src/core/coreconfig.h +++ b/src/core/coreconfig.h @@ -68,6 +68,7 @@ namespace vnotex MoveOneSplitUp, MoveOneSplitRight, OpenLastClosedFile, + UnitedEntry, MaxShortcut }; Q_ENUM(Shortcut) @@ -133,6 +134,9 @@ namespace vnotex const QStringList *findFileTypeSuffix(const QString &p_name) const; + const QJsonArray &getUnitedEntryAlias() const; + void setUnitedEntryAlias(const QJsonArray &p_alias); + private: friend class MainConfig; @@ -146,6 +150,10 @@ namespace vnotex QJsonArray saveFileTypeSuffixes() const; + void loadUnitedEntry(const QJsonObject &p_app, const QJsonObject &p_user); + + QJsonObject saveUnitedEntry() const; + // Theme name. QString m_theme; @@ -181,6 +189,8 @@ namespace vnotex QVector m_fileTypeSuffixes; + QJsonArray m_unitedEntryAlias; + static QStringList s_availableLocales; }; } // ns vnotex diff --git a/src/core/vnotex.cpp b/src/core/vnotex.cpp index fec06acd..ee6c4c62 100644 --- a/src/core/vnotex.cpp +++ b/src/core/vnotex.cpp @@ -20,9 +20,7 @@ using namespace vnotex; VNoteX::VNoteX(QObject *p_parent) - : QObject(p_parent), - m_mainWindow(nullptr), - m_notebookMgr(nullptr) + : QObject(p_parent) { m_instanceId = QRandomGenerator::global()->generate64(); diff --git a/src/core/vnotex.h b/src/core/vnotex.h index 5f63ff8e..941f4bef 100644 --- a/src/core/vnotex.h +++ b/src/core/vnotex.h @@ -132,19 +132,19 @@ namespace vnotex void initQuickAccess(); - MainWindow *m_mainWindow; + MainWindow *m_mainWindow = nullptr; // QObject managed. - ThemeMgr *m_themeMgr; + ThemeMgr *m_themeMgr = nullptr; // QObject managed. - TaskMgr *m_taskMgr; + TaskMgr *m_taskMgr = nullptr; // QObject managed. - NotebookMgr *m_notebookMgr; + NotebookMgr *m_notebookMgr = nullptr; // QObject managed. - BufferMgr *m_bufferMgr; + BufferMgr *m_bufferMgr = nullptr; // Used to identify app's instance. ID m_instanceId = 0; diff --git a/src/core/widgetconfig.cpp b/src/core/widgetconfig.cpp index 6bc6383c..86c32f39 100644 --- a/src/core/widgetconfig.cpp +++ b/src/core/widgetconfig.cpp @@ -46,6 +46,8 @@ void WidgetConfig::init(const QJsonObject &p_app, m_tagExplorerTwoColumnsEnabled = READBOOL(QStringLiteral("tag_explorer_two_columns_enabled")); m_newNoteDefaultFileType = READINT(QStringLiteral("new_note_default_file_type")); + + m_unitedEntryExpandAllEnabled = READBOOL(QStringLiteral("united_entry_expand_all")); } QJsonObject WidgetConfig::toJson() const @@ -68,6 +70,7 @@ QJsonObject WidgetConfig::toJson() const QStringLiteral("main_window_keep_docks_expanding_content_area"), m_mainWindowKeepDocksExpandingContentArea); obj[QStringLiteral("new_note_default_file_type")] = m_newNoteDefaultFileType; + obj[QStringLiteral("united_entry_expand_all")] = m_unitedEntryExpandAllEnabled; return obj; } @@ -200,3 +203,13 @@ void WidgetConfig::setNewNoteDefaultFileType(int p_type) { updateConfig(m_newNoteDefaultFileType, p_type, this); } + +bool WidgetConfig::getUnitedEntryExpandAllEnabled() const +{ + return m_unitedEntryExpandAllEnabled; +} + +void WidgetConfig::setUnitedEntryExpandAllEnabled(bool p_enabled) +{ + updateConfig(m_unitedEntryExpandAllEnabled, p_enabled, this); +} diff --git a/src/core/widgetconfig.h b/src/core/widgetconfig.h index 62a066db..d5094f5a 100644 --- a/src/core/widgetconfig.h +++ b/src/core/widgetconfig.h @@ -57,6 +57,9 @@ namespace vnotex int getNewNoteDefaultFileType() const; void setNewNoteDefaultFileType(int p_type); + bool getUnitedEntryExpandAllEnabled() const; + void setUnitedEntryExpandAllEnabled(bool p_enabled); + private: int m_outlineAutoExpandedLevel = 6; @@ -85,6 +88,8 @@ namespace vnotex bool m_tagExplorerTwoColumnsEnabled = false; int m_newNoteDefaultFileType = 0; + + bool m_unitedEntryExpandAllEnabled = false; }; } diff --git a/src/data/core/core.qrc b/src/data/core/core.qrc index d2467518..f647633d 100644 --- a/src/data/core/core.qrc +++ b/src/data/core/core.qrc @@ -9,6 +9,7 @@ icons/new_notebook.svg icons/notebook_menu.svg icons/task_menu.svg + icons/united_entry.svg icons/advanced_settings.svg icons/new_notebook_from_folder.svg icons/discard_editor.svg @@ -36,6 +37,7 @@ icons/notebook_default.svg icons/file_node.svg icons/folder_node.svg + icons/other_item.svg icons/manage_notebooks.svg icons/up_level.svg icons/properties.svg diff --git a/src/data/core/icons/other_item.svg b/src/data/core/icons/other_item.svg new file mode 100644 index 00000000..805c35fa --- /dev/null +++ b/src/data/core/icons/other_item.svg @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/src/data/core/icons/united_entry.svg b/src/data/core/icons/united_entry.svg new file mode 100644 index 00000000..710a8b7d --- /dev/null +++ b/src/data/core/icons/united_entry.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/data/core/vnotex.json b/src/data/core/vnotex.json index a821865f..c64006ca 100644 --- a/src/data/core/vnotex.json +++ b/src/data/core/vnotex.json @@ -59,7 +59,8 @@ "MoveOneSplitDown" : "Ctrl+G, Shift+J", "MoveOneSplitUp" : "Ctrl+G, Shift+K", "MoveOneSplitRight" : "Ctrl+G, Shift+L", - "OpenLastClosedFile" : "Ctrl+Shift+T" + "OpenLastClosedFile" : "Ctrl+Shift+T", + "UnitedEntry" : "Ctrl+G, G" }, "file_type_suffixes" : [ { @@ -98,7 +99,71 @@ "history_max_count" : 100, "per_notebook_history" : false, "//comment" : "Line ending policy for config files, platform/lf/crlf/cr", - "line_ending" : "lf" + "line_ending" : "lf", + "united_entry" : { + "alias" : [ + { + "name" : "q", + "description" : "Search for folders/files by name in all notebooks", + "value" : "find --scope all_notebook --object name --target file --target folder" + }, + { + "name" : "a", + "description" : "Search for files by content in all notebooks", + "value" : "find --scope all_notebook --object content --target file" + }, + { + "name" : "z", + "description" : "Search for files by tag in all notebooks", + "value" : "find --scope all_notebook --object tag --target file" + }, + { + "name" : "w", + "description" : "Search for notebooks by name in all notebooks", + "value" : "find --scope all_notebook --object name --target notebook" + }, + { + "name" : "e", + "description" : "Search for folders/files by name in current notebook", + "value" : "find --scope notebook --object name --target file --target folder" + }, + { + "name" : "d", + "description" : "Search for files by content in current notebook", + "value" : "find --scope notebook --object content --target file" + }, + { + "name" : "c", + "description" : "Search for files by tag in current notebook", + "value" : "find --scope notebook --object tag --target file" + }, + { + "name" : "r", + "description" : "Search for folders/files by name in current folder", + "value" : "find --scope folder --object name --target file --target folder" + }, + { + "name" : "f", + "description" : "Search for files by content in current folder", + "value" : "find --scope folder --object content --target file" + }, + { + "name" : "v", + "description" : "Search for files by tag in current folder", + "value" : "find --scope folder --object tag --target file" + }, + { + "name" : "t", + "description" : "Search for files by name in buffers", + "value" : "find --scope buffer --object name --target file" + }, + { + "name" : "g", + "description" : "Search for files by content in buffers", + "value" : "find --scope buffer --object content --target file" + } + ] + } }, "editor" : { "core": { @@ -131,7 +196,7 @@ "TypeMath" : "Ctrl+.", "TypeMathBlock" : "Ctrl+G, .", "TypeQuote" : "", - "TypeLink" : "", + "TypeLink" : "Ctrl+,", "TypeImage" : "", "TypeTable" : "Ctrl+/", "TypeMark" : "Ctrl+G, M", @@ -418,6 +483,7 @@ "main_window_keep_docks_expanding_content_area": ["OutlineDock.vnotex"], "snippet_panel_builtin_snippets_visible" : true, "tag_explorer_two_columns_enabled" : true, - "new_note_default_file_type" : 0 + "new_note_default_file_type" : 0, + "united_entry_expand_all" : false } } diff --git a/src/data/extra/themes/moonlight/interface.qss b/src/data/extra/themes/moonlight/interface.qss index 0c011989..0b0c466d 100644 --- a/src/data/extra/themes/moonlight/interface.qss +++ b/src/data/extra/themes/moonlight/interface.qss @@ -469,12 +469,12 @@ QLineEdit { } QLineEdit:focus { - border: 2px solid @widgets#qlineedit#focus#border; + border: 1px solid @widgets#qlineedit#focus#border; background-color: @widgets#qlineedit#focus#bg; } QLineEdit:hover { - border: 2px solid @widgets#qlineedit#hover#border; + border: 1px solid @widgets#qlineedit#hover#border; background-color: @widgets#qlineedit#hover#bg; } @@ -1174,3 +1174,7 @@ vnotex--ViewSplit QTabBar[ViewSplitFlash="true"]::tab:selected { vte--VTextEdit { border: none; } + +vnotex--EntryPopup { + border: 1px solid @widgets#unitedentry#popup#border; +} diff --git a/src/data/extra/themes/moonlight/palette.json b/src/data/extra/themes/moonlight/palette.json index 54dccc5e..8e371d2e 100644 --- a/src/data/extra/themes/moonlight/palette.json +++ b/src/data/extra/themes/moonlight/palette.json @@ -647,6 +647,17 @@ "fg" : "@base#master#fg", "bg" : "@base#master#bg" } + }, + "unitedentry" : { + "icon" : { + "fg" : "@base#icon#fg", + "busy" : { + "fg" : "@base#master#bg" + } + }, + "popup" : { + "border" : "@base#normal#border" + } } } } diff --git a/src/data/extra/themes/native/interface.qss b/src/data/extra/themes/native/interface.qss index 2eb53300..4b9a8659 100644 --- a/src/data/extra/themes/native/interface.qss +++ b/src/data/extra/themes/native/interface.qss @@ -127,3 +127,7 @@ vnotex--MainWindow QLabel#MainWindowTipsLabel { vnotex--ViewSplit QTabBar[ViewSplitFlash="true"]::tab:selected { background-color: @widgets#viewsplit#flash#bg; } + +vnotex--EntryPopup { + border: 1px solid @widgets#unitedentry#popup#border; +} diff --git a/src/data/extra/themes/native/palette.json b/src/data/extra/themes/native/palette.json index cdd94883..77b2feea 100644 --- a/src/data/extra/themes/native/palette.json +++ b/src/data/extra/themes/native/palette.json @@ -153,6 +153,17 @@ } } } + }, + "unitedentry" : { + "icon" : { + "fg" : "@base#icon#fg", + "busy" : { + "fg" : "@base#info#fg" + } + }, + "popup" : { + "border" : "@base#normal#border" + } } } } diff --git a/src/data/extra/themes/pure/interface.qss b/src/data/extra/themes/pure/interface.qss index 0c011989..0b0c466d 100644 --- a/src/data/extra/themes/pure/interface.qss +++ b/src/data/extra/themes/pure/interface.qss @@ -469,12 +469,12 @@ QLineEdit { } QLineEdit:focus { - border: 2px solid @widgets#qlineedit#focus#border; + border: 1px solid @widgets#qlineedit#focus#border; background-color: @widgets#qlineedit#focus#bg; } QLineEdit:hover { - border: 2px solid @widgets#qlineedit#hover#border; + border: 1px solid @widgets#qlineedit#hover#border; background-color: @widgets#qlineedit#hover#bg; } @@ -1174,3 +1174,7 @@ vnotex--ViewSplit QTabBar[ViewSplitFlash="true"]::tab:selected { vte--VTextEdit { border: none; } + +vnotex--EntryPopup { + border: 1px solid @widgets#unitedentry#popup#border; +} diff --git a/src/data/extra/themes/pure/palette.json b/src/data/extra/themes/pure/palette.json index 16a9945c..17aebb83 100644 --- a/src/data/extra/themes/pure/palette.json +++ b/src/data/extra/themes/pure/palette.json @@ -643,6 +643,17 @@ "fg" : "@base#master#fg", "bg" : "@base#master#bg" } + }, + "unitedentry" : { + "icon" : { + "fg" : "@base#icon#fg", + "busy" : { + "fg" : "@base#master#bg" + } + }, + "popup" : { + "border" : "@base#normal#border" + } } } } diff --git a/src/data/extra/themes/solarized-dark/interface.qss b/src/data/extra/themes/solarized-dark/interface.qss index acb80e65..4e2250e9 100644 --- a/src/data/extra/themes/solarized-dark/interface.qss +++ b/src/data/extra/themes/solarized-dark/interface.qss @@ -465,12 +465,12 @@ QLineEdit { } QLineEdit:focus { - border: 2px solid @widgets#qlineedit#focus#border; + border: 1px solid @widgets#qlineedit#focus#border; background-color: @widgets#qlineedit#focus#bg; } QLineEdit:hover { - border: 2px solid @widgets#qlineedit#hover#border; + border: 1px solid @widgets#qlineedit#hover#border; background-color: @widgets#qlineedit#hover#bg; } @@ -1170,3 +1170,7 @@ vnotex--ViewSplit QTabBar[ViewSplitFlash="true"]::tab:selected { vte--VTextEdit { border: none; } + +vnotex--EntryPopup { + border: 1px solid @widgets#unitedentry#popup#border; +} diff --git a/src/data/extra/themes/solarized-dark/palette.json b/src/data/extra/themes/solarized-dark/palette.json index eaa778c0..9c1985c3 100644 --- a/src/data/extra/themes/solarized-dark/palette.json +++ b/src/data/extra/themes/solarized-dark/palette.json @@ -648,6 +648,17 @@ "fg" : "@base#master#fg", "bg" : "@base#master#bg" } + }, + "unitedentry" : { + "icon" : { + "fg" : "@base#icon#fg", + "busy" : { + "fg" : "@base#master#bg" + } + }, + "popup" : { + "border" : "@base#normal#border" + } } } } diff --git a/src/data/extra/themes/solarized-light/interface.qss b/src/data/extra/themes/solarized-light/interface.qss index acb80e65..4e2250e9 100644 --- a/src/data/extra/themes/solarized-light/interface.qss +++ b/src/data/extra/themes/solarized-light/interface.qss @@ -465,12 +465,12 @@ QLineEdit { } QLineEdit:focus { - border: 2px solid @widgets#qlineedit#focus#border; + border: 1px solid @widgets#qlineedit#focus#border; background-color: @widgets#qlineedit#focus#bg; } QLineEdit:hover { - border: 2px solid @widgets#qlineedit#hover#border; + border: 1px solid @widgets#qlineedit#hover#border; background-color: @widgets#qlineedit#hover#bg; } @@ -1170,3 +1170,7 @@ vnotex--ViewSplit QTabBar[ViewSplitFlash="true"]::tab:selected { vte--VTextEdit { border: none; } + +vnotex--EntryPopup { + border: 1px solid @widgets#unitedentry#popup#border; +} diff --git a/src/data/extra/themes/solarized-light/palette.json b/src/data/extra/themes/solarized-light/palette.json index f1eba8ab..11149ba8 100644 --- a/src/data/extra/themes/solarized-light/palette.json +++ b/src/data/extra/themes/solarized-light/palette.json @@ -648,6 +648,17 @@ "fg" : "@base#master#fg", "bg" : "@base#master#bg" } + }, + "unitedentry" : { + "icon" : { + "fg" : "@base#icon#fg", + "busy" : { + "fg" : "@base#master#bg" + } + }, + "popup" : { + "border" : "@base#normal#border" + } } } } diff --git a/src/data/extra/themes/vscode-dark/interface.qss b/src/data/extra/themes/vscode-dark/interface.qss index acb80e65..4e2250e9 100644 --- a/src/data/extra/themes/vscode-dark/interface.qss +++ b/src/data/extra/themes/vscode-dark/interface.qss @@ -465,12 +465,12 @@ QLineEdit { } QLineEdit:focus { - border: 2px solid @widgets#qlineedit#focus#border; + border: 1px solid @widgets#qlineedit#focus#border; background-color: @widgets#qlineedit#focus#bg; } QLineEdit:hover { - border: 2px solid @widgets#qlineedit#hover#border; + border: 1px solid @widgets#qlineedit#hover#border; background-color: @widgets#qlineedit#hover#bg; } @@ -1170,3 +1170,7 @@ vnotex--ViewSplit QTabBar[ViewSplitFlash="true"]::tab:selected { vte--VTextEdit { border: none; } + +vnotex--EntryPopup { + border: 1px solid @widgets#unitedentry#popup#border; +} diff --git a/src/data/extra/themes/vscode-dark/palette.json b/src/data/extra/themes/vscode-dark/palette.json index 92bd1961..ac566251 100644 --- a/src/data/extra/themes/vscode-dark/palette.json +++ b/src/data/extra/themes/vscode-dark/palette.json @@ -647,6 +647,17 @@ "fg" : "@base#master#fg", "bg" : "@base#master#bg" } + }, + "unitedentry" : { + "icon" : { + "fg" : "@base#icon#fg", + "busy" : { + "fg" : "@base#master#bg" + } + }, + "popup" : { + "border" : "@base#normal#border" + } } } } diff --git a/src/search/isearchinfoprovider.h b/src/search/isearchinfoprovider.h new file mode 100644 index 00000000..353bf930 --- /dev/null +++ b/src/search/isearchinfoprovider.h @@ -0,0 +1,30 @@ +#ifndef ISEARCHINFOPROVIDER_H +#define ISEARCHINFOPROVIDER_H + +#include +#include + +namespace vnotex +{ + class Node; + class Notebook; + class Buffer; + + class ISearchInfoProvider + { + public: + ISearchInfoProvider() = default; + + virtual ~ISearchInfoProvider() = default; + + virtual QList getBuffers() const = 0; + + virtual Node *getCurrentFolder() const = 0; + + virtual Notebook *getCurrentNotebook() const = 0; + + virtual QVector getNotebooks() const = 0; + }; +} + +#endif // ISEARCHINFOPROVIDER_H diff --git a/src/search/search.pri b/src/search/search.pri index d6a7005c..bcc61c89 100644 --- a/src/search/search.pri +++ b/src/search/search.pri @@ -3,8 +3,10 @@ QT += widgets HEADERS += \ $$PWD/filesearchengine.h \ $$PWD/isearchengine.h \ + $$PWD/isearchinfoprovider.h \ $$PWD/searchdata.h \ $$PWD/searcher.h \ + $$PWD/searchhelper.h \ $$PWD/searchresultitem.h \ $$PWD/searchtoken.h @@ -12,6 +14,7 @@ SOURCES += \ $$PWD/filesearchengine.cpp \ $$PWD/searchdata.cpp \ $$PWD/searcher.cpp \ + $$PWD/searchhelper.cpp \ $$PWD/searchresultitem.cpp \ $$PWD/searchtoken.cpp diff --git a/src/search/searchdata.cpp b/src/search/searchdata.cpp index 51830650..20bfbc1a 100644 --- a/src/search/searchdata.cpp +++ b/src/search/searchdata.cpp @@ -45,3 +45,8 @@ bool SearchOption::operator==(const SearchOption &p_other) const && m_engine == p_other.m_engine && m_findOptions == p_other.m_findOptions; } + +bool SearchOption::strictEquals(const SearchOption &p_other) const +{ + return (*this == p_other) && m_keyword == p_other.m_keyword; +} diff --git a/src/search/searchdata.h b/src/search/searchdata.h index debe3037..a1638be0 100644 --- a/src/search/searchdata.h +++ b/src/search/searchdata.h @@ -87,6 +87,8 @@ namespace vnotex bool operator==(const SearchOption &p_other) const; + bool strictEquals(const SearchOption &p_other) const; + QString m_keyword; QString m_filePattern; diff --git a/src/search/searchhelper.cpp b/src/search/searchhelper.cpp new file mode 100644 index 00000000..d1a114d3 --- /dev/null +++ b/src/search/searchhelper.cpp @@ -0,0 +1,99 @@ +#include "searchhelper.h" + +#include + +using namespace vnotex; + +bool SearchHelper::isSearchOptionValid(const SearchOption &p_option, QString &p_msg) +{ + if (p_option.m_keyword.isEmpty()) { + p_msg = Searcher::tr("Invalid keyword"); + return false; + } + + if (p_option.m_objects == SearchObject::ObjectNone) { + p_msg = Searcher::tr("No object specified"); + return false; + } + + if (p_option.m_targets == SearchTarget::TargetNone) { + p_msg = Searcher::tr("No target specified"); + return false; + } + + if (p_option.m_findOptions & FindOption::FuzzySearch + && p_option.m_objects & SearchObject::SearchContent) { + p_msg = Searcher::tr("Fuzzy search is not allowed when searching content"); + return false; + } + + p_msg.clear(); + return true; +} + +SearchState SearchHelper::searchOnProvider(Searcher *p_searcher, + const QSharedPointer &p_option, + const QSharedPointer &p_provider, + QString &p_msg) +{ + p_msg.clear(); + + if (!isSearchOptionValid(*p_option, p_msg)) { + return SearchState::Failed; + } + + SearchState state = SearchState::Finished; + + switch (p_option->m_scope) { + case SearchScope::Buffers: + { + auto buffers = p_provider->getBuffers(); + if (buffers.isEmpty()) { + break; + } + state = p_searcher->search(p_option, buffers); + break; + } + + case SearchScope::CurrentFolder: + { + auto notebook = p_provider->getCurrentNotebook(); + if (!notebook) { + break; + } + auto folder = p_provider->getCurrentFolder(); + if (!folder) { + break; + } + + state = p_searcher->search(p_option, folder); + break; + } + + case SearchScope::CurrentNotebook: + { + auto notebook = p_provider->getCurrentNotebook(); + if (!notebook) { + break; + } + + QVector notebooks; + notebooks.push_back(notebook); + state = p_searcher->search(p_option, notebooks); + break; + } + + case SearchScope::AllNotebooks: + { + auto notebooks = p_provider->getNotebooks(); + if (notebooks.isEmpty()) { + break; + } + + state = p_searcher->search(p_option, notebooks); + break; + } + } + + return state; +} diff --git a/src/search/searchhelper.h b/src/search/searchhelper.h new file mode 100644 index 00000000..ad728bf3 --- /dev/null +++ b/src/search/searchhelper.h @@ -0,0 +1,28 @@ +#ifndef SEARCHHELPER_H +#define SEARCHHELPER_H + +#include + +#include "searchdata.h" +#include "searcher.h" + +namespace vnotex +{ + class ISearchInfoProvider; + + class SearchHelper + { + public: + SearchHelper() = delete; + + static SearchState searchOnProvider(Searcher *p_searcher, + const QSharedPointer &p_option, + const QSharedPointer &p_provider, + QString &p_msg); + + private: + static bool isSearchOptionValid(const SearchOption &p_option, QString &p_msg); + }; +} + +#endif // SEARCHHELPER_H diff --git a/src/search/searchtoken.cpp b/src/search/searchtoken.cpp index eaed8b67..47879f7f 100644 --- a/src/search/searchtoken.cpp +++ b/src/search/searchtoken.cpp @@ -8,8 +8,6 @@ using namespace vnotex; -QScopedPointer SearchToken::s_parser; - void SearchToken::clear() { m_type = Type::PlainText; @@ -157,31 +155,40 @@ bool SearchToken::isEmpty() const return constraintSize() == 0; } -void SearchToken::createCommandLineParser() +QCommandLineParser *SearchToken::getCommandLineParser() { - if (s_parser) { - return; + static QScopedPointer parser; + + if (parser) { + return parser.data(); } - s_parser.reset(new QCommandLineParser()); - s_parser->setApplicationDescription(SearchPanel::tr("Full-text search.")); + parser.reset(new QCommandLineParser()); + parser->setApplicationDescription(SearchPanel::tr("Full-text search.")); + parser->addPositionalArgument("keywords", SearchPanel::tr("Keywords to search for.")); + + addSearchOptionsToCommand(parser.data()); + + return parser.data(); +} + +void SearchToken::addSearchOptionsToCommand(QCommandLineParser *p_parser) +{ QCommandLineOption caseSensitiveOpt(QStringList() << "c" << "case-sensitive", SearchPanel::tr("Search in case sensitive.")); - s_parser->addOption(caseSensitiveOpt); + p_parser->addOption(caseSensitiveOpt); QCommandLineOption regularExpressionOpt(QStringList() << "r" << "regular-expression", SearchPanel::tr("Search by regular expression.")); - s_parser->addOption(regularExpressionOpt); + p_parser->addOption(regularExpressionOpt); QCommandLineOption wholeWordOnlyOpt(QStringList() << "w" << "whole-word-only", SearchPanel::tr("Search whole word only.")); - s_parser->addOption(wholeWordOnlyOpt); + p_parser->addOption(wholeWordOnlyOpt); QCommandLineOption fuzzySearchOpt(QStringList() << "f" << "fuzzy-search", SearchPanel::tr("Do a fuzzy search (not applicable to content search).")); - s_parser->addOption(fuzzySearchOpt); + p_parser->addOption(fuzzySearchOpt); QCommandLineOption orOpt(QStringList() << "o" << "or", SearchPanel::tr("Do an OR combination of keywords.")); - s_parser->addOption(orOpt); - - s_parser->addPositionalArgument("keywords", SearchPanel::tr("Keywords to search.")); + p_parser->addOption(orOpt); } bool SearchToken::compile(const QString &p_keyword, FindOptions p_options, SearchToken &p_token) @@ -193,7 +200,7 @@ bool SearchToken::compile(const QString &p_keyword, FindOptions p_options, Searc return false; } - createCommandLineParser(); + auto parser = getCommandLineParser(); auto caseSensitivity = p_options & FindOption::CaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive; bool isRegularExpression = p_options & FindOption::RegularExpression; @@ -203,25 +210,25 @@ bool SearchToken::compile(const QString &p_keyword, FindOptions p_options, Searc auto args = ProcessUtils::parseCombinedArgString(p_keyword); // The parser needs the first arg to be the application name. args.prepend("vnotex"); - if (!s_parser->parse(args)) + if (!parser->parse(args)) { return false; } - if (s_parser->isSet("c")) { + if (parser->isSet("c")) { caseSensitivity = Qt::CaseSensitive; } - if (s_parser->isSet("r")) { + if (parser->isSet("r")) { isRegularExpression = true; } - if (s_parser->isSet("w")) { + if (parser->isSet("w")) { isWholeWordOnly = true; } - if (s_parser->isSet("f")) { + if (parser->isSet("f")) { isFuzzySearch = true; } - args = s_parser->positionalArguments(); + args = parser->positionalArguments(); if (args.isEmpty()) { return false; } @@ -232,7 +239,7 @@ bool SearchToken::compile(const QString &p_keyword, FindOptions p_options, Searc } else { p_token.m_type = Type::PlainText; } - p_token.m_operator = s_parser->isSet("o") ? Operator::Or : Operator::And; + p_token.m_operator = parser->isSet("o") ? Operator::Or : Operator::And; auto patternOptions = caseSensitivity == Qt::CaseInsensitive ? QRegularExpression::CaseInsensitiveOption : QRegularExpression::NoPatternOption; @@ -266,8 +273,8 @@ bool SearchToken::compile(const QString &p_keyword, FindOptions p_options, Searc QString SearchToken::getHelpText() { - createCommandLineParser(); - auto text = s_parser->helpText(); + auto parser = getCommandLineParser(); + auto text = parser->helpText(); // Skip the first line containing the application name. return text.mid(text.indexOf('\n') + 1); } diff --git a/src/search/searchtoken.h b/src/search/searchtoken.h index 8d21e30c..2d46e189 100644 --- a/src/search/searchtoken.h +++ b/src/search/searchtoken.h @@ -62,8 +62,10 @@ namespace vnotex static QString getHelpText(); + static void addSearchOptionsToCommand(QCommandLineParser *p_parser); + private: - static void createCommandLineParser(); + static QCommandLineParser *getCommandLineParser(); Type m_type = Type::PlainText; @@ -79,8 +81,6 @@ namespace vnotex QBitArray m_matchedConstraintsInBatchMode; int m_matchedConstraintsCountInBatchMode = 0; - - static QScopedPointer s_parser; }; } diff --git a/src/src.pro b/src/src.pro index d2557a92..890da376 100644 --- a/src/src.pro +++ b/src/src.pro @@ -62,6 +62,8 @@ include($$PWD/core/core.pri) include($$PWD/widgets/widgets.pri) +include($$PWD/unitedentry/unitedentry.pri) + RESOURCES += \ $$PWD/data/core/core.qrc diff --git a/src/unitedentry/entrypopup.cpp b/src/unitedentry/entrypopup.cpp new file mode 100644 index 00000000..d0d5ae6c --- /dev/null +++ b/src/unitedentry/entrypopup.cpp @@ -0,0 +1,86 @@ +#include "entrypopup.h" + +#include +#include +#include + +using namespace vnotex; + +EntryPopup::EntryPopup(QWidget *p_parent) + : QFrame(p_parent) +{ + Q_ASSERT(p_parent); + auto layout = new QVBoxLayout(this); + Q_UNUSED(layout); + + setWindowFlags(Qt::ToolTip); + setFocusPolicy(Qt::FocusPolicy::ClickFocus); +} + +EntryPopup::~EntryPopup() +{ + if (m_widget) { + takeWidget(m_widget.data()); + } +} + +void EntryPopup::setWidget(const QSharedPointer &p_widget) +{ + Q_ASSERT(p_widget); + + if (p_widget == m_widget) { + return; + } + + if (m_widget) { + takeWidget(m_widget.data()); + } + + layout()->addWidget(p_widget.data()); + m_widget = p_widget; + m_widget->show(); + + updateGeometryToContents(); +} + +void EntryPopup::takeWidget(QWidget *p_widget) +{ + layout()->removeWidget(p_widget); + p_widget->hide(); + p_widget->setParent(nullptr); +} + +void EntryPopup::showEvent(QShowEvent *p_event) +{ + QFrame::showEvent(p_event); + + updateGeometryToContents(); +} + +void EntryPopup::updateGeometryToContents() +{ + adjustSize(); + + auto pa = parentWidget(); + auto pos = pa->mapToGlobal(QPoint(0, pa->height())); + setGeometry(QRect(pos, preferredSize())); + + if (m_widget) { + m_widget->updateGeometry(); + } +} + +QSize EntryPopup::preferredSize() const +{ + const int minWidth = 400; + const int minHeight = 300; + + auto pa = parentWidget(); + int w = pa->width(); + int h = sizeHint().height(); + if (auto win = pa->window()) { + w = qMax(w, qMin(win->width() - 500, 900)); + h = qMax(h, qMin(win->height() - 500, 800)); + } + return QSize(qMax(minWidth, w), qMax(h, minHeight)); +} diff --git a/src/unitedentry/entrypopup.h b/src/unitedentry/entrypopup.h new file mode 100644 index 00000000..ea044618 --- /dev/null +++ b/src/unitedentry/entrypopup.h @@ -0,0 +1,34 @@ +#ifndef ENTRYPOPUP_H +#define ENTRYPOPUP_H + +#include +#include + +namespace vnotex +{ + class EntryPopup : public QFrame + { + Q_OBJECT + public: + explicit EntryPopup(QWidget *p_parent = nullptr); + + ~EntryPopup(); + + void setWidget(const QSharedPointer &p_widget); + + void updateGeometryToContents(); + + protected: + void showEvent(QShowEvent *p_event) Q_DECL_OVERRIDE; + + private: + QSize preferredSize() const; + + void takeWidget(QWidget *p_widget); + + private: + QSharedPointer m_widget = nullptr; + }; +} + +#endif // ENTRYPOPUP_H diff --git a/src/unitedentry/entrywidgetfactory.cpp b/src/unitedentry/entrywidgetfactory.cpp new file mode 100644 index 00000000..e96bdb6e --- /dev/null +++ b/src/unitedentry/entrywidgetfactory.cpp @@ -0,0 +1,29 @@ +#include "entrywidgetfactory.h" + +#include + +#include +#include + +using namespace vnotex; + +QSharedPointer EntryWidgetFactory::createTreeWidget(int p_columnCount) +{ + auto tree = QSharedPointer::create(TreeWidget::Flag::EnhancedStyle, nullptr); + tree->setColumnCount(p_columnCount); + tree->setHeaderHidden(true); + TreeWidget::showHorizontalScrollbar(tree.data()); + return tree; +} + +QSharedPointer EntryWidgetFactory::createLabel(const QString &p_info) +{ + auto label = QSharedPointer::create(p_info, nullptr); + label->setAlignment(Qt::AlignTop | Qt::AlignLeft); + + auto fnt = label->font(); + fnt.setPointSize(fnt.pointSize() + 2); + label->setFont(fnt); + + return label; +} diff --git a/src/unitedentry/entrywidgetfactory.h b/src/unitedentry/entrywidgetfactory.h new file mode 100644 index 00000000..e402709e --- /dev/null +++ b/src/unitedentry/entrywidgetfactory.h @@ -0,0 +1,23 @@ +#ifndef ENTRYWIDGETFACTORY_H +#define ENTRYWIDGETFACTORY_H + +#include + +class QTreeWidget; +class QLabel; +class QString; + +namespace vnotex +{ + class EntryWidgetFactory + { + public: + EntryWidgetFactory() = delete; + + static QSharedPointer createTreeWidget(int p_columnCount); + + static QSharedPointer createLabel(const QString &p_info); + }; +} + +#endif // ENTRYWIDGETFACTORY_H diff --git a/src/unitedentry/findunitedentry.cpp b/src/unitedentry/findunitedentry.cpp new file mode 100644 index 00000000..639801cf --- /dev/null +++ b/src/unitedentry/findunitedentry.cpp @@ -0,0 +1,346 @@ +#include "findunitedentry.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "unitedentryhelper.h" +#include "entrywidgetfactory.h" +#include "unitedentrymgr.h" + +using namespace vnotex; + +FindUnitedEntry::FindUnitedEntry(const QSharedPointer &p_provider, + const UnitedEntryMgr *p_mgr, + QObject *p_parent) + : IUnitedEntry("find", + tr("Search for files in notebooks"), + p_mgr, + p_parent), + m_provider(p_provider) +{ + m_processTimer = new QTimer(this); + m_processTimer->setSingleShot(true); + m_processTimer->setInterval(500); + connect(m_processTimer, &QTimer::timeout, + this, &FindUnitedEntry::doProcessInternal); +} + +void FindUnitedEntry::initOnFirstProcess() +{ + m_parser.setApplicationDescription(tr("Search for files in notebooks with advanced options for scope, object, target and so on.")); + + m_parser.addPositionalArgument("keywords", tr("Keywords to search for.")); + + QCommandLineOption scopeOpt({"s", "scope"}, + tr("Search scope. Possible values: buffer/folder/notebook/all_notebook."), + tr("search_scope"), + "notebook"); + m_parser.addOption(scopeOpt); + + QCommandLineOption objectOpt({"b", "object"}, + tr("Search objects. Possible values: name/content/tag/path."), + tr("search_objects")); + objectOpt.setDefaultValues({"name", "content"}); + m_parser.addOption(objectOpt); + + QCommandLineOption targetOpt({"t", "target"}, + tr("Search targets. Possible values: file/folder/notebook."), + tr("search_targets")); + targetOpt.setDefaultValues({"file", "folder"}); + m_parser.addOption(targetOpt); + + QCommandLineOption patternOpt({"p", "pattern"}, + tr("Wildcard pattern of files to search."), + tr("file_pattern")); + m_parser.addOption(patternOpt); + + SearchToken::addSearchOptionsToCommand(&m_parser); +} + +void FindUnitedEntry::processInternal(const QString &p_args, + const std::function &)> &p_popupWidgetFunc) +{ + // Do another timer delay here since it is a very expensive operation. + m_processTimer->stop(); + + Q_ASSERT(!isOngoing()); + + setOngoing(true); + + auto args = ProcessUtils::parseCombinedArgString(p_args); + // The parser needs the first arg to be the application name. + args.prepend(name()); + bool ret = m_parser.parse(args); + const auto positionalArgs = m_parser.positionalArguments(); + if (!ret) { + auto label = EntryWidgetFactory::createLabel(m_parser.errorText()); + p_popupWidgetFunc(label); + finish(); + return; + } + if (positionalArgs.isEmpty()) { + auto label = EntryWidgetFactory::createLabel(getHelpText()); + p_popupWidgetFunc(label); + finish(); + return; + } + + auto opt = collectOptions(); + if (m_searchOption && m_searchOption->strictEquals(*opt)) { + // Reuse last result. + p_popupWidgetFunc(m_resultTree); + finish(); + return; + } + + m_searchOption = opt; + + m_searchTokenOfSession.clear(); + + prepareResultTree(); + + p_popupWidgetFunc(m_resultTree); + + m_processTimer->start(); +} + +QSharedPointer FindUnitedEntry::collectOptions() const +{ + auto opt = QSharedPointer::create(); + + opt->m_engine = SearchEngine::Internal; + + opt->m_keyword = m_parser.positionalArguments().join(QLatin1Char(' ')); + Q_ASSERT(!opt->m_keyword.isEmpty()); + + opt->m_filePattern = m_parser.value("p"); + + { + SearchScope scope = SearchScope::CurrentNotebook; + const auto scopeStr = m_parser.value("s"); + if (scopeStr == QStringLiteral("buffer")) { + scope = SearchScope::Buffers; + } else if (scopeStr == QStringLiteral("folder")) { + scope = SearchScope::CurrentFolder; + } else if (scopeStr == QStringLiteral("notebook")) { + scope = SearchScope::CurrentNotebook; + } else if (scopeStr == QStringLiteral("all_notebook")) { + scope = SearchScope::AllNotebooks; + } + opt->m_scope = scope; + } + + { + SearchObjects objects = SearchObject::ObjectNone; + const auto objectStrs = m_parser.values("b"); + for (const auto &str : objectStrs) { + if (str == QStringLiteral("name")) { + objects |= SearchObject::SearchName; + } else if (str == QStringLiteral("content")) { + objects |= SearchObject::SearchContent; + } else if (str == QStringLiteral("tag")) { + objects |= SearchObject::SearchTag; + } else if (str == QStringLiteral("path")) { + objects |= SearchObject::SearchPath; + } + } + opt->m_objects = objects; + } + + { + SearchTargets targets = SearchTarget::TargetNone; + auto targetStrs = m_parser.values("t"); + for (const auto &str : targetStrs) { + if (str == QStringLiteral("file")) { + targets |= SearchTarget::SearchFile; + } else if (str == QStringLiteral("folder")) { + targets |= SearchTarget::SearchFolder; + } else if (str == QStringLiteral("notebook")) { + targets |= SearchTarget::SearchNotebook; + } + } + opt->m_targets = targets; + } + + { + FindOptions options = FindOption::FindNone; + if (m_parser.isSet("c")) { + options |= FindOption::CaseSensitive; + } + if (m_parser.isSet("r")) { + options |= FindOption::RegularExpression; + } + if (m_parser.isSet("w")) { + options |= FindOption::WholeWordOnly; + } + if (m_parser.isSet("f")) { + options |= FindOption::FuzzySearch; + } + opt->m_findOptions = options; + } + + return opt; +} + +QString FindUnitedEntry::getHelpText() const +{ + auto text = m_parser.helpText(); + // Skip the first line containing the application name. + return text.mid(text.indexOf('\n') + 1); +} + +Searcher *FindUnitedEntry::getSearcher() +{ + if (!m_searcher) { + m_searcher = new Searcher(this); + connect(m_searcher, &Searcher::resultItemAdded, + this, [this](const QSharedPointer &p_item) { + addLocation(p_item->m_location); + }); + connect(m_searcher, &Searcher::resultItemsAdded, + this, [this](const QVector> &p_items) { + for (const auto &item : p_items) { + addLocation(item->m_location); + } + }); + connect(m_searcher, &Searcher::finished, + this, &FindUnitedEntry::handleSearchFinished); + } + return m_searcher; +} + +void FindUnitedEntry::handleSearchFinished(SearchState p_state) +{ + if (p_state != SearchState::Busy) { + getSearcher()->clear(); + finish(); + } +} + +void FindUnitedEntry::prepareResultTree() +{ + if (!m_resultTree) { + m_resultTree = EntryWidgetFactory::createTreeWidget(1); + connect(m_resultTree.data(), &QTreeWidget::itemActivated, + this, &FindUnitedEntry::handleItemActivated); + } + + m_resultTree->clear(); +} + +void FindUnitedEntry::addLocation(const ComplexLocation &p_location) +{ + auto item = new QTreeWidgetItem(m_resultTree.data()); + item->setText(0, p_location.m_displayPath); + item->setIcon(0, UnitedEntryHelper::itemIcon(UnitedEntryHelper::locationTypeToItemType(p_location.m_type))); + item->setData(0, Qt::UserRole, p_location.m_path); + item->setToolTip(0, p_location.m_path); + + // Add sub items. + for (const auto &line : p_location.m_lines) { + auto subItem = new QTreeWidgetItem(item); + + // Truncate the text. + if (line.m_text.size() > 500) { + subItem->setText(0, line.m_text.left(500)); + } else { + subItem->setText(0, line.m_text); + } + + if (!line.m_segments.isEmpty()) { + subItem->setData(0, HighlightsRole, QVariant::fromValue(line.m_segments)); + } + + subItem->setData(0, Qt::UserRole, line.m_lineNumber); + } + + if (m_mgr->getExpandAllEnabled()) { + item->setExpanded(true); + } + + if (m_resultTree->topLevelItemCount() == 1) { + m_resultTree->setCurrentItem(item); + } +} + +void FindUnitedEntry::doProcessInternal() +{ + if (isAskedToStop()) { + finish(); + return; + } + + QString msg; + auto state = SearchHelper::searchOnProvider(getSearcher(), m_searchOption, m_provider, msg); + if (!msg.isEmpty()) { + qWarning() << msg; + } + + handleSearchFinished(state); +} + +void FindUnitedEntry::stop() +{ + IUnitedEntry::stop(); + + if (m_processTimer->isActive()) { + m_processTimer->stop(); + // Let it go finished. + doProcessInternal(); + } +} + +void FindUnitedEntry::finish() +{ + setOngoing(false); + emit finished(); +} + +QSharedPointer FindUnitedEntry::currentPopupWidget() const +{ + return m_resultTree; +} + +void FindUnitedEntry::handleItemActivated(QTreeWidgetItem *p_item, int p_column) +{ + Q_UNUSED(p_column); + + if (!m_searchTokenOfSession) { + if (m_searchOption->m_objects & SearchObject::SearchContent) { + m_searchTokenOfSession = QSharedPointer::create(getSearcher()->getToken()); + } + } + + // TODO: decode the path of location and handle different types of destination. + auto paras = QSharedPointer::create(); + + QString itemPath; + auto pa = p_item->parent(); + if (pa) { + itemPath = pa->data(0, Qt::UserRole).toString(); + paras->m_lineNumber = p_item->data(0, Qt::UserRole).toInt(); + } else { + itemPath = p_item->data(0, Qt::UserRole).toString(); + // Use the first line number if there is any. + if (p_item->childCount() > 0) { + auto childItem = p_item->child(0); + paras->m_lineNumber = childItem->data(0, Qt::UserRole).toInt(); + } + } + + paras->m_searchToken = m_searchTokenOfSession; + emit VNoteX::getInst().openFileRequested(itemPath, paras); +} diff --git a/src/unitedentry/findunitedentry.h b/src/unitedentry/findunitedentry.h new file mode 100644 index 00000000..076bf78a --- /dev/null +++ b/src/unitedentry/findunitedentry.h @@ -0,0 +1,75 @@ +#ifndef FINDUNITEDENTRY_H +#define FINDUNITEDENTRY_H + +#include "iunitedentry.h" + +#include +#include + +#include + +class QTreeWidget; +class QTreeWidgetItem; +class QTimer; + +namespace vnotex +{ + class Searcher; + class ISearchInfoProvider; + struct ComplexLocation; + class SearchToken; + + class FindUnitedEntry : public IUnitedEntry + { + Q_OBJECT + public: + FindUnitedEntry(const QSharedPointer &p_provider, + const UnitedEntryMgr *p_mgr, + QObject *p_parent = nullptr); + + void stop() Q_DECL_OVERRIDE; + + QSharedPointer currentPopupWidget() const Q_DECL_OVERRIDE; + + protected: + void initOnFirstProcess() Q_DECL_OVERRIDE; + + void processInternal(const QString &p_args, + const std::function &)> &p_popupWidgetFunc) Q_DECL_OVERRIDE; + + private: + QString getHelpText() const; + + QSharedPointer collectOptions() const; + + Searcher *getSearcher(); + + void handleSearchFinished(SearchState p_state); + + void prepareResultTree(); + + void addLocation(const ComplexLocation &p_location); + + void doProcessInternal(); + + void finish(); + + void handleItemActivated(QTreeWidgetItem *p_item, int p_column); + + QSharedPointer m_provider; + + QCommandLineParser m_parser; + + Searcher *m_searcher = nullptr; + + QSharedPointer m_resultTree; + + QTimer *m_processTimer = nullptr; + + QSharedPointer m_searchOption; + + QSharedPointer m_searchTokenOfSession; + }; +} + +#endif // FINDUNITEDENTRY_H diff --git a/src/unitedentry/helpunitedentry.cpp b/src/unitedentry/helpunitedentry.cpp new file mode 100644 index 00000000..981e8f16 --- /dev/null +++ b/src/unitedentry/helpunitedentry.cpp @@ -0,0 +1,48 @@ +#include "helpunitedentry.h" + +#include + +#include "entrywidgetfactory.h" + +using namespace vnotex; + +HelpUnitedEntry::HelpUnitedEntry(const UnitedEntryMgr *p_mgr, QObject *p_parent) + : IUnitedEntry("help", + tr("Help information about United Entry"), + p_mgr, + p_parent) +{ +} + +QSharedPointer HelpUnitedEntry::currentPopupWidget() const +{ + return m_infoTree; +} + +void HelpUnitedEntry::initOnFirstProcess() +{ + m_infoTree = EntryWidgetFactory::createTreeWidget(2); + m_infoTree->setHeaderHidden(false); + m_infoTree->setHeaderLabels(QStringList() << tr("Shortcut") << tr("Description")); + + QVector shortcuts = {{"Esc/Ctrl+[", tr("Close United Entry")}, + {"Up/Ctrl+K", tr("Go to previous item")}, + {"Down/Ctrl+J", tr("Go to next item")}, + {"Ctrl+L", tr("Go to the item one level up")}, + {"Ctrl+I", tr("Expand/Collapse current item")}, + {"Ctrl+B", tr("Expand/Collapse all the items")}, + {"Enter", tr("Activate current item")}, + {"Ctrl+E", tr("Clear the input except the entry name")}, + {"Ctrl+F", tr("Select the entry name")}, + {"Ctrl+D", tr("Stop current entry")}}; + for (const auto &shortcut : shortcuts) { + m_infoTree->addTopLevelItem(new QTreeWidgetItem(shortcut)); + } +} + +void HelpUnitedEntry::processInternal(const QString &p_args, + const std::function &)> &p_popupWidgetFunc) +{ + Q_UNUSED(p_args); + p_popupWidgetFunc(m_infoTree); +} diff --git a/src/unitedentry/helpunitedentry.h b/src/unitedentry/helpunitedentry.h new file mode 100644 index 00000000..719d2972 --- /dev/null +++ b/src/unitedentry/helpunitedentry.h @@ -0,0 +1,31 @@ +#ifndef HELPUNITEDENTRY_H +#define HELPUNITEDENTRY_H + +#include "iunitedentry.h" + +#include + +class QTreeWidget; + +namespace vnotex +{ + class HelpUnitedEntry : public IUnitedEntry + { + Q_OBJECT + public: + HelpUnitedEntry(const UnitedEntryMgr *p_mgr, QObject *p_parent = nullptr); + + QSharedPointer currentPopupWidget() const Q_DECL_OVERRIDE; + + protected: + void initOnFirstProcess() Q_DECL_OVERRIDE; + + void processInternal(const QString &p_args, + const std::function &)> &p_popupWidgetFunc) Q_DECL_OVERRIDE; + + private: + QSharedPointer m_infoTree; + }; +} + +#endif // HELPUNITEDENTRY_H diff --git a/src/unitedentry/iunitedentry.cpp b/src/unitedentry/iunitedentry.cpp new file mode 100644 index 00000000..30fc0cc0 --- /dev/null +++ b/src/unitedentry/iunitedentry.cpp @@ -0,0 +1,138 @@ +#include "iunitedentry.h" + +#include +#include +#include + +#include + +using namespace vnotex; + +IUnitedEntry::IUnitedEntry(const QString &p_name, + const QString &p_description, + const UnitedEntryMgr *p_mgr, + QObject *p_parent) + : QObject(p_parent), + m_name(p_name), + m_description(p_description), + m_mgr(p_mgr) +{ +} + +const QString &IUnitedEntry::name() const +{ + return m_name; +} + +QString IUnitedEntry::description() const +{ + return m_description; +} + +void IUnitedEntry::process(const QString &p_args, + const std::function &)> &p_popupWidgetFunc) +{ + if (!m_initialized) { + m_initialized = true; + + initOnFirstProcess(); + } + + m_askedToStop.store(0); + + return processInternal(p_args, p_popupWidgetFunc); +} + +void IUnitedEntry::stop() +{ + m_askedToStop.store(1); +} + +bool IUnitedEntry::isAskedToStop() const +{ + return m_askedToStop.load() == 1; +} + +void IUnitedEntry::setOngoing(bool p_ongoing) +{ + m_ongoing = p_ongoing; +} + +bool IUnitedEntry::isOngoing() const +{ + return m_ongoing; +} + +void IUnitedEntry::handleAction(Action p_act) +{ + auto widget = currentPopupWidget(); + if (!widget) { + return; + } + handleActionCommon(p_act, widget.data()); +} + +void IUnitedEntry::handleActionCommon(Action p_act, QWidget *p_widget) +{ + switch (p_act) + { + case Action::NextItem: + { + auto eve = new QKeyEvent(QEvent::KeyPress, Qt::Key_Down, Qt::NoModifier); + QCoreApplication::postEvent(p_widget, eve); + break; + } + + case Action::PreviousItem: + { + auto eve = new QKeyEvent(QEvent::KeyPress, Qt::Key_Up, Qt::NoModifier); + QCoreApplication::postEvent(p_widget, eve); + break; + } + + case Action::Activate: + { + auto eve = new QKeyEvent(QEvent::KeyPress, Qt::Key_Enter, Qt::NoModifier); + QCoreApplication::postEvent(p_widget, eve); + break; + } + + case Action::LevelUp: + { + auto treeWidget = dynamic_cast(p_widget); + if (treeWidget) { + TreeWidget::selectParentItem(treeWidget); + } + break; + } + + case Action::ExpandCollapse: + { + auto treeWidget = dynamic_cast(p_widget); + if (treeWidget) { + auto item = treeWidget->currentItem(); + if (item) { + item->setExpanded(!item->isExpanded()); + } + } + break; + } + + case Action::ExpandCollapseAll: + { + auto treeWidget = dynamic_cast(p_widget); + if (treeWidget) { + if (TreeWidget::isExpanded(treeWidget)) { + treeWidget->collapseAll(); + } else { + treeWidget->expandAll(); + } + } + break; + } + + default: + Q_ASSERT(false); + break; + } +} diff --git a/src/unitedentry/iunitedentry.h b/src/unitedentry/iunitedentry.h new file mode 100644 index 00000000..02d2287d --- /dev/null +++ b/src/unitedentry/iunitedentry.h @@ -0,0 +1,79 @@ +#ifndef IUNITEDENTRY_H +#define IUNITEDENTRY_H + +#include + +#include + +#include + +namespace vnotex +{ + class UnitedEntryMgr; + + // Interface of a UnitedEntry. + class IUnitedEntry : public QObject + { + Q_OBJECT + public: + enum class Action + { + NextItem, + PreviousItem, + Activate, + LevelUp, + ExpandCollapse, + ExpandCollapseAll + }; + + IUnitedEntry(const QString &p_name, + const QString &p_description, + const UnitedEntryMgr *p_mgr, + QObject *p_parent = nullptr); + + const QString &name() const; + + virtual QString description() const; + + void process(const QString &p_args, + const std::function &)> &p_popupWidgetFunc); + + virtual bool isOngoing() const; + + virtual void stop(); + + virtual void handleAction(Action p_act); + + virtual QSharedPointer currentPopupWidget() const = 0; + + static void handleActionCommon(Action p_act, QWidget *p_widget); + + signals: + void finished(); + + protected: + virtual void initOnFirstProcess() = 0; + + virtual void processInternal(const QString &p_args, + const std::function &)> &p_popupWidgetFunc) = 0; + + bool isAskedToStop() const; + + virtual void setOngoing(bool p_ongoing); + + const UnitedEntryMgr *m_mgr = nullptr; + + private: + bool m_initialized = false; + + QString m_name; + + QString m_description; + + QAtomicInt m_askedToStop = 0; + + bool m_ongoing = false; + }; +} + +#endif // IUNITEDENTRY_H diff --git a/src/unitedentry/unitedentry.cpp b/src/unitedentry/unitedentry.cpp new file mode 100644 index 00000000..fd5a9c76 --- /dev/null +++ b/src/unitedentry/unitedentry.cpp @@ -0,0 +1,487 @@ +#include "unitedentry.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "entrypopup.h" +#include "entrywidgetfactory.h" +#include "unitedentrymgr.h" +#include "iunitedentry.h" +#include "unitedentryhelper.h" + +using namespace vnotex; + +UnitedEntry::UnitedEntry(QWidget *p_parent) + : QWidget(p_parent) +{ + m_processTimer = new QTimer(this); + m_processTimer->setSingleShot(true); + m_processTimer->setInterval(300); + connect(m_processTimer, &QTimer::timeout, + this, &UnitedEntry::processInput); + + setupUI(); + + connect(qApp, &QApplication::focusChanged, + this, &UnitedEntry::handleFocusChanged); + + connect(&UnitedEntryMgr::getInst(), &UnitedEntryMgr::entryFinished, + this, &UnitedEntry::handleEntryFinished); +} + +UnitedEntry::~UnitedEntry() +{ +} + +void UnitedEntry::setupUI() +{ + auto mainLayout = new QHBoxLayout(this); + mainLayout->setContentsMargins(0, 0, 0, 0); + + setSizePolicy(QSizePolicy::Policy::Preferred, QSizePolicy::Policy::Fixed); + + // Shortcut. + const auto shortcut = ConfigMgr::getInst().getCoreConfig().getShortcut(CoreConfig::Shortcut::UnitedEntry); + const QKeySequence kseq(shortcut); + const auto shortcutText = kseq.isEmpty() ? QString() : kseq.toString(QKeySequence::NativeText); + if (!kseq.isEmpty()) { + auto sc = WidgetUtils::createShortcut(shortcut, this, Qt::ShortcutContext::ApplicationShortcut); + if (sc) { + connect(sc, &QShortcut::activated, + this, [this]() { + if (m_lineEdit->hasFocus()) { + return; + } + + bool popupVisible = m_popup->isVisible(); + if (popupVisible) { + // Make m_lineEdit->setFocus() work. + m_popup->hide(); + } + m_lineEdit->setFocus(); + if (popupVisible) { + m_popup->show(); + } + }); + } + } + setToolTip(shortcutText.isEmpty() ? tr("United Entry") : tr("United Entry (%1)").arg(shortcutText)); + + // Line edit. + m_lineEdit = WidgetsFactory::createLineEditWithSnippet(this); + mainLayout->addWidget(m_lineEdit); + m_lineEdit->setToolTip(QString()); + m_lineEdit->setPlaceholderText(shortcutText.isEmpty() ? tr("Type to command") : tr("Type to command (%1)").arg(shortcutText)); + m_lineEdit->setClearButtonEnabled(true); + m_lineEdit->setSizePolicy(QSizePolicy::Policy::Preferred, QSizePolicy::Policy::Fixed); + connect(m_lineEdit, &QLineEdit::textChanged, + m_processTimer, QOverload::of(&QTimer::start)); + setFocusProxy(m_lineEdit); + + // Popup. + m_popup = new EntryPopup(this); + m_popup->installEventFilter(this); + m_popup->hide(); + + setupIcons(); +} + +void UnitedEntry::setupIcons() +{ + const auto &themeMgr = VNoteX::getInst().getThemeMgr(); + const auto fg = themeMgr.paletteColor("widgets#unitedentry#icon#fg"); + // Use QIcon::Disabled as the busy state. + const auto busyFg = themeMgr.paletteColor("widgets#unitedentry#icon#busy#fg"); + QVector colors; + colors.push_back(IconUtils::OverriddenColor(fg, QIcon::Normal)); + colors.push_back(IconUtils::OverriddenColor(busyFg, QIcon::Disabled)); + + const auto icon = IconUtils::fetchIcon(themeMgr.getIconFile("united_entry.svg"), colors); + m_iconAction = m_lineEdit->addAction(icon, QLineEdit::ActionPosition::LeadingPosition); + m_iconAction->setText(tr("Options")); + + // Menu. + auto menu = WidgetsFactory::createMenu(this); + m_iconAction->setMenu(menu); + + { + auto expandAct = menu->addAction(tr("Expand All"), + this, + [this](bool checked) { + ConfigMgr::getInst().getWidgetConfig().setUnitedEntryExpandAllEnabled(checked); + UnitedEntryMgr::getInst().setExpandAllEnabled(checked); + }); + expandAct->setCheckable(true); + expandAct->setChecked(ConfigMgr::getInst().getWidgetConfig().getUnitedEntryExpandAllEnabled()); + UnitedEntryMgr::getInst().setExpandAllEnabled(expandAct->isChecked()); + } + + connect(m_iconAction, &QAction::triggered, + this, [this]() { + auto pos = mapToGlobal(QPoint(0, height())); + auto menu = m_iconAction->menu(); + menu->exec(pos); + }); +} + +void UnitedEntry::activateUnitedEntry() +{ + if (m_activated) { + return; + } + + if (!UnitedEntryMgr::getInst().isInitialized()) { + return; + } + + m_activated = true; + + setSizePolicy(QSizePolicy::Policy::MinimumExpanding, QSizePolicy::Policy::Fixed); + + m_lineEdit->selectAll(); + m_lineEdit->setFocus(); + + m_processTimer->start(); +} + +void UnitedEntry::deactivateUnitedEntry() +{ + if (!m_activated) { + return; + } + + m_activated = false; + m_previousFocusWidget = nullptr; + + setSizePolicy(QSizePolicy::Policy::Preferred, QSizePolicy::Policy::Fixed); + + m_popup->hide(); +} + +void UnitedEntry::handleFocusChanged(QWidget *p_old, QWidget *p_now) +{ + if (p_now == m_lineEdit) { + activateUnitedEntry(); + if (!m_previousFocusWidget && p_old != this && !WidgetUtils::isOrAncestorOf(this, p_old)) { + m_previousFocusWidget = p_old; + } + return; + } + + if (!m_activated) { + return; + } + + if (!p_now || (p_now != this && !WidgetUtils::isOrAncestorOf(this, p_now))) { + deactivateUnitedEntry(); + } +} + +void UnitedEntry::keyPressEvent(QKeyEvent *p_event) +{ + const int key = p_event->key(); + const int modifiers = p_event->modifiers(); + IUnitedEntry::Action act = IUnitedEntry::Action::NextItem; + switch (key) { + case Qt::Key_BracketLeft: + if (!WidgetUtils::isViControlModifier(modifiers)) { + break; + } + Q_FALLTHROUGH(); + case Qt::Key_Escape: + exitUnitedEntry(); + break; + + // Up/Down Ctrl+K/J to navigate to previous/next item. + case Qt::Key_Up: + act = IUnitedEntry::Action::PreviousItem; + Q_FALLTHROUGH(); + case Qt::Key_Down: + if (m_lastEntry) { + m_lastEntry->handleAction(act); + } else if (m_entryListWidget && m_entryListWidget->isVisible()) { + IUnitedEntry::handleActionCommon(act, m_entryListWidget.data()); + } + break; + + case Qt::Key_K: + act = IUnitedEntry::Action::PreviousItem; + Q_FALLTHROUGH(); + case Qt::Key_J: + if (WidgetUtils::isViControlModifier(modifiers)) { + if (m_lastEntry) { + m_lastEntry->handleAction(act); + } else if (m_entryListWidget && m_entryListWidget->isVisible()) { + IUnitedEntry::handleActionCommon(act, m_entryListWidget.data()); + } + } + break; + + case Qt::Key_Enter: + Q_FALLTHROUGH(); + case Qt::Key_Return: + if (m_lastEntry) { + m_lastEntry->handleAction(IUnitedEntry::Action::Activate); + } + break; + + case Qt::Key_E: + if (WidgetUtils::isViControlModifier(modifiers)) { + // Eliminate input till the entry name. + const auto text = m_lineEdit->evaluatedText(); + const auto entry = UnitedEntryHelper::parseUserEntry(text); + if (!entry.m_name.isEmpty()) { + m_lineEdit->setText(entry.m_name + QLatin1Char(' ')); + } + } + break; + + case Qt::Key_F: + if (WidgetUtils::isViControlModifier(modifiers)) { + // Select the entry name. + const auto text = m_lineEdit->evaluatedText(); + const auto entry = UnitedEntryHelper::parseUserEntry(text); + if (!entry.m_name.isEmpty()) { + m_lineEdit->setSelection(0, entry.m_name.size()); + } + } + break; + + case Qt::Key_D: + if (WidgetUtils::isViControlModifier(modifiers)) { + // Stop the entry. + if (m_lastEntry) { + m_lastEntry->stop(); + } + } + break; + + case Qt::Key_L: + if (WidgetUtils::isViControlModifier(modifiers)) { + // Go up one level. + if (m_lastEntry) { + m_lastEntry->handleAction(IUnitedEntry::Action::LevelUp); + } + } + break; + + case Qt::Key_I: + if (WidgetUtils::isViControlModifier(modifiers)) { + // Expand/Collapse the item. + if (m_lastEntry) { + m_lastEntry->handleAction(IUnitedEntry::Action::ExpandCollapse); + } + } + break; + + case Qt::Key_B: + if (WidgetUtils::isViControlModifier(modifiers)) { + // Expand/Collapse all the items. + if (m_lastEntry) { + m_lastEntry->handleAction(IUnitedEntry::Action::ExpandCollapseAll); + } + } + break; + + default: + break; + } + + QWidget::keyPressEvent(p_event); +} + +void UnitedEntry::clear() +{ + m_lineEdit->setFocus(); + m_lineEdit->clear(); +} + +void UnitedEntry::processInput() +{ + m_hasPending = false; + + if (!m_activated) { + return; + } + + if (m_iconAction->menu()->isVisible()) { + // Do not display the popup which will hide the menu. + return; + } + + if (m_lastEntry && m_lastEntry->isOngoing()) { + m_hasPending = true; + m_lastEntry->stop(); + return; + } + + const auto text = m_lineEdit->evaluatedText(); + const auto entry = UnitedEntryHelper::parseUserEntry(text); + if (entry.m_name.isEmpty()) { + filterEntryListWidgetEntries(entry.m_name); + popupWidget(getEntryListWidget()); + m_lastEntry.clear(); + return; + } + + // Filter the help widget if space after entry name is not entered yet. + if (entry.m_name == text.trimmed() && !text.back().isSpace()) { + if (filterEntryListWidgetEntries(entry.m_name)) { + popupWidget(getEntryListWidget()); + m_lastEntry.clear(); + return; + } + } else { + if (!m_lastEntry || m_lastEntry->name() == entry.m_name) { + m_lastEntry = UnitedEntryMgr::getInst().findEntry(entry.m_name); + } + + if (m_lastEntry) { + // Found. + setBusy(true); + m_lastEntry->process(entry.m_args, std::bind(&UnitedEntry::popupWidget, this, std::placeholders::_1)); + return; + } + } + + // No entry found. + popupWidget(getInfoWidget(tr("Unknown entry: %1").arg(entry.m_name))); + m_lastEntry.clear(); +} + +void UnitedEntry::popupWidget(const QSharedPointer &p_widget) +{ + m_popup->setWidget(p_widget); + m_popup->show(); +} + +const QSharedPointer &UnitedEntry::getEntryListWidget() +{ + if (!m_entryListWidget) { + m_entryListWidget = EntryWidgetFactory::createTreeWidget(2); + m_entryListWidget->setHeaderHidden(false); + m_entryListWidget->setHeaderLabels(QStringList() << tr("Entry") << tr("Description")); + + const auto entries = UnitedEntryMgr::getInst().getEntries(); + for (const auto &entry : entries) { + m_entryListWidget->addTopLevelItem(new QTreeWidgetItem({entry->name(), entry->description()})); + } + } + + return m_entryListWidget; +} + +QSharedPointer UnitedEntry::getInfoWidget(const QString &p_info) +{ + return EntryWidgetFactory::createLabel(p_info); +} + +void UnitedEntry::resizeEvent(QResizeEvent *p_event) +{ + QWidget::resizeEvent(p_event); + + updatePopupGeometry(); +} + +void UnitedEntry::updatePopupGeometry() +{ + m_popup->updateGeometryToContents(); +} + +bool UnitedEntry::filterEntryListWidgetEntries(const QString &p_name) +{ + const auto &entryListWidget = getEntryListWidget(); + + if (p_name.isEmpty()) { + for (int i = 0; i < entryListWidget->topLevelItemCount(); ++i) { + entryListWidget->topLevelItem(i)->setHidden(false); + } + return true; + } + + auto items = entryListWidget->findItems(p_name, Qt::MatchStartsWith); + for (int i = 0; i < entryListWidget->topLevelItemCount(); ++i) { + entryListWidget->topLevelItem(i)->setHidden(true); + } + for (const auto &item : items) { + item->setHidden(false); + } + return !items.isEmpty(); +} + +void UnitedEntry::handleEntryFinished(IUnitedEntry *p_entry) +{ + if (p_entry != m_lastEntry.data()) { + return; + } + + setBusy(false); + + if (m_hasPending) { + m_processTimer->start(); + } +} + +void UnitedEntry::setBusy(bool p_busy) +{ + m_iconAction->setEnabled(!p_busy); +} + +bool UnitedEntry::eventFilter(QObject *p_watched, QEvent *p_event) +{ + if (p_watched == m_popup) { + if (p_event->type() == QEvent::KeyPress) { + auto eve = static_cast(p_event); + switch (eve->key()) { + case Qt::Key_BracketLeft: + if (!WidgetUtils::isViControlModifier(eve->modifiers())) { + break; + } + Q_FALLTHROUGH(); + case Qt::Key_Escape: + exitUnitedEntry(); + // Need to call deactivateUnitedEntry() again since focusChanged is not triggered. + deactivateUnitedEntry(); + return true; + + default: + break; + } + } + } + + return QWidget::eventFilter(p_watched, p_event); +} + +void UnitedEntry::exitUnitedEntry() +{ + if (m_previousFocusWidget) { + // Deactivate and focus previous widget. + m_previousFocusWidget->setFocus(); + } else { + VNoteX::getInst().getMainWindow()->setFocus(); + } +} diff --git a/src/unitedentry/unitedentry.h b/src/unitedentry/unitedentry.h new file mode 100644 index 00000000..fa5c79af --- /dev/null +++ b/src/unitedentry/unitedentry.h @@ -0,0 +1,85 @@ +#ifndef UNITEDENTRY_H +#define UNITEDENTRY_H + +#include +#include + +class QAction; +class QTimer; +class QTreeWidget; +class QLabel; + +namespace vnotex +{ + class LineEditWithSnippet; + class EntryPopup; + class IUnitedEntry; + + class UnitedEntry : public QWidget + { + Q_OBJECT + public: + explicit UnitedEntry(QWidget *p_parent = nullptr); + + ~UnitedEntry(); + + bool eventFilter(QObject *p_watched, QEvent *p_event) Q_DECL_OVERRIDE; + + protected: + void keyPressEvent(QKeyEvent *p_event) Q_DECL_OVERRIDE; + + void resizeEvent(QResizeEvent *p_event) Q_DECL_OVERRIDE; + + private: + void setupUI(); + + void setupIcons(); + + void activateUnitedEntry(); + + void deactivateUnitedEntry(); + + void handleFocusChanged(QWidget *p_old, QWidget *p_now); + + void clear(); + + void processInput(); + + const QSharedPointer &getEntryListWidget(); + + QSharedPointer getInfoWidget(const QString &p_info); + + void updatePopupGeometry(); + + void popupWidget(const QSharedPointer &p_widget); + + // Return true if there is any entry visible. + bool filterEntryListWidgetEntries(const QString &p_name); + + void handleEntryFinished(IUnitedEntry *p_entry); + + void setBusy(bool p_busy); + + void exitUnitedEntry(); + + LineEditWithSnippet *m_lineEdit = nullptr; + + EntryPopup *m_popup = nullptr; + + QAction *m_iconAction = nullptr; + + bool m_activated = false; + + QWidget *m_previousFocusWidget = nullptr; + + QTimer *m_processTimer = nullptr; + + QSharedPointer m_entryListWidget = nullptr; + + QSharedPointer m_lastEntry; + + bool m_hasPending = false; + }; +} + +#endif // UNITEDENTRY_H diff --git a/src/unitedentry/unitedentry.pri b/src/unitedentry/unitedentry.pri new file mode 100644 index 00000000..7e03edbc --- /dev/null +++ b/src/unitedentry/unitedentry.pri @@ -0,0 +1,24 @@ +QT += widgets + +HEADERS += \ + $$PWD/entrypopup.h \ + $$PWD/entrywidgetfactory.h \ + $$PWD/findunitedentry.h \ + $$PWD/helpunitedentry.h \ + $$PWD/iunitedentry.h \ + $$PWD/unitedentry.h \ + $$PWD/unitedentryalias.h \ + $$PWD/unitedentryhelper.h \ + $$PWD/unitedentrymgr.h + +SOURCES += \ + $$PWD/entrypopup.cpp \ + $$PWD/entrywidgetfactory.cpp \ + $$PWD/findunitedentry.cpp \ + $$PWD/helpunitedentry.cpp \ + $$PWD/iunitedentry.cpp \ + $$PWD/unitedentry.cpp \ + $$PWD/unitedentryalias.cpp \ + $$PWD/unitedentryhelper.cpp \ + $$PWD/unitedentrymgr.cpp + diff --git a/src/unitedentry/unitedentryalias.cpp b/src/unitedentry/unitedentryalias.cpp new file mode 100644 index 00000000..65ee2244 --- /dev/null +++ b/src/unitedentry/unitedentryalias.cpp @@ -0,0 +1,99 @@ +#include "unitedentryalias.h" + +#include +#include + +#include "unitedentrymgr.h" +#include "entrywidgetfactory.h" + +using namespace vnotex; + +UnitedEntryAlias::UnitedEntryAlias(const QString &p_name, + const QString &p_description, + const QString &p_value, + const UnitedEntryMgr *p_mgr, + QObject *p_parent) + : IUnitedEntry(p_name, p_description, p_mgr, p_parent), + m_value(p_value) +{ + m_alias = UnitedEntryHelper::parseUserEntry(m_value); +} + +UnitedEntryAlias::UnitedEntryAlias(const QJsonObject &p_obj, + const UnitedEntryMgr *p_mgr, + QObject *p_parent) + : UnitedEntryAlias(p_obj[QStringLiteral("name")].toString(), + p_obj[QStringLiteral("description")].toString(), + p_obj[QStringLiteral("value")].toString(), + p_mgr, + p_parent) +{ +} + +QString UnitedEntryAlias::description() const +{ + return tr("[Alias] ") + IUnitedEntry::description(); +} + +QJsonObject UnitedEntryAlias::toJson() const +{ + QJsonObject obj; + obj[QStringLiteral("name")] = name(); + obj[QStringLiteral("description")] = description(); + obj[QStringLiteral("value")] = m_value; + return obj; +} + +void UnitedEntryAlias::initOnFirstProcess() +{ + m_realEntry = m_mgr->findEntry(m_alias.m_name).data(); + if (!m_realEntry) { + qWarning() << "invalid UnitedEntry alias" << name() << m_value; + } else { + connect(m_realEntry, &IUnitedEntry::finished, + this, &IUnitedEntry::finished); + } +} + +void UnitedEntryAlias::processInternal(const QString &p_args, + const std::function &)> &p_popupWidgetFunc) +{ + if (!m_realEntry) { + auto label = EntryWidgetFactory::createLabel(tr("Invalid UnitedEntry alias: %1").arg(m_value)); + p_popupWidgetFunc(label); + emit finished(); + return; + } + + m_realEntry->process(m_alias.m_args + " " + p_args, p_popupWidgetFunc); +} + +bool UnitedEntryAlias::isOngoing() const +{ + if (m_realEntry) { + return m_realEntry->isOngoing(); + } + return false; +} + +void UnitedEntryAlias::setOngoing(bool p_ongoing) +{ + Q_UNUSED(p_ongoing); + Q_ASSERT(false); +} + +void UnitedEntryAlias::handleAction(Action p_act) +{ + if (m_realEntry) { + m_realEntry->handleAction(p_act); + } +} + +QSharedPointer UnitedEntryAlias::currentPopupWidget() const +{ + if (m_realEntry) { + return m_realEntry->currentPopupWidget(); + } + + return nullptr; +} diff --git a/src/unitedentry/unitedentryalias.h b/src/unitedentry/unitedentryalias.h new file mode 100644 index 00000000..9f5c6534 --- /dev/null +++ b/src/unitedentry/unitedentryalias.h @@ -0,0 +1,56 @@ +#ifndef UNITEDENTRYALIAS_H +#define UNITEDENTRYALIAS_H + +#include "iunitedentry.h" + +#include + +#include "unitedentryhelper.h" + +namespace vnotex +{ + class UnitedEntryMgr; + + // UnitedEntry which points to another UnitedEntry. + class UnitedEntryAlias : public IUnitedEntry + { + Q_OBJECT + public: + UnitedEntryAlias(const QString &p_name, + const QString &p_description, + const QString &p_value, + const UnitedEntryMgr *p_mgr, + QObject *p_parent = nullptr); + + UnitedEntryAlias(const QJsonObject &p_obj, + const UnitedEntryMgr *p_mgr, + QObject *p_parent = nullptr); + + QJsonObject toJson() const; + + QString description() const Q_DECL_OVERRIDE; + + bool isOngoing() const Q_DECL_OVERRIDE; + + void handleAction(Action p_act) Q_DECL_OVERRIDE; + + QSharedPointer currentPopupWidget() const Q_DECL_OVERRIDE; + + protected: + void initOnFirstProcess() Q_DECL_OVERRIDE; + + void processInternal(const QString &p_args, + const std::function &)> &p_popupWidgetFunc) Q_DECL_OVERRIDE; + + void setOngoing(bool p_ongoing) Q_DECL_OVERRIDE; + + private: + QString m_value; + + UnitedEntryHelper::UserEntry m_alias; + + IUnitedEntry *m_realEntry = nullptr; + }; +} + +#endif // UNITEDENTRYALIAS_H diff --git a/src/unitedentry/unitedentryhelper.cpp b/src/unitedentry/unitedentryhelper.cpp new file mode 100644 index 00000000..183e45a1 --- /dev/null +++ b/src/unitedentry/unitedentryhelper.cpp @@ -0,0 +1,70 @@ +#include "unitedentryhelper.h" + +#include + +#include +#include +#include + +using namespace vnotex; + +UnitedEntryHelper::UserEntry UnitedEntryHelper::parseUserEntry(const QString &p_text) +{ + UserEntry entry; + + const auto text = p_text.trimmed(); + + if (text.isEmpty()) { + return entry; + } + + int idx = text.indexOf(QLatin1Char(' ')); + if (idx == -1) { + entry.m_name = text.toLower(); + } else { + entry.m_name = text.left(idx).toLower(); + entry.m_args = text.mid(idx).trimmed(); + } + + return entry; +} + +const QIcon &UnitedEntryHelper::itemIcon(ItemType p_type) +{ + static QIcon icons[ItemType::MaxItemType]; + + if (icons[0].isNull()) { + // Init. + const QString nodeIconFgName = "base#icon#fg"; + const auto &themeMgr = VNoteX::getInst().getThemeMgr(); + const auto fg = themeMgr.paletteColor(nodeIconFgName); + + icons[ItemType::Buffer] = IconUtils::fetchIcon(themeMgr.getIconFile("buffer.svg"), fg); + icons[ItemType::File] = IconUtils::fetchIcon(themeMgr.getIconFile("file_node.svg"), fg); + icons[ItemType::Folder] = IconUtils::fetchIcon(themeMgr.getIconFile("folder_node.svg"), fg); + icons[ItemType::Notebook] = IconUtils::fetchIcon(themeMgr.getIconFile("notebook_default.svg"), fg); + icons[ItemType::Other] = IconUtils::fetchIcon(themeMgr.getIconFile("other_item.svg"), fg); + } + + return icons[p_type]; +} + +UnitedEntryHelper::ItemType UnitedEntryHelper::locationTypeToItemType(LocationType p_type) +{ + switch (p_type) { + case LocationType::Buffer: + return ItemType::Buffer; + + case LocationType::File: + return ItemType::File; + + case LocationType::Folder: + return ItemType::Folder; + + case LocationType::Notebook: + return ItemType::Notebook; + + default: + return ItemType::Other; + } +} diff --git a/src/unitedentry/unitedentryhelper.h b/src/unitedentry/unitedentryhelper.h new file mode 100644 index 00000000..60cded5e --- /dev/null +++ b/src/unitedentry/unitedentryhelper.h @@ -0,0 +1,43 @@ +#ifndef UNITEDENTRYHELPER_H +#define UNITEDENTRYHELPER_H + +#include + +#include + +class QIcon; + +namespace vnotex +{ + class UnitedEntryHelper + { + public: + struct UserEntry + { + QString m_name; + + QString m_args; + }; + + enum ItemType + { + Buffer, + File, + Folder, + Notebook, + Other, + MaxItemType + }; + + UnitedEntryHelper() = delete; + + static UserEntry parseUserEntry(const QString &p_text); + + static const QIcon &itemIcon(ItemType p_type); + + static ItemType locationTypeToItemType(LocationType p_type); + }; + +} + +#endif // UNITEDENTRYHELPER_H diff --git a/src/unitedentry/unitedentrymgr.cpp b/src/unitedentry/unitedentrymgr.cpp new file mode 100644 index 00000000..3edccfd6 --- /dev/null +++ b/src/unitedentry/unitedentrymgr.cpp @@ -0,0 +1,80 @@ +#include "unitedentrymgr.h" + +#include "findunitedentry.h" +#include "helpunitedentry.h" +#include "unitedentryalias.h" + +#include +#include +#include +#include + +using namespace vnotex; + +UnitedEntryMgr::UnitedEntryMgr(QObject *p_parent) + : QObject(p_parent) +{ +} + +void UnitedEntryMgr::init() +{ + if (m_initialized) { + return; + } + + m_initialized = true; + + // Built-in entries. + const auto mainWindow = VNoteX::getInst().getMainWindow(); + addEntry(QSharedPointer::create(SearchInfoProvider::create(mainWindow), this)); + + addEntry(QSharedPointer::create(this)); + + // Alias from config. + const auto &config = ConfigMgr::getInst().getCoreConfig(); + const auto &aliasArr = config.getUnitedEntryAlias(); + for (int i = 0; i < aliasArr.size(); ++i) { + auto entry = QSharedPointer::create(aliasArr[i].toObject(), this); + addEntry(entry); + } +} + +void UnitedEntryMgr::addEntry(const QSharedPointer &p_entry) +{ + Q_ASSERT(!m_entries.contains(p_entry->name())); + m_entries.insert(p_entry->name(), p_entry); + connect(p_entry.data(), &IUnitedEntry::finished, + this, [this]() { + emit entryFinished(reinterpret_cast(sender())); + }); +} + +QList> UnitedEntryMgr::getEntries() const +{ + return m_entries.values(); +} + +QSharedPointer UnitedEntryMgr::findEntry(const QString &p_name) const +{ + auto it = m_entries.find(p_name); + if (it == m_entries.end()) { + return nullptr; + } + + return it.value(); +} + +bool UnitedEntryMgr::isInitialized() const +{ + return m_initialized; +} + +bool UnitedEntryMgr::getExpandAllEnabled() const +{ + return m_expandAllEnabled; +} + +void UnitedEntryMgr::setExpandAllEnabled(bool p_enabled) +{ + m_expandAllEnabled = p_enabled; +} diff --git a/src/unitedentry/unitedentrymgr.h b/src/unitedentry/unitedentrymgr.h new file mode 100644 index 00000000..44ef49f0 --- /dev/null +++ b/src/unitedentry/unitedentrymgr.h @@ -0,0 +1,52 @@ +#ifndef UNITEDENTRYMGR_H +#define UNITEDENTRYMGR_H + +#include +#include +#include + +#include + +namespace vnotex +{ + class IUnitedEntry; + + class UnitedEntryMgr : public QObject, private Noncopyable + { + Q_OBJECT + public: + static UnitedEntryMgr &getInst() + { + static UnitedEntryMgr inst; + inst.init(); + return inst; + } + + void init(); + + QList> getEntries() const; + + QSharedPointer findEntry(const QString &p_name) const; + + bool isInitialized() const; + + bool getExpandAllEnabled() const; + void setExpandAllEnabled(bool p_enabled); + + signals: + void entryFinished(IUnitedEntry *p_entry); + + private: + explicit UnitedEntryMgr(QObject *p_parent = nullptr); + + void addEntry(const QSharedPointer &p_entry); + + bool m_initialized = false; + + QMap> m_entries; + + bool m_expandAllEnabled = false; + }; +} + +#endif // UNITEDENTRYMGR_H diff --git a/src/utils/asyncworker.h b/src/utils/asyncworker.h index 38bfe90c..05e713f4 100644 --- a/src/utils/asyncworker.h +++ b/src/utils/asyncworker.h @@ -2,6 +2,7 @@ #define ASYNCWORKER_H #include + #include namespace vnotex diff --git a/src/utils/widgetutils.cpp b/src/utils/widgetutils.cpp index db4db7d6..e9af3680 100644 --- a/src/utils/widgetutils.cpp +++ b/src/utils/widgetutils.cpp @@ -468,3 +468,23 @@ void WidgetUtils::clearLayout(QFormLayout *p_layout) p_layout->removeRow(i); } } + +// Different from QWidget::isAncestorOf(): unnecessary to be within the same window. +bool WidgetUtils::isOrAncestorOf(const QWidget *p_widget, const QWidget *p_child) +{ + Q_ASSERT(p_widget); + + if (!p_child) { + return false; + } + + const QWidget *pa = p_child; + while (pa) { + if (pa == p_widget) { + return true; + } + pa = pa->parentWidget(); + } + + return false; +} diff --git a/src/utils/widgetutils.h b/src/utils/widgetutils.h index b6050fa9..21b4959a 100644 --- a/src/utils/widgetutils.h +++ b/src/utils/widgetutils.h @@ -93,6 +93,8 @@ namespace vnotex static void clearLayout(QFormLayout *p_layout); + static bool isOrAncestorOf(const QWidget *p_widget, const QWidget *p_child); + private: static void resizeToHideScrollBar(QScrollArea *p_scroll, bool p_vertical, bool p_horizontal); }; diff --git a/src/widgets/lineedit.cpp b/src/widgets/lineedit.cpp index b62fd5d1..b7c577ce 100644 --- a/src/widgets/lineedit.cpp +++ b/src/widgets/lineedit.cpp @@ -135,3 +135,8 @@ void LineEdit::updateInputMethod() const // Ask input method to query current state, which will call inputMethodQuery(). im->update(Qt::ImEnabled); } + +QRect LineEdit::cursorRect() const +{ + return QLineEdit::cursorRect(); +} diff --git a/src/widgets/lineedit.h b/src/widgets/lineedit.h index bdeb2bb8..38abcb18 100644 --- a/src/widgets/lineedit.h +++ b/src/widgets/lineedit.h @@ -18,6 +18,8 @@ namespace vnotex void setInputMethodEnabled(bool p_enabled); + QRect cursorRect() const; + static void selectBaseName(QLineEdit *p_lineEdit); protected: diff --git a/src/widgets/locationlist.cpp b/src/widgets/locationlist.cpp index 85864331..5270d014 100644 --- a/src/widgets/locationlist.cpp +++ b/src/widgets/locationlist.cpp @@ -140,6 +140,7 @@ void LocationList::addLocation(const ComplexLocation &p_location) item->setText(Columns::PathColumn, p_location.m_displayPath); item->setIcon(Columns::PathColumn, getItemIcon(p_location.m_type)); item->setData(Columns::PathColumn, Qt::UserRole, p_location.m_path); + item->setToolTip(Columns::PathColumn, p_location.m_path); if (p_location.m_lines.size() == 1) { setItemLocationLineAndText(item, p_location.m_lines[0]); diff --git a/src/widgets/mainwindow.cpp b/src/widgets/mainwindow.cpp index 791e6f7e..77609fe6 100644 --- a/src/widgets/mainwindow.cpp +++ b/src/widgets/mainwindow.cpp @@ -551,6 +551,12 @@ void MainWindow::focusViewArea() m_viewArea->focus(); } +NotebookExplorer *MainWindow::getNotebookExplorer() const +{ + Q_ASSERT(m_notebookExplorer); + return m_notebookExplorer; +} + void MainWindow::setupToolBar() { const int sz = ConfigMgr::getInst().getCoreConfig().getToolBarIconSize(); diff --git a/src/widgets/mainwindow.h b/src/widgets/mainwindow.h index 8465cf36..8175c007 100644 --- a/src/widgets/mainwindow.h +++ b/src/widgets/mainwindow.h @@ -53,6 +53,8 @@ namespace vnotex ViewArea *getViewArea() const; + NotebookExplorer *getNotebookExplorer() const; + void setContentAreaExpanded(bool p_expanded); // Should be called after MainWindow is shown. bool isContentAreaExpanded() const; diff --git a/src/widgets/searchinfoprovider.cpp b/src/widgets/searchinfoprovider.cpp index 636bcb53..6d64bb73 100644 --- a/src/widgets/searchinfoprovider.cpp +++ b/src/widgets/searchinfoprovider.cpp @@ -1,17 +1,19 @@ #include "searchinfoprovider.h" +#include #include "viewarea.h" #include "notebookexplorer.h" #include "notebookmgr.h" +#include "mainwindow.h" using namespace vnotex; SearchInfoProvider::SearchInfoProvider(const ViewArea *p_viewArea, - const NotebookExplorer *p_notebookExplorer, - const NotebookMgr *p_notebookMgr) + const NotebookExplorer *p_notebookExplorer, + const NotebookMgr *p_notebookMgr) : m_viewArea(p_viewArea), - m_notebookExplorer(p_notebookExplorer), - m_notebookMgr(p_notebookMgr) + m_notebookExplorer(p_notebookExplorer), + m_notebookMgr(p_notebookMgr) { } @@ -41,3 +43,10 @@ QVector SearchInfoProvider::getNotebooks() const return nbs; } + +QSharedPointer SearchInfoProvider::create(const MainWindow *p_mainWindow) +{ + return QSharedPointer::create(p_mainWindow->getViewArea(), + p_mainWindow->getNotebookExplorer(), + &VNoteX::getInst().getNotebookMgr()); +} diff --git a/src/widgets/searchinfoprovider.h b/src/widgets/searchinfoprovider.h index 94b18985..e2cd50fa 100644 --- a/src/widgets/searchinfoprovider.h +++ b/src/widgets/searchinfoprovider.h @@ -1,13 +1,16 @@ #ifndef SEARCHINFOPROVIDER_H #define SEARCHINFOPROVIDER_H -#include "searchpanel.h" +#include + +#include namespace vnotex { class ViewArea; class NotebookExplorer; class NotebookMgr; + class MainWindow; class SearchInfoProvider : public ISearchInfoProvider { @@ -24,6 +27,8 @@ namespace vnotex QVector getNotebooks() const Q_DECL_OVERRIDE; + static QSharedPointer create(const MainWindow *p_mainWindow); + private: const ViewArea *m_viewArea = nullptr; diff --git a/src/widgets/searchpanel.cpp b/src/widgets/searchpanel.cpp index b57b17d2..669ce6bd 100644 --- a/src/widgets/searchpanel.cpp +++ b/src/widgets/searchpanel.cpp @@ -29,6 +29,8 @@ #include "mainwindow.h" #include #include +#include +#include #include #include "locationlist.h" @@ -305,7 +307,11 @@ void SearchPanel::startSearch() saveFields(*m_option); - auto state = search(m_option); + QString msg; + auto state = SearchHelper::searchOnProvider(getSearcher(), m_option, m_provider, msg); + if (!msg.isEmpty()) { + appendLog(msg); + } // On end. handleSearchFinished(state); @@ -313,8 +319,6 @@ void SearchPanel::startSearch() void SearchPanel::handleSearchFinished(SearchState p_state) { - qDebug() << "handleSearchFinished" << (int)p_state; - Q_ASSERT(m_searchOngoing); Q_ASSERT(p_state != SearchState::Idle); @@ -420,94 +424,6 @@ void SearchPanel::saveFields(SearchOption &p_option) } } -SearchState SearchPanel::search(const QSharedPointer &p_option) -{ - if (!isSearchOptionValid(*p_option)) { - return SearchState::Failed; - } - - SearchState state = SearchState::Finished; - - switch (p_option->m_scope) { - case SearchScope::Buffers: - { - auto buffers = m_provider->getBuffers(); - if (buffers.isEmpty()) { - break; - } - state = getSearcher()->search(p_option, buffers); - break; - } - - case SearchScope::CurrentFolder: - { - auto notebook = m_provider->getCurrentNotebook(); - if (!notebook) { - break; - } - auto folder = m_provider->getCurrentFolder(); - if (!folder) { - break; - } - - state = getSearcher()->search(p_option, folder); - break; - } - - case SearchScope::CurrentNotebook: - { - auto notebook = m_provider->getCurrentNotebook(); - if (!notebook) { - break; - } - - QVector notebooks; - notebooks.push_back(notebook); - state = getSearcher()->search(p_option, notebooks); - break; - } - - case SearchScope::AllNotebooks: - { - auto notebooks = m_provider->getNotebooks(); - if (notebooks.isEmpty()) { - break; - } - - state = getSearcher()->search(p_option, notebooks); - break; - } - } - - return state; -} - -bool SearchPanel::isSearchOptionValid(const SearchOption &p_option) -{ - if (p_option.m_keyword.isEmpty()) { - appendLog(tr("Invalid keyword")); - return false; - } - - if (p_option.m_objects == SearchObject::ObjectNone) { - appendLog(tr("No object specified")); - return false; - } - - if (p_option.m_targets == SearchTarget::TargetNone) { - appendLog(tr("No target specified")); - return false; - } - - if (p_option.m_findOptions & FindOption::FuzzySearch - && p_option.m_objects & SearchObject::SearchContent) { - appendLog(tr("Fuzzy search is not allowed when searching content")); - return false; - } - - return true; -} - Searcher *SearchPanel::getSearcher() { if (!m_searcher) { diff --git a/src/widgets/searchpanel.h b/src/widgets/searchpanel.h index 56c6028d..c21bda6c 100644 --- a/src/widgets/searchpanel.h +++ b/src/widgets/searchpanel.h @@ -3,7 +3,6 @@ #include #include -#include #include #include @@ -21,28 +20,10 @@ class QVBoxLayout; namespace vnotex { class TitleBar; - class Buffer; - class Node; - class Notebook; class LocationList; struct Location; class SearchToken; - - class ISearchInfoProvider - { - public: - ISearchInfoProvider() = default; - - virtual ~ISearchInfoProvider() = default; - - virtual QList getBuffers() const = 0; - - virtual Node *getCurrentFolder() const = 0; - - virtual Notebook *getCurrentNotebook() const = 0; - - virtual QVector getNotebooks() const = 0; - }; + class ISearchInfoProvider; class SearchPanel : public QFrame { @@ -85,10 +66,6 @@ namespace vnotex void clearLog(); - SearchState search(const QSharedPointer &p_option); - - bool isSearchOptionValid(const SearchOption &p_option); - Searcher *getSearcher(); void prepareLocationList(); diff --git a/src/widgets/toolbarhelper.cpp b/src/widgets/toolbarhelper.cpp index b91f03ce..98b2aca1 100644 --- a/src/widgets/toolbarhelper.cpp +++ b/src/widgets/toolbarhelper.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include "propertydefs.h" #include "dialogs/settings/settingsdialog.h" #include "dialogs/updater.h" @@ -279,6 +280,35 @@ QToolBar *ToolBarHelper::setupQuickAccessToolBar(MainWindow *p_win, QToolBar *p_ tb->addWidget(toolBtn); } + // Task. + { + auto act = tb->addAction(generateIcon("task_menu.svg"), MainWindow::tr("Task")); + auto btn = dynamic_cast(tb->widgetForAction(act)); + btn->setPopupMode(QToolButton::InstantPopup); + btn->setProperty(PropertyDefs::c_toolButtonWithoutMenuIndicator, true); + + auto taskMenu = WidgetsFactory::createMenu(tb); + setupTaskActionMenu(taskMenu); + btn->setMenu(taskMenu); + MainWindow::connect(taskMenu, &QMenu::triggered, + taskMenu, [](QAction *act) { + auto task = reinterpret_cast(act->data().toULongLong()); + if (task) { + task->run(); + } + }); + MainWindow::connect(&VNoteX::getInst().getTaskMgr(), &TaskMgr::tasksUpdated, + taskMenu, [taskMenu]() { + setupTaskMenu(taskMenu); + }); + } + + // United Entry. + { + auto ueEdit = new UnitedEntry(tb); + tb->addWidget(ueEdit); + } + return tb; } @@ -361,36 +391,6 @@ void ToolBarHelper::addTaskMenu(QMenu *p_menu, Task *p_task) WidgetUtils::addActionShortcut(action, p_task->getShortcut()); } -QToolBar *ToolBarHelper::setupTaskToolBar(MainWindow *p_win, QToolBar *p_toolBar) -{ - auto tb = p_toolBar; - if (!tb) { - tb = createToolBar(p_win, MainWindow::tr("Task"), "TaskToolBar"); - } - - auto act = tb->addAction(generateIcon("task_menu.svg"), MainWindow::tr("Task")); - auto btn = dynamic_cast(tb->widgetForAction(act)); - btn->setPopupMode(QToolButton::InstantPopup); - btn->setProperty(PropertyDefs::c_toolButtonWithoutMenuIndicator, true); - - auto taskMenu = WidgetsFactory::createMenu(tb); - setupTaskActionMenu(taskMenu); - btn->setMenu(taskMenu); - MainWindow::connect(taskMenu, &QMenu::triggered, - taskMenu, [](QAction *act) { - auto task = reinterpret_cast(act->data().toULongLong()); - if (task) { - task->run(); - } - }); - MainWindow::connect(&VNoteX::getInst().getTaskMgr(), &TaskMgr::tasksUpdated, - taskMenu, [taskMenu]() { - setupTaskMenu(taskMenu); - }); - - return tb; -} - QToolBar *ToolBarHelper::setupSettingsToolBar(MainWindow *p_win, QToolBar *p_toolBar) { auto tb = p_toolBar; @@ -453,8 +453,6 @@ void ToolBarHelper::setupToolBars(MainWindow *p_mainWindow) setupQuickAccessToolBar(p_mainWindow, nullptr); - setupTaskToolBar(p_mainWindow, nullptr); - setupSettingsToolBar(p_mainWindow, nullptr); } @@ -466,7 +464,6 @@ void ToolBarHelper::setupToolBars(MainWindow *p_mainWindow, QToolBar *p_toolBar) setupFileToolBar(p_mainWindow, p_toolBar); setupQuickAccessToolBar(p_mainWindow, p_toolBar); - setupTaskToolBar(p_mainWindow, p_toolBar); setupSettingsToolBar(p_mainWindow, p_toolBar); } diff --git a/src/widgets/toolbarhelper.h b/src/widgets/toolbarhelper.h index b4094c68..147b9f07 100644 --- a/src/widgets/toolbarhelper.h +++ b/src/widgets/toolbarhelper.h @@ -40,8 +40,6 @@ namespace vnotex static void addTaskMenu(QMenu *p_menu, Task *p_task); - static QToolBar *setupTaskToolBar(MainWindow *p_win, QToolBar *p_toolBar); - static QToolBar *setupSettingsToolBar(MainWindow *p_win, QToolBar *p_toolBar); static void updateQuickAccessMenu(QMenu *p_menu); diff --git a/src/widgets/treewidget.cpp b/src/widgets/treewidget.cpp index baf78c97..2459a784 100644 --- a/src/widgets/treewidget.cpp +++ b/src/widgets/treewidget.cpp @@ -282,3 +282,46 @@ void TreeWidget::expandRecursively(QTreeWidgetItem *p_item) expandRecursively(p_item->child(i)); } } + +void TreeWidget::selectParentItem(QTreeWidget *p_widget) +{ + auto item = p_widget->currentItem(); + if (item) { + auto pitem = item->parent(); + if (pitem) { + p_widget->setCurrentItem(pitem, 0, QItemSelectionModel::ClearAndSelect); + } + } +} + +static bool isItemTreeExpanded(const QTreeWidgetItem *p_item) +{ + if (!p_item) { + return true; + } + + if (p_item->isHidden() || !p_item->isExpanded()) { + return false; + } + + int cnt = p_item->childCount(); + for (int i = 0; i < cnt; ++i) { + if (!isItemTreeExpanded(p_item->child(i))) { + return false; + } + } + + return true; +} + +bool TreeWidget::isExpanded(const QTreeWidget *p_widget) +{ + int cnt = p_widget->topLevelItemCount(); + for (int i = 0; i < cnt; ++i) { + if (!isItemTreeExpanded(p_widget->topLevelItem(i))) { + return false; + } + } + + return true; +} diff --git a/src/widgets/treewidget.h b/src/widgets/treewidget.h index 7dc05472..4a53fb09 100644 --- a/src/widgets/treewidget.h +++ b/src/widgets/treewidget.h @@ -44,6 +44,10 @@ namespace vnotex static void expandRecursively(QTreeWidgetItem *p_item); + static void selectParentItem(QTreeWidget *p_widget); + + static bool isExpanded(const QTreeWidget *p_widget); + signals: // Emit when single item is selected and Drag&Drop to move internally. void itemMoved(QTreeWidgetItem *p_item); diff --git a/tests/commonfull.pri b/tests/commonfull.pri index 367a945c..90c5ead0 100644 --- a/tests/commonfull.pri +++ b/tests/commonfull.pri @@ -27,3 +27,5 @@ include($$SRC_FOLDER/task/task.pri) include($$SRC_FOLDER/core/core.pri) include($$SRC_FOLDER/widgets/widgets.pri) + +include($$SRC_FOLDER/unitedentry/unitedentry.pri)