diff --git a/src/utils/vutils.cpp b/src/utils/vutils.cpp index 90a42011..96564d45 100644 --- a/src/utils/vutils.cpp +++ b/src/utils/vutils.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include "vorphanfile.h" #include "vnote.h" @@ -1390,3 +1391,16 @@ QStringList VUtils::parseCombinedArgString(const QString &p_program) return args; } + +const QTreeWidgetItem *VUtils::topLevelTreeItem(const QTreeWidgetItem *p_item) +{ + if (!p_item) { + return NULL; + } + + if (p_item->parent()) { + return p_item->parent(); + } else { + return p_item; + } +} diff --git a/src/utils/vutils.h b/src/utils/vutils.h index 02c5ddda..cfba9c5a 100644 --- a/src/utils/vutils.h +++ b/src/utils/vutils.h @@ -19,6 +19,7 @@ class QWidget; class QComboBox; class QWebEngineView; class QAction; +class QTreeWidgetItem; #if !defined(V_ASSERT) #define V_ASSERT(cond) ((!(cond)) ? qt_assert(#cond, __FILE__, __LINE__) : qt_noop()) @@ -314,6 +315,8 @@ public: // From QProcess code. static QStringList parseCombinedArgString(const QString &p_program); + static const QTreeWidgetItem *topLevelTreeItem(const QTreeWidgetItem *p_item); + // Regular expression for image link. // ![image title]( http://github.com/tamlok/vnote.jpg "alt \" text" ) // Captured texts (need to be trimmed): diff --git a/src/vlistwidget.cpp b/src/vlistwidget.cpp index d354ce3f..e891aa3e 100644 --- a/src/vlistwidget.cpp +++ b/src/vlistwidget.cpp @@ -176,11 +176,11 @@ void VListWidget::sortListWidget(QListWidget *p_list, const QVector &p_sort QSize VListWidget::sizeHint() const { - if (count() == 0 || !m_fitContent) { + int cnt = count(); + if (cnt == 0 || !m_fitContent) { return QListWidget::sizeHint(); } else { // Adjust size to content. - int cnt = count(); int hei = 0; int wid = sizeHintForColumn(0) + 10; for (int i = 0; i < cnt; ++i) { diff --git a/src/vlistwidget.h b/src/vlistwidget.h index e3770357..88a64878 100644 --- a/src/vlistwidget.h +++ b/src/vlistwidget.h @@ -32,7 +32,7 @@ public: virtual void selectNextItem(bool p_forward) Q_DECL_OVERRIDE; - QSize sizeHint() const Q_DECL_OVERRIDE; + virtual QSize sizeHint() const Q_DECL_OVERRIDE; // Sort @p_list according to @p_sortedIdx. static void sortListWidget(QListWidget *p_list, const QVector &p_sortedIdx); diff --git a/src/vmainwindow.cpp b/src/vmainwindow.cpp index 4cf6c4e5..787c67d5 100644 --- a/src/vmainwindow.cpp +++ b/src/vmainwindow.cpp @@ -3201,4 +3201,5 @@ void VMainWindow::initUniversalEntry() VSearchUE *searchUE = new VSearchUE(this); m_ue->registerEntry('q', searchUE, VSearchUE::Name_Notebook_AllNotebook); m_ue->registerEntry('a', searchUE, VSearchUE::Name_FolderNote_AllNotebook); + m_ue->registerEntry('z', searchUE, VSearchUE::Content_Note_AllNotebook); } diff --git a/src/vsearchconfig.h b/src/vsearchconfig.h index 6d944a4d..dc53ab35 100644 --- a/src/vsearchconfig.h +++ b/src/vsearchconfig.h @@ -259,6 +259,16 @@ struct VSearchConfig compileToken(p_keyword); } + // We support some magic switch in the keyword which will suppress the specified + // options: + // \c: Case insensitive; + // \C: Case sensitive; + // \r: Turn off regular expression; + // \R: Turn on regular expression; + // \f: Turn off fuzzy search; + // \F: Turn on fuzzy search (invalid when searching content); + // \w: Turn off whole word only; + // \W: Turn on whole word only; void compileToken(const QString &p_keyword) { m_token.clear(); @@ -267,12 +277,48 @@ struct VSearchConfig return; } + // """ to input a "; + // && for AND, || for OR; + QStringList args = VUtils::parseCombinedArgString(p_keyword); + Qt::CaseSensitivity cs = m_option & VSearchConfig::CaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive; bool useReg = m_option & VSearchConfig::RegularExpression; bool wwo = m_option & VSearchConfig::WholeWordOnly; bool fuzzy = m_option & VSearchConfig::Fuzzy; + // Read magic switch from keyword. + for (int i = 0; i < args.size();) { + const QString &arg = args[i]; + if (arg.size() != 2 || arg[0] != '\\') { + ++i; + continue; + } + + if (arg == "\\c") { + cs = Qt::CaseInsensitive; + } else if (arg == "\\C") { + cs = Qt::CaseSensitive; + } else if (arg == "\\r") { + useReg = false; + } else if (arg == "\\R") { + useReg = true; + } else if (arg == "\\f") { + fuzzy = false; + } else if (arg == "\\F") { + fuzzy = true; + } else if (arg == "\\w") { + wwo = false; + } else if (arg == "\\W") { + wwo = true; + } else { + ++i; + continue; + } + + args.removeAt(i); + } + m_token.m_caseSensitivity = cs; m_contentToken.m_caseSensitivity = cs; @@ -293,10 +339,6 @@ struct VSearchConfig } VSearchToken::Operator op = VSearchToken::And; - - // """ to input a "; - // && for AND, || for OR; - QStringList args = VUtils::parseCombinedArgString(p_keyword); for (auto const & arg : args) { if (arg == QStringLiteral("&&")) { op = VSearchToken::And; diff --git a/src/vsearchresulttree.cpp b/src/vsearchresulttree.cpp index 0c9487cc..ddc4f25e 100644 --- a/src/vsearchresulttree.cpp +++ b/src/vsearchresulttree.cpp @@ -3,6 +3,7 @@ #include #include +#include "utils/vutils.h" #include "utils/viconutils.h" #include "vnote.h" #include "vmainwindow.h" @@ -43,7 +44,7 @@ void VSearchResultTree::initActions() m_openAct->setToolTip(tr("Open selected notes")); connect(m_openAct, &QAction::triggered, this, [this]() { - activateItem(topLevelItem(currentItem())); + activateItem(currentItem()); }); m_locateAct = new QAction(VIconUtils::menuIcon(":/resources/icons/locate_note.svg"), @@ -207,7 +208,7 @@ VSearchResultItem::ItemType VSearchResultTree::itemResultType(const QTreeWidgetI const QSharedPointer &VSearchResultTree::itemResultData(const QTreeWidgetItem *p_item) const { Q_ASSERT(p_item); - const QTreeWidgetItem *topItem = topLevelItem(p_item); + const QTreeWidgetItem *topItem = VUtils::topLevelTreeItem(p_item); int idx = topItem->data(0, Qt::UserRole).toInt(); Q_ASSERT(idx >= 0 && idx < m_data.size()); return m_data[idx]; diff --git a/src/vsearchresulttree.h b/src/vsearchresulttree.h index bc9f2e48..253b8cfd 100644 --- a/src/vsearchresulttree.h +++ b/src/vsearchresulttree.h @@ -39,8 +39,6 @@ private: VSearchResultItem::ItemType itemResultType(const QTreeWidgetItem *p_item) const; - const QTreeWidgetItem *topLevelItem(const QTreeWidgetItem *p_item) const; - void activateItem(const QTreeWidgetItem *p_item) const; const QSharedPointer &itemResultData(const QTreeWidgetItem *p_item) const; @@ -58,16 +56,4 @@ private: QAction *m_addToCartAct; }; -inline const QTreeWidgetItem *VSearchResultTree::topLevelItem(const QTreeWidgetItem *p_item) const -{ - if (!p_item) { - return NULL; - } - - if (p_item->parent()) { - return p_item->parent(); - } else { - return p_item; - } -} #endif // VSEARCHRESULTTREE_H diff --git a/src/vsearchue.cpp b/src/vsearchue.cpp index 3055fdb3..1a452af9 100644 --- a/src/vsearchue.cpp +++ b/src/vsearchue.cpp @@ -4,10 +4,12 @@ #include #include "vlistwidgetdoublerows.h" +#include "vtreewidget.h" #include "vnotebook.h" #include "vnote.h" #include "vsearch.h" #include "utils/viconutils.h" +#include "utils/vutils.h" #include "vmainwindow.h" #include "vnotebookselector.h" #include "vnotefile.h" @@ -20,8 +22,9 @@ VSearchUE::VSearchUE(QObject *p_parent) : IUniversalEntry(p_parent), m_search(NULL), m_inSearch(false), + m_id(ID::Name_Notebook_AllNotebook), m_listWidget(NULL), - m_id(ID::Name_Notebook_AllNotebook) + m_treeWidget(NULL) { } @@ -34,6 +37,9 @@ QString VSearchUE::description(int p_id) const case ID::Name_FolderNote_AllNotebook: return tr("Search the name of folders/notes in all notebooks"); + case ID::Content_Note_AllNotebook: + return tr("Search the content of notes in all notebooks"); + default: Q_ASSERT(false); return tr("Invalid ID %1").arg(p_id); @@ -63,8 +69,20 @@ void VSearchUE::init() m_listWidget = new VListWidgetDoubleRows(m_widgetParent); m_listWidget->setFitContent(true); m_listWidget->hide(); - connect(m_listWidget, &VListWidgetDoubleRows::itemActivated, - this, &VSearchUE::activateItem); + connect(m_listWidget, SIGNAL(itemActivated(QListWidgetItem *)), + this, SLOT(activateItem(QListWidgetItem *))); + + m_treeWidget = new VTreeWidget(m_widgetParent); + m_treeWidget->setColumnCount(1); + m_treeWidget->setHeaderHidden(true); + m_treeWidget->setExpandsOnDoubleClick(false); + m_treeWidget->setSimpleSearchMatchFlags(m_treeWidget->getSimpleSearchMatchFlags() & ~Qt::MatchRecursive); + m_treeWidget->setFitContent(true); + m_treeWidget->hide(); + connect(m_treeWidget, SIGNAL(itemActivated(QTreeWidgetItem *, int)), + this, SLOT(activateItem(QTreeWidgetItem *, int))); + connect(m_treeWidget, &VTreeWidget::itemExpanded, + this, &VSearchUE::widgetUpdated); } QWidget *VSearchUE::widget(int p_id) @@ -73,10 +91,12 @@ QWidget *VSearchUE::widget(int p_id) switch (p_id) { case ID::Name_Notebook_AllNotebook: - V_FALLTHROUGH; case ID::Name_FolderNote_AllNotebook: return m_listWidget; + case ID::Content_Note_AllNotebook: + return m_treeWidget; + default: Q_ASSERT(false); return NULL; @@ -103,6 +123,10 @@ void VSearchUE::processCommand(int p_id, const QString &p_cmd) searchNameOfFolderNoteInAllNotebooks(p_cmd); break; + case ID::Content_Note_AllNotebook: + searchContentOfNoteInAllNotebooks(p_cmd); + break; + default: Q_ASSERT(false); break; @@ -165,6 +189,27 @@ void VSearchUE::searchNameOfFolderNoteInAllNotebooks(const QString &p_cmd) } } +void VSearchUE::searchContentOfNoteInAllNotebooks(const QString &p_cmd) +{ + const QVector ¬ebooks = g_vnote->getNotebooks(); + if (p_cmd.isEmpty()) { + m_inSearch = false; + emit stateUpdated(State::Success); + } else { + VSearchConfig::Option opt = VSearchConfig::NoneOption; + QSharedPointer config(new VSearchConfig(VSearchConfig::AllNotebooks, + VSearchConfig::Content, + VSearchConfig::Note, + VSearchConfig::Internal, + opt, + p_cmd, + QString())); + m_search->setConfig(config); + QSharedPointer result = m_search->search(notebooks); + handleSearchFinished(result); + } +} + void VSearchUE::clear(int p_id) { Q_UNUSED(p_id); @@ -172,6 +217,7 @@ void VSearchUE::clear(int p_id) m_data.clear(); m_listWidget->clearAll(); + m_treeWidget->clearAll(); } void VSearchUE::entryHidden(int p_id) @@ -183,11 +229,14 @@ void VSearchUE::handleSearchItemAdded(const QSharedPointer &p { switch (m_id) { case ID::Name_Notebook_AllNotebook: - V_FALLTHROUGH; case ID::Name_FolderNote_AllNotebook: appendItemToList(p_item); break; + case ID::Content_Note_AllNotebook: + appendItemToTree(p_item); + break; + default: break; } @@ -228,17 +277,71 @@ void VSearchUE::appendItemToList(const QSharedPointer &p_item item->setData(Qt::UserRole, m_data.size() - 1); item->setToolTip(p_item->m_path); + ++itemAdded; if (m_listWidget->currentRow() == -1) { m_listWidget->setCurrentRow(0); - } - - if (++itemAdded >= 10) { + m_listWidget->updateGeometry(); + emit widgetUpdated(); + } else if (itemAdded >= 20) { itemAdded = 0; m_listWidget->updateGeometry(); emit widgetUpdated(); } } +void VSearchUE::appendItemToTree(const QSharedPointer &p_item) +{ + static int itemAdded = 0; + m_data.append(p_item); + + QTreeWidgetItem *item = new QTreeWidgetItem(m_treeWidget); + item->setData(0, Qt::UserRole, m_data.size() - 1); + item->setText(0, p_item->m_text.isEmpty() ? p_item->m_path : p_item->m_text); + item->setToolTip(0, p_item->m_path); + + switch (p_item->m_type) { + case VSearchResultItem::Note: + item->setIcon(0, m_noteIcon); + break; + + case VSearchResultItem::Folder: + item->setIcon(0, m_folderIcon); + break; + + case VSearchResultItem::Notebook: + item->setIcon(0, m_notebookIcon); + break; + + default: + break; + } + + for (auto const & it: p_item->m_matches) { + QTreeWidgetItem *subItem = new QTreeWidgetItem(item); + QString text; + if (it.m_lineNumber > -1) { + text = QString("[%1] %2").arg(it.m_lineNumber).arg(it.m_text); + } else { + text = it.m_text; + } + + subItem->setText(0, text); + subItem->setToolTip(0, it.m_text); + } + + ++itemAdded; + if (!m_treeWidget->currentItem()) { + m_treeWidget->setCurrentItem(item); + m_treeWidget->resizeColumnToContents(0); + m_treeWidget->updateGeometry(); + emit widgetUpdated(); + } else if (itemAdded >= 20) { + itemAdded = 0; + m_treeWidget->updateGeometry(); + emit widgetUpdated(); + } +} + void VSearchUE::handleSearchFinished(const QSharedPointer &p_result) { Q_ASSERT(m_inSearch); @@ -248,11 +351,13 @@ void VSearchUE::handleSearchFinished(const QSharedPointer &p_resu IUniversalEntry::State state = State::Idle; + bool finished = true; switch (p_result->m_state) { case VSearchState::Busy: qDebug() << "search is ongoing"; state = State::Busy; - return; + finished = false; + break; case VSearchState::Success: qDebug() << "search succeeded"; @@ -273,10 +378,17 @@ void VSearchUE::handleSearchFinished(const QSharedPointer &p_resu break; } - m_search->clear(); - m_inSearch = false; + if (finished) { + m_search->clear(); + m_inSearch = false; + } - widget(m_id)->updateGeometry(); + QWidget *wid = widget(m_id); + if (wid == m_treeWidget) { + m_treeWidget->resizeColumnToContents(0); + } + + wid->updateGeometry(); emit widgetUpdated(); emit stateUpdated(state); @@ -302,26 +414,28 @@ const QSharedPointer &VSearchUE::itemResultData(const QListWi return m_data[idx]; } -void VSearchUE::activateItem(const QListWidgetItem *p_item) +const QSharedPointer &VSearchUE::itemResultData(const QTreeWidgetItem *p_item) const { - if (!p_item) { - return; - } + Q_ASSERT(p_item); + const QTreeWidgetItem *topItem = VUtils::topLevelTreeItem(p_item); + int idx = topItem->data(0, Qt::UserRole).toInt(); + Q_ASSERT(idx >= 0 && idx < m_data.size()); + return m_data[idx]; +} - emit requestHideUniversalEntry(); - - const QSharedPointer &resItem = itemResultData(p_item); - switch (resItem->m_type) { +void VSearchUE::activateItem(const QSharedPointer &p_item) +{ + switch (p_item->m_type) { case VSearchResultItem::Note: { - QStringList files(resItem->m_path); + QStringList files(p_item->m_path); g_mainWin->openFiles(files); break; } case VSearchResultItem::Folder: { - VDirectory *dir = g_vnote->getInternalDirectory(resItem->m_path); + VDirectory *dir = g_vnote->getInternalDirectory(p_item->m_path); if (dir) { g_mainWin->locateDirectory(dir); } @@ -331,7 +445,7 @@ void VSearchUE::activateItem(const QListWidgetItem *p_item) case VSearchResultItem::Notebook: { - VNotebook *nb = g_vnote->getNotebook(resItem->m_path); + VNotebook *nb = g_vnote->getNotebook(p_item->m_path); if (nb) { g_mainWin->getNotebookSelector()->locateNotebook(nb); } @@ -344,11 +458,31 @@ void VSearchUE::activateItem(const QListWidgetItem *p_item) } } +void VSearchUE::activateItem(QListWidgetItem *p_item) +{ + if (!p_item) { + return; + } + + emit requestHideUniversalEntry(); + activateItem(itemResultData(p_item)); +} + +void VSearchUE::activateItem(QTreeWidgetItem *p_item, int p_col) +{ + Q_UNUSED(p_col); + if (!p_item) { + return; + } + + emit requestHideUniversalEntry(); + activateItem(itemResultData(p_item)); +} + void VSearchUE::selectNextItem(int p_id, bool p_forward) { switch (p_id) { case ID::Name_Notebook_AllNotebook: - V_FALLTHROUGH; case ID::Name_FolderNote_AllNotebook: { // Could not use postEvent method here which will induce infinite recursion. @@ -356,6 +490,12 @@ void VSearchUE::selectNextItem(int p_id, bool p_forward) break; } + case ID::Content_Note_AllNotebook: + { + m_treeWidget->selectNextItem(p_forward); + break; + } + default: Q_ASSERT(false); } @@ -365,13 +505,18 @@ void VSearchUE::activate(int p_id) { switch (p_id) { case ID::Name_Notebook_AllNotebook: - V_FALLTHROUGH; case ID::Name_FolderNote_AllNotebook: { activateItem(m_listWidget->currentItem()); break; } + case ID::Content_Note_AllNotebook: + { + activateItem(m_treeWidget->currentItem(), 0); + break; + } + default: Q_ASSERT(false); } diff --git a/src/vsearchue.h b/src/vsearchue.h index 0b75e76e..ba859317 100644 --- a/src/vsearchue.h +++ b/src/vsearchue.h @@ -10,6 +10,8 @@ class VListWidgetDoubleRows; class QListWidgetItem; +class VTreeWidget; +class QTreeWidgetItem; // Universal Entry to list and search all the notebooks. @@ -23,7 +25,10 @@ public: Name_Notebook_AllNotebook = 0, // Search the name of the folder/note in all the notebooks. - Name_FolderNote_AllNotebook + Name_FolderNote_AllNotebook, + + // Search content of the note in all the notebooks. + Content_Note_AllNotebook, }; explicit VSearchUE(QObject *p_parent = nullptr); @@ -52,20 +57,30 @@ private slots: void handleSearchFinished(const QSharedPointer &p_result); + void activateItem(QListWidgetItem *p_item); + + void activateItem(QTreeWidgetItem *p_item, int p_col); + private: void searchNameOfAllNotebooks(const QString &p_cmd); void searchNameOfFolderNoteInAllNotebooks(const QString &p_cmd); + void searchContentOfNoteInAllNotebooks(const QString &p_cmd); + // Stop the search synchronously. void stopSearch(); void appendItemToList(const QSharedPointer &p_item); - void activateItem(const QListWidgetItem *p_item); + void appendItemToTree(const QSharedPointer &p_item); + + void activateItem(const QSharedPointer &p_item); const QSharedPointer &itemResultData(const QListWidgetItem *p_item) const; + const QSharedPointer &itemResultData(const QTreeWidgetItem *p_item) const; + VSearch *m_search; bool m_inSearch; @@ -80,6 +95,8 @@ private: QIcon m_notebookIcon; VListWidgetDoubleRows *m_listWidget; + + VTreeWidget *m_treeWidget; }; #endif // VSEARCHUE_H diff --git a/src/vtreewidget.cpp b/src/vtreewidget.cpp index e615e5a9..4990e33d 100644 --- a/src/vtreewidget.cpp +++ b/src/vtreewidget.cpp @@ -18,7 +18,8 @@ VTreeWidget::VTreeWidget(QWidget *p_parent) : QTreeWidget(p_parent), - ISimpleSearch() + ISimpleSearch(), + m_fitContent(false) { setAttribute(Qt::WA_MacShowFocusRect, false); @@ -45,6 +46,13 @@ VTreeWidget::VTreeWidget(QWidget *p_parent) m_delegate = new VStyledItemDelegate(this); setItemDelegate(m_delegate); + + connect(this, &VTreeWidget::itemExpanded, + this, [this]() { + if (m_fitContent) { + resizeColumnToContents(0); + } + }); } void VTreeWidget::keyPressEvent(QKeyEvent *p_event) @@ -288,26 +296,32 @@ void VTreeWidget::selectNextItem(bool p_forward) return; } - QTreeWidgetItem *nextItem = NULL; + QTreeWidgetItem *nItem = nextItem(item, p_forward); + if (nItem) { + setCurrentItem(nItem); + } +} + +QTreeWidgetItem *VTreeWidget::nextItem(QTreeWidgetItem *p_item, bool p_forward) +{ + QTreeWidgetItem *nItem = NULL; if (p_forward) { - if (item->isExpanded()) { - nextItem = item->child(0); + if (p_item->isExpanded()) { + nItem = p_item->child(0); } else { - while (!nextItem && item) { - nextItem = nextSibling(item, true); - item = item->parent(); + while (!nItem && p_item) { + nItem = nextSibling(p_item, true); + p_item = p_item->parent(); } } } else { - nextItem = nextSibling(item, false); - if (!nextItem) { - nextItem = item->parent(); + nItem = nextSibling(p_item, false); + if (!nItem) { + nItem = p_item->parent(); } else { - nextItem = lastItemOfTree(nextItem); + nItem = lastItemOfTree(nItem); } } - if (nextItem) { - setCurrentItem(nextItem); - } + return nItem; } diff --git a/src/vtreewidget.h b/src/vtreewidget.h index 2c53cd3d..2972cfcc 100644 --- a/src/vtreewidget.h +++ b/src/vtreewidget.h @@ -39,6 +39,8 @@ public: virtual void selectNextItem(bool p_forward) Q_DECL_OVERRIDE; + void setFitContent(bool p_enabled); + protected: void keyPressEvent(QKeyEvent *p_event) Q_DECL_OVERRIDE; @@ -63,11 +65,16 @@ private: QTreeWidgetItem *nextSibling(QTreeWidgetItem *p_item, bool p_forward); + // Next visible item. + QTreeWidgetItem *nextItem(QTreeWidgetItem *p_item, bool p_forward); + VSimpleSearchInput *m_searchInput; VStyledItemDelegate *m_delegate; QTimer *m_searchColdTimer; + + bool m_fitContent; }; inline void VTreeWidget::setSimpleSearchMatchFlags(Qt::MatchFlags p_flags) @@ -79,4 +86,11 @@ inline Qt::MatchFlags VTreeWidget::getSimpleSearchMatchFlags() const { return m_searchInput->getMatchFlags(); } + +inline void VTreeWidget::setFitContent(bool p_enabled) +{ + m_fitContent = p_enabled; + setSizeAdjustPolicy(m_fitContent ? QAbstractScrollArea::AdjustToContents + : QAbstractScrollArea::AdjustIgnored); +} #endif // VTREEWIDGET_H