From b46a8f4f39d1f05e43c831774739448cc3ee7d7f Mon Sep 17 00:00:00 2001 From: Le Tan Date: Thu, 29 Mar 2018 19:47:47 +0800 Subject: [PATCH] UniversalEntry: add q to list all notebooks and a to search name of folder/note --- src/iuniversalentry.h | 73 ++++ .../themes/v_moonlight/v_moonlight.palette | 8 + .../themes/v_moonlight/v_moonlight.qss | 14 + .../themes/v_native/v_native.palette | 4 + src/resources/themes/v_pure/v_pure.palette | 8 + src/resources/themes/v_pure/v_pure.qss | 14 + src/src.pro | 11 +- src/vdoublerowitemwidget.cpp | 37 ++ src/vdoublerowitemwidget.h | 21 + src/vlistwidget.cpp | 53 ++- src/vlistwidget.h | 15 +- src/vlistwidgetdoublerows.cpp | 55 +++ src/vlistwidgetdoublerows.h | 24 ++ src/vmainwindow.cpp | 29 +- src/vmainwindow.h | 2 + src/vmdeditoperations.cpp | 1 + src/vmetawordlineedit.cpp | 5 + src/vmetawordlineedit.h | 2 + src/vsearchue.cpp | 384 ++++++++++++++++++ src/vsearchue.h | 85 ++++ src/vuniversalentry.cpp | 296 +++++++++++++- src/vuniversalentry.h | 86 +++- 22 files changed, 1198 insertions(+), 29 deletions(-) create mode 100644 src/iuniversalentry.h create mode 100644 src/vdoublerowitemwidget.cpp create mode 100644 src/vdoublerowitemwidget.h create mode 100644 src/vlistwidgetdoublerows.cpp create mode 100644 src/vlistwidgetdoublerows.h create mode 100644 src/vsearchue.cpp create mode 100644 src/vsearchue.h diff --git a/src/iuniversalentry.h b/src/iuniversalentry.h new file mode 100644 index 00000000..adb6011f --- /dev/null +++ b/src/iuniversalentry.h @@ -0,0 +1,73 @@ +#ifndef IUNIVERSALENTRY_H +#define IUNIVERSALENTRY_H + +#include +#include + +// Interface for one Universal Entry. +// An entry may be used for different keys, in which case we could use an ID to +// identify. +class IUniversalEntry : public QObject +{ + Q_OBJECT +public: + enum State + { + Idle, + Busy, + Success, + Fail, + Cancelled + }; + + IUniversalEntry(QObject *p_parent = nullptr) + : QObject(p_parent), + m_initialized(false), + m_widgetParent(NULL) + { + } + + // Return a description string for the entry. + virtual QString description(int p_id) const = 0; + + // Return the widget to show to user. + virtual QWidget *widget(int p_id) = 0; + + virtual void processCommand(int p_id, const QString &p_cmd) = 0; + + // This UE is not used now. Free resources. + virtual void clear(int p_id) = 0; + + // UE is hidden by the user. + virtual void entryHidden(int p_id) = 0; + + // Select next item. + virtual void selectNextItem(int p_id, bool p_forward) = 0; + + // Activate current item. + virtual void activate(int p_id) = 0; + + // Ask the UE to stop asynchronously. + virtual void askToStop(int p_id) = 0; + + void setWidgetParent(QWidget *p_parent) + { + m_widgetParent = p_parent; + } + +signals: + void widgetUpdated(); + + void stateUpdated(IUniversalEntry::State p_state); + + void requestHideUniversalEntry(); + +protected: + virtual void init() = 0; + + bool m_initialized; + + QWidget *m_widgetParent; +}; + +#endif // IUNIVERSALENTRY_H diff --git a/src/resources/themes/v_moonlight/v_moonlight.palette b/src/resources/themes/v_moonlight/v_moonlight.palette index 7b7b9b07..06e194b4 100644 --- a/src/resources/themes/v_moonlight/v_moonlight.palette +++ b/src/resources/themes/v_moonlight/v_moonlight.palette @@ -115,6 +115,10 @@ template_title_flash_dark_fg=@master_bg search_hit_item_fg=@selected_fg search_hit_item_bg=@master_dark_bg +; Universal Entry CMD Edit border color +ue_cmd_busy_border=#3F51B5 +ue_cmd_fail_border=@danger_bg + [widgets] ; Widget color attributes. @@ -351,3 +355,7 @@ headerview_checked_bg=@selected_bg progressbar_bg=@edit_bg progressbar_border_bg=@border_bg progressbar_chunk_bg=@master_dark_bg + +universalentry_border_bg=@border_bg + +doublerowitem_second_row_label_fg=#6C6C6C diff --git a/src/resources/themes/v_moonlight/v_moonlight.qss b/src/resources/themes/v_moonlight/v_moonlight.qss index 84e13c30..b2f054e9 100644 --- a/src/resources/themes/v_moonlight/v_moonlight.qss +++ b/src/resources/themes/v_moonlight/v_moonlight.qss @@ -590,6 +590,15 @@ VTabIndicator QLabel[TabIndicatorLabel="true"] { background: transparent; } +VDoubleRowItemWidget QLabel[FirstRowLabel="true"] { + font-size: 10pt; +} + +VDoubleRowItemWidget QLabel[SecondRowLabel="true"] { + font-size: 8pt; + color: @doublerowitem_second_row_label_fg; +} + QLabel { border: none; color: @label_fg; @@ -1255,3 +1264,8 @@ QProgressBar::chunk { width: $20px; } /* End QProgressBar */ + +VUniversalEntry { + background: transparent; + border: 1px solid @universalentry_border_bg; +} diff --git a/src/resources/themes/v_native/v_native.palette b/src/resources/themes/v_native/v_native.palette index 7d951a31..a851ab94 100644 --- a/src/resources/themes/v_native/v_native.palette +++ b/src/resources/themes/v_native/v_native.palette @@ -83,6 +83,10 @@ template_title_flash_dark_fg=#00897B search_hit_item_fg=@selected_fg search_hit_item_bg=#80CBC4 +; Universal Entry CMD Edit border color +ue_cmd_busy_border=#3F51B5 +ue_cmd_fail_border=@danger_bg + [widgets] ; Widget color attributes. diff --git a/src/resources/themes/v_pure/v_pure.palette b/src/resources/themes/v_pure/v_pure.palette index 2d18967f..2728965f 100644 --- a/src/resources/themes/v_pure/v_pure.palette +++ b/src/resources/themes/v_pure/v_pure.palette @@ -109,6 +109,10 @@ template_title_flash_dark_fg=@master_bg search_hit_item_fg=@selected_fg search_hit_item_bg=#CCE7E4 +; Universal Entry CMD Edit border color +ue_cmd_busy_border=#3F51B5 +ue_cmd_fail_border=@danger_bg + [widgets] ; Widget color attributes. @@ -344,3 +348,7 @@ headerview_checked_bg=@selected_bg progressbar_bg=@edit_bg progressbar_border_bg=@border_bg progressbar_chunk_bg=@master_light_bg + +universalentry_border_bg=@border_bg + +doublerowitem_second_row_label_fg=#6C6C6C diff --git a/src/resources/themes/v_pure/v_pure.qss b/src/resources/themes/v_pure/v_pure.qss index 1a4605f2..dcbc20d4 100644 --- a/src/resources/themes/v_pure/v_pure.qss +++ b/src/resources/themes/v_pure/v_pure.qss @@ -590,6 +590,15 @@ VTabIndicator QLabel[TabIndicatorLabel="true"] { background: transparent; } +VDoubleRowItemWidget QLabel[FirstRowLabel="true"] { + font-size: 10pt; +} + +VDoubleRowItemWidget QLabel[SecondRowLabel="true"] { + font-size: 8pt; + color: @doublerowitem_second_row_label_fg; +} + QLabel { border: none; color: @label_fg; @@ -1254,3 +1263,8 @@ QProgressBar::chunk { width: $20px; } /* End QProgressBar */ + +VUniversalEntry { + background: transparent; + border: 1px solid @universalentry_border_bg; +} diff --git a/src/src.pro b/src/src.pro index 40472ed8..79377e0a 100644 --- a/src/src.pro +++ b/src/src.pro @@ -118,7 +118,10 @@ SOURCES += main.cpp\ vsearch.cpp \ vsearchresulttree.cpp \ vsearchengine.cpp \ - vuniversalentry.cpp + vuniversalentry.cpp \ + vlistwidgetdoublerows.cpp \ + vdoublerowitemwidget.cpp \ + vsearchue.cpp HEADERS += vmainwindow.h \ vdirectorytree.h \ @@ -226,7 +229,11 @@ HEADERS += vmainwindow.h \ isearchengine.h \ vsearchconfig.h \ vsearchengine.h \ - vuniversalentry.h + vuniversalentry.h \ + iuniversalentry.h \ + vlistwidgetdoublerows.h \ + vdoublerowitemwidget.h \ + vsearchue.h RESOURCES += \ vnote.qrc \ diff --git a/src/vdoublerowitemwidget.cpp b/src/vdoublerowitemwidget.cpp new file mode 100644 index 00000000..3ae9ad4b --- /dev/null +++ b/src/vdoublerowitemwidget.cpp @@ -0,0 +1,37 @@ +#include "vdoublerowitemwidget.h" + +#include +#include +#include + +VDoubleRowItemWidget::VDoubleRowItemWidget(QWidget *p_parent) + : QWidget(p_parent) +{ + m_firstLabel = new QLabel(this); + m_firstLabel->setProperty("FirstRowLabel", true); + + m_secondLabel = new QLabel(this); + m_secondLabel->setProperty("SecondRowLabel", true); + + QVBoxLayout *layout = new QVBoxLayout(); + layout->addWidget(m_firstLabel); + layout->addWidget(m_secondLabel); + layout->addStretch(); + layout->setContentsMargins(3, 0, 0, 0); + layout->setSpacing(0); + + setLayout(layout); +} + +void VDoubleRowItemWidget::setText(const QString &p_firstText, + const QString &p_secondText) +{ + m_firstLabel->setText(p_firstText); + + if (!p_secondText.isEmpty()) { + m_secondLabel->setText(p_secondText); + m_secondLabel->show(); + } else { + m_secondLabel->hide(); + } +} diff --git a/src/vdoublerowitemwidget.h b/src/vdoublerowitemwidget.h new file mode 100644 index 00000000..5bdad563 --- /dev/null +++ b/src/vdoublerowitemwidget.h @@ -0,0 +1,21 @@ +#ifndef VDOUBLEROWITEMWIDGET_H +#define VDOUBLEROWITEMWIDGET_H + +#include + +class QLabel; + +class VDoubleRowItemWidget : public QWidget +{ + Q_OBJECT +public: + explicit VDoubleRowItemWidget(QWidget *p_parent = nullptr); + + void setText(const QString &p_firstText, const QString &p_secondText); + +private: + QLabel *m_firstLabel; + QLabel *m_secondLabel; +}; + +#endif // VDOUBLEROWITEMWIDGET_H diff --git a/src/vlistwidget.cpp b/src/vlistwidget.cpp index c1b66cdb..d354ce3f 100644 --- a/src/vlistwidget.cpp +++ b/src/vlistwidget.cpp @@ -9,9 +9,10 @@ #include "utils/vimnavigationforwidget.h" #include "vstyleditemdelegate.h" -VListWidget::VListWidget(QWidget *parent) - : QListWidget(parent), - ISimpleSearch() +VListWidget::VListWidget(QWidget *p_parent) + : QListWidget(p_parent), + ISimpleSearch(), + m_fitContent(false) { m_searchInput = new VSimpleSearchInput(this, this); connect(m_searchInput, &VSimpleSearchInput::triggered, @@ -142,8 +143,19 @@ int VListWidget::totalNumberOfItems() void VListWidget::selectNextItem(bool p_forward) { - Q_UNUSED(p_forward); - Q_ASSERT(false); + if (count() == 0) { + return; + } + + int cur = currentRow(); + cur = cur + (p_forward ? 1 : -1); + if (cur < 0) { + cur = 0; + } else if (cur >= count()) { + cur = count() - 1; + } + + setCurrentRow(cur); } void VListWidget::sortListWidget(QListWidget *p_list, const QVector &p_sortedIdx) @@ -161,3 +173,34 @@ void VListWidget::sortListWidget(QListWidget *p_list, const QVector &p_sort p_list->insertItem(i, it); } } + +QSize VListWidget::sizeHint() const +{ + if (count() == 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) { + hei += sizeHintForRow(i); + } + + hei += 2 * cnt; + + // Scrollbar. + QScrollBar *verBar = verticalScrollBar(); + QScrollBar *horBar = horizontalScrollBar(); + if (verBar && (verBar->minimum() != verBar->maximum())) { + wid += verBar->width(); + } + + if (horBar && (horBar->minimum() != horBar->maximum())) { + hei += horBar->height(); + } + + return QSize(wid, hei); + } +} + diff --git a/src/vlistwidget.h b/src/vlistwidget.h index fbfbab95..e3770357 100644 --- a/src/vlistwidget.h +++ b/src/vlistwidget.h @@ -12,11 +12,11 @@ class VStyledItemDelegate; class VListWidget : public QListWidget, public ISimpleSearch { public: - explicit VListWidget(QWidget *parent = Q_NULLPTR); + explicit VListWidget(QWidget *p_parent = Q_NULLPTR); // Clear list widget as well as other data. // clear() is not virtual to override. - void clearAll(); + virtual void clearAll(); // Implement ISimpleSearch. virtual QList searchItems(const QString &p_text, @@ -32,9 +32,13 @@ public: virtual void selectNextItem(bool p_forward) Q_DECL_OVERRIDE; + QSize sizeHint() const Q_DECL_OVERRIDE; + // Sort @p_list according to @p_sortedIdx. static void sortListWidget(QListWidget *p_list, const QVector &p_sortedIdx); + void setFitContent(bool p_enabled); + private slots: void handleSearchModeTriggered(bool p_inSearchMode, bool p_focus); @@ -50,6 +54,13 @@ private: VSimpleSearchInput *m_searchInput; VStyledItemDelegate *m_delegate; + + // Whether fit the size to content. + bool m_fitContent; }; +inline void VListWidget::setFitContent(bool p_enabled) +{ + m_fitContent = p_enabled; +} #endif // VLISTWIDGET_H diff --git a/src/vlistwidgetdoublerows.cpp b/src/vlistwidgetdoublerows.cpp new file mode 100644 index 00000000..74bfb365 --- /dev/null +++ b/src/vlistwidgetdoublerows.cpp @@ -0,0 +1,55 @@ +#include "vlistwidgetdoublerows.h" + +#include +#include + +#include "vdoublerowitemwidget.h" + +VListWidgetDoubleRows::VListWidgetDoubleRows(QWidget *p_parent) + : VListWidget(p_parent) +{ +} + +QListWidgetItem *VListWidgetDoubleRows::addItem(const QIcon &p_icon, + const QString &p_firstRow, + const QString &p_secondRow) +{ + VDoubleRowItemWidget *itemWidget = new VDoubleRowItemWidget(this); + itemWidget->setText(p_firstRow, p_secondRow); + QSize sz = itemWidget->sizeHint(); + QSize iconSz = iconSize(); + if (!iconSz.isValid()) { + iconSz = QSize(sz.height(), sz.height()); + setIconSize(iconSz); + } + + sz.setHeight(sz.height() * 1.25); + + QListWidgetItem *item = new QListWidgetItem(); + if (!p_icon.isNull()) { + item->setIcon(p_icon); + sz.setWidth(sz.width() + iconSz.width()); + sz.setHeight(qMax(sz.height(), iconSz.height())); + } + + item->setSizeHint(sz); + + VListWidget::addItem(item); + VListWidget::setItemWidget(item, itemWidget); + return item; +} + +void VListWidgetDoubleRows::clearAll() +{ + // Delete the item widget for each item. + int cnt = count(); + for (int i = 0; i < cnt; ++i) { + QWidget *wid = itemWidget(item(i)); + removeItemWidget(item(i)); + delete wid; + } + + VListWidget::clearAll(); + + setIconSize(QSize()); +} diff --git a/src/vlistwidgetdoublerows.h b/src/vlistwidgetdoublerows.h new file mode 100644 index 00000000..01793e68 --- /dev/null +++ b/src/vlistwidgetdoublerows.h @@ -0,0 +1,24 @@ +#ifndef VLISTWIDGETDOUBLEROWS_H +#define VLISTWIDGETDOUBLEROWS_H + +#include "vlistwidget.h" + +#include + +class QListWidgetItem; + + +class VListWidgetDoubleRows : public VListWidget +{ + Q_OBJECT +public: + explicit VListWidgetDoubleRows(QWidget *p_parent = nullptr); + + QListWidgetItem *addItem(const QIcon &p_icon, + const QString &p_firstRow, + const QString &p_secondRow); + + void clearAll() Q_DECL_OVERRIDE; +}; + +#endif // VLISTWIDGETDOUBLEROWS_H diff --git a/src/vmainwindow.cpp b/src/vmainwindow.cpp index 12ee1953..4cf6c4e5 100644 --- a/src/vmainwindow.cpp +++ b/src/vmainwindow.cpp @@ -40,6 +40,7 @@ #include "dialog/vexportdialog.h" #include "vsearcher.h" #include "vuniversalentry.h" +#include "vsearchue.h" extern VConfigManager *g_config; @@ -3164,15 +3165,7 @@ VNotebook *VMainWindow::getCurrentNotebook() const void VMainWindow::activateUniversalEntry() { if (!m_ue) { - m_ue = new VUniversalEntry(this); - m_ue->hide(); - m_ue->setWindowFlags(Qt::Popup - | Qt::FramelessWindowHint - | Qt::NoDropShadowWindowHint); - connect(m_ue, &VUniversalEntry::exited, - this, [this]() { - m_captain->setCaptainModeEnabled(true); - }); + initUniversalEntry(); } m_captain->setCaptainModeEnabled(false); @@ -3191,3 +3184,21 @@ void VMainWindow::activateUniversalEntry() m_ue->show(); m_ue->raise(); } + +void VMainWindow::initUniversalEntry() +{ + m_ue = new VUniversalEntry(this); + m_ue->hide(); + m_ue->setWindowFlags(Qt::Popup + | Qt::FramelessWindowHint + | Qt::NoDropShadowWindowHint); + connect(m_ue, &VUniversalEntry::exited, + this, [this]() { + m_captain->setCaptainModeEnabled(true); + }); + + // Register entries. + VSearchUE *searchUE = new VSearchUE(this); + m_ue->registerEntry('q', searchUE, VSearchUE::Name_Notebook_AllNotebook); + m_ue->registerEntry('a', searchUE, VSearchUE::Name_FolderNote_AllNotebook); +} diff --git a/src/vmainwindow.h b/src/vmainwindow.h index 0b4ed9c5..caccee59 100644 --- a/src/vmainwindow.h +++ b/src/vmainwindow.h @@ -282,6 +282,8 @@ private: void updateEditReadAct(const VEditTab *p_tab); + void initUniversalEntry(); + // Captain mode functions. // Popup the attachment list if it is enabled. diff --git a/src/vmdeditoperations.cpp b/src/vmdeditoperations.cpp index 12b4c1c5..6238d7a9 100644 --- a/src/vmdeditoperations.cpp +++ b/src/vmdeditoperations.cpp @@ -382,6 +382,7 @@ bool VMdEditOperations::handleKeyPressEvent(QKeyEvent *p_event) case Qt::Key_Enter: // Fall through. + V_FALLTHROUGH; case Qt::Key_Return: { if (handleKeyReturn(p_event)) { diff --git a/src/vmetawordlineedit.cpp b/src/vmetawordlineedit.cpp index 39d2353a..5d400469 100644 --- a/src/vmetawordlineedit.cpp +++ b/src/vmetawordlineedit.cpp @@ -45,3 +45,8 @@ const QString &VMetaWordLineEdit::getEvaluatedText() const { return m_evaluatedText; } + +QString VMetaWordLineEdit::evaluateText(const QString &p_text) const +{ + return g_mwMgr->evaluate(p_text); +} diff --git a/src/vmetawordlineedit.h b/src/vmetawordlineedit.h index 17352970..f192c7fc 100644 --- a/src/vmetawordlineedit.h +++ b/src/vmetawordlineedit.h @@ -16,6 +16,8 @@ public: // Return the evaluated text. const QString &getEvaluatedText() const; + QString evaluateText(const QString &p_text) const; + private slots: void handleTextChanged(const QString &p_text); diff --git a/src/vsearchue.cpp b/src/vsearchue.cpp new file mode 100644 index 00000000..3055fdb3 --- /dev/null +++ b/src/vsearchue.cpp @@ -0,0 +1,384 @@ +#include "vsearchue.h" + +#include +#include + +#include "vlistwidgetdoublerows.h" +#include "vnotebook.h" +#include "vnote.h" +#include "vsearch.h" +#include "utils/viconutils.h" +#include "vmainwindow.h" +#include "vnotebookselector.h" +#include "vnotefile.h" + +extern VNote *g_vnote; + +extern VMainWindow *g_mainWin; + +VSearchUE::VSearchUE(QObject *p_parent) + : IUniversalEntry(p_parent), + m_search(NULL), + m_inSearch(false), + m_listWidget(NULL), + m_id(ID::Name_Notebook_AllNotebook) +{ +} + +QString VSearchUE::description(int p_id) const +{ + switch (p_id) { + case ID::Name_Notebook_AllNotebook: + return tr("List and search all notebooks"); + + case ID::Name_FolderNote_AllNotebook: + return tr("Search the name of folders/notes in all notebooks"); + + default: + Q_ASSERT(false); + return tr("Invalid ID %1").arg(p_id); + } +} + +void VSearchUE::init() +{ + if (m_initialized) { + return; + } + + Q_ASSERT(m_widgetParent); + + m_initialized = true; + + m_search = new VSearch(this); + connect(m_search, &VSearch::resultItemAdded, + this, &VSearchUE::handleSearchItemAdded); + connect(m_search, &VSearch::finished, + this, &VSearchUE::handleSearchFinished); + + m_noteIcon = VIconUtils::treeViewIcon(":/resources/icons/note_item.svg"); + m_folderIcon = VIconUtils::treeViewIcon(":/resources/icons/dir_item.svg"); + m_notebookIcon = VIconUtils::treeViewIcon(":/resources/icons/notebook_item.svg"); + + m_listWidget = new VListWidgetDoubleRows(m_widgetParent); + m_listWidget->setFitContent(true); + m_listWidget->hide(); + connect(m_listWidget, &VListWidgetDoubleRows::itemActivated, + this, &VSearchUE::activateItem); +} + +QWidget *VSearchUE::widget(int p_id) +{ + init(); + + switch (p_id) { + case ID::Name_Notebook_AllNotebook: + V_FALLTHROUGH; + case ID::Name_FolderNote_AllNotebook: + return m_listWidget; + + default: + Q_ASSERT(false); + return NULL; + } +} + +void VSearchUE::processCommand(int p_id, const QString &p_cmd) +{ + qDebug() << "processCommand" << p_id << p_cmd; + + init(); + + clear(-1); + + m_inSearch = true; + m_id = p_id; + emit stateUpdated(State::Busy); + switch (p_id) { + case ID::Name_Notebook_AllNotebook: + searchNameOfAllNotebooks(p_cmd); + break; + + case ID::Name_FolderNote_AllNotebook: + searchNameOfFolderNoteInAllNotebooks(p_cmd); + break; + + default: + Q_ASSERT(false); + break; + } + + widget(p_id)->updateGeometry(); + emit widgetUpdated(); +} + +void VSearchUE::searchNameOfAllNotebooks(const QString &p_cmd) +{ + const QVector ¬ebooks = g_vnote->getNotebooks(); + if (p_cmd.isEmpty()) { + // List all the notebooks. + for (auto const & nb : notebooks) { + QSharedPointer item(new VSearchResultItem(VSearchResultItem::Notebook, + VSearchResultItem::LineNumber, + nb->getName(), + nb->getPath())); + handleSearchItemAdded(item); + } + + m_inSearch = false; + emit stateUpdated(State::Success); + } else { + // Do a fuzzy search against the name of the notebooks. + VSearchConfig::Option opt = VSearchConfig::Fuzzy; + QSharedPointer config(new VSearchConfig(VSearchConfig::AllNotebooks, + VSearchConfig::Name, + VSearchConfig::Notebook, + VSearchConfig::Internal, + opt, + p_cmd, + QString())); + m_search->setConfig(config); + + QSharedPointer result = m_search->search(notebooks); + handleSearchFinished(result); + } +} + +void VSearchUE::searchNameOfFolderNoteInAllNotebooks(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::Name, + VSearchConfig::Folder | 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); + stopSearch(); + + m_data.clear(); + m_listWidget->clearAll(); +} + +void VSearchUE::entryHidden(int p_id) +{ + Q_UNUSED(p_id); +} + +void VSearchUE::handleSearchItemAdded(const QSharedPointer &p_item) +{ + switch (m_id) { + case ID::Name_Notebook_AllNotebook: + V_FALLTHROUGH; + case ID::Name_FolderNote_AllNotebook: + appendItemToList(p_item); + break; + + default: + break; + } +} + +void VSearchUE::appendItemToList(const QSharedPointer &p_item) +{ + static int itemAdded = 0; + m_data.append(p_item); + + QString first, second; + if (p_item->m_text.isEmpty()) { + first = p_item->m_path; + } else { + first = p_item->m_text; + second = p_item->m_path; + } + + QIcon *icon = NULL; + switch (p_item->m_type) { + case VSearchResultItem::Note: + icon = &m_noteIcon; + break; + + case VSearchResultItem::Folder: + icon = &m_folderIcon; + break; + + case VSearchResultItem::Notebook: + icon = &m_notebookIcon; + break; + + default: + break; + } + + QListWidgetItem *item = m_listWidget->addItem(*icon, first, second); + item->setData(Qt::UserRole, m_data.size() - 1); + item->setToolTip(p_item->m_path); + + if (m_listWidget->currentRow() == -1) { + m_listWidget->setCurrentRow(0); + } + + if (++itemAdded >= 10) { + itemAdded = 0; + m_listWidget->updateGeometry(); + emit widgetUpdated(); + } +} + +void VSearchUE::handleSearchFinished(const QSharedPointer &p_result) +{ + Q_ASSERT(m_inSearch); + Q_ASSERT(p_result->m_state != VSearchState::Idle); + + qDebug() << "handleSearchFinished" << (int)p_result->m_state; + + IUniversalEntry::State state = State::Idle; + + switch (p_result->m_state) { + case VSearchState::Busy: + qDebug() << "search is ongoing"; + state = State::Busy; + return; + + case VSearchState::Success: + qDebug() << "search succeeded"; + state = State::Success; + break; + + case VSearchState::Fail: + qDebug() << "search failed"; + state = State::Fail; + break; + + case VSearchState::Cancelled: + qDebug() << "search cancelled"; + state = State::Cancelled; + break; + + default: + break; + } + + m_search->clear(); + m_inSearch = false; + + widget(m_id)->updateGeometry(); + emit widgetUpdated(); + + emit stateUpdated(state); +} + +void VSearchUE::stopSearch() +{ + if (m_inSearch) { + m_search->stop(); + + while (m_inSearch) { + VUtils::sleepWait(100); + qDebug() << "sleep wait for search to stop"; + } + } +} + +const QSharedPointer &VSearchUE::itemResultData(const QListWidgetItem *p_item) const +{ + Q_ASSERT(p_item); + int idx = p_item->data(Qt::UserRole).toInt(); + Q_ASSERT(idx >= 0 && idx < m_data.size()); + return m_data[idx]; +} + +void VSearchUE::activateItem(const QListWidgetItem *p_item) +{ + if (!p_item) { + return; + } + + emit requestHideUniversalEntry(); + + const QSharedPointer &resItem = itemResultData(p_item); + switch (resItem->m_type) { + case VSearchResultItem::Note: + { + QStringList files(resItem->m_path); + g_mainWin->openFiles(files); + break; + } + + case VSearchResultItem::Folder: + { + VDirectory *dir = g_vnote->getInternalDirectory(resItem->m_path); + if (dir) { + g_mainWin->locateDirectory(dir); + } + + break; + } + + case VSearchResultItem::Notebook: + { + VNotebook *nb = g_vnote->getNotebook(resItem->m_path); + if (nb) { + g_mainWin->getNotebookSelector()->locateNotebook(nb); + } + + break; + } + + default: + break; + } +} + +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. + m_listWidget->selectNextItem(p_forward); + break; + } + + default: + Q_ASSERT(false); + } +} + +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; + } + + default: + Q_ASSERT(false); + } +} + +void VSearchUE::askToStop(int p_id) +{ + Q_UNUSED(p_id); + m_search->stop(); +} diff --git a/src/vsearchue.h b/src/vsearchue.h new file mode 100644 index 00000000..0b75e76e --- /dev/null +++ b/src/vsearchue.h @@ -0,0 +1,85 @@ +#ifndef VSEARCHUE_H +#define VSEARCHUE_H + + +#include "iuniversalentry.h" + +#include + +#include "vsearchconfig.h" + +class VListWidgetDoubleRows; +class QListWidgetItem; + + +// Universal Entry to list and search all the notebooks. +class VSearchUE : public IUniversalEntry +{ + Q_OBJECT +public: + enum ID + { + // List and search the name of all notebooks. + Name_Notebook_AllNotebook = 0, + + // Search the name of the folder/note in all the notebooks. + Name_FolderNote_AllNotebook + }; + + explicit VSearchUE(QObject *p_parent = nullptr); + + QString description(int p_id) const Q_DECL_OVERRIDE; + + QWidget *widget(int p_id) Q_DECL_OVERRIDE; + + void processCommand(int p_id, const QString &p_cmd) Q_DECL_OVERRIDE; + + void clear(int p_id) Q_DECL_OVERRIDE; + + void entryHidden(int p_id) Q_DECL_OVERRIDE; + + void selectNextItem(int p_id, bool p_forward) Q_DECL_OVERRIDE; + + void activate(int p_id) Q_DECL_OVERRIDE; + + void askToStop(int p_id) Q_DECL_OVERRIDE; + +protected: + void init() Q_DECL_OVERRIDE; + +private slots: + void handleSearchItemAdded(const QSharedPointer &p_item); + + void handleSearchFinished(const QSharedPointer &p_result); + +private: + void searchNameOfAllNotebooks(const QString &p_cmd); + + void searchNameOfFolderNoteInAllNotebooks(const QString &p_cmd); + + // Stop the search synchronously. + void stopSearch(); + + void appendItemToList(const QSharedPointer &p_item); + + void activateItem(const QListWidgetItem *p_item); + + const QSharedPointer &itemResultData(const QListWidgetItem *p_item) const; + + VSearch *m_search; + + bool m_inSearch; + + // Current instance ID. + int m_id; + + QVector > m_data; + + QIcon m_noteIcon; + QIcon m_folderIcon; + QIcon m_notebookIcon; + + VListWidgetDoubleRows *m_listWidget; +}; + +#endif // VSEARCHUE_H diff --git a/src/vuniversalentry.cpp b/src/vuniversalentry.cpp index 689c1763..7587d310 100644 --- a/src/vuniversalentry.cpp +++ b/src/vuniversalentry.cpp @@ -5,31 +5,119 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include -#include "vlineedit.h" +#include "vmetawordlineedit.h" #include "utils/vutils.h" +#include "vlistwidget.h" +#include "vpalette.h" #define MINIMUM_WIDTH 200 +#define CMD_EDIT_INTERVAL 500 + +extern VPalette *g_palette; + +VUniversalEntryContainer::VUniversalEntryContainer(QWidget *p_parent) + : QWidget(p_parent), + m_widget(NULL) +{ + m_layout = new QVBoxLayout(); + m_layout->setContentsMargins(0, 0, 0, 0); + + setLayout(m_layout); +} + +void VUniversalEntryContainer::clear() +{ + if (m_widget) { + m_layout->removeWidget(m_widget); + m_widget->hide(); + m_widget = NULL; + } + + adjustSizeByWidget(); +} + +void VUniversalEntryContainer::setWidget(QWidget *p_widget) +{ + if (m_widget != p_widget) { + clear(); + m_widget = p_widget; + m_layout->addWidget(m_widget); + m_widget->show(); + } + + adjustSizeByWidget(); +} + +void VUniversalEntryContainer::adjustSizeByWidget() +{ + updateGeometry(); +} + +QSize VUniversalEntryContainer::sizeHint() const +{ + if (m_widget) { + return m_widget->sizeHint(); + } else { + return QWidget::sizeHint(); + } +} + + VUniversalEntry::VUniversalEntry(QWidget *p_parent) : QWidget(p_parent), - m_availableRect(0, 0, MINIMUM_WIDTH, MINIMUM_WIDTH) + m_availableRect(0, 0, MINIMUM_WIDTH, MINIMUM_WIDTH), + m_lastEntry(NULL), + m_inQueue(0), + m_pendingCommand(false) { m_minimumWidth = MINIMUM_WIDTH * VUtils::calculateScaleFactor() + 0.5; + + m_cmdTimer = new QTimer(this); + m_cmdTimer->setSingleShot(true); + m_cmdTimer->setInterval(CMD_EDIT_INTERVAL); + connect(m_cmdTimer, &QTimer::timeout, + this, [this]() { + processCommand(); + }); + setupUI(); + + m_infoWidget = new VListWidget(this); + m_infoWidget->setFitContent(true); + m_container->setWidget(m_infoWidget); + + m_cmdStyleSheet = m_cmdEdit->styleSheet(); } void VUniversalEntry::setupUI() { - m_cmdEdit = new VLineEdit(this); + m_cmdEdit = new VMetaWordLineEdit(this); m_cmdEdit->setPlaceholderText(tr("Welcome to Universal Entry")); + connect(m_cmdEdit, &VMetaWordLineEdit::textEdited, + this, [this]() { + m_cmdTimer->stop(); + m_cmdTimer->start(); + }); - m_layout = new QVBoxLayout(); - m_layout->addWidget(m_cmdEdit); + m_container = new VUniversalEntryContainer(this); - m_layout->setContentsMargins(0, 0, 0, 0); + QVBoxLayout *mainLayout = new QVBoxLayout(); + mainLayout->addWidget(m_cmdEdit); + mainLayout->addWidget(m_container); - setLayout(m_layout); + mainLayout->setContentsMargins(1, 1, 1, 1); + mainLayout->setSpacing(0); + + setLayout(mainLayout); setMinimumWidth(m_minimumWidth); } @@ -37,6 +125,10 @@ void VUniversalEntry::setupUI() void VUniversalEntry::hideEvent(QHideEvent *p_event) { QWidget::hideEvent(p_event); + if (m_lastEntry) { + m_lastEntry->m_entry->entryHidden(m_lastEntry->m_id); + } + emit exited(); } @@ -45,7 +137,6 @@ void VUniversalEntry::showEvent(QShowEvent *p_event) QWidget::showEvent(p_event); m_cmdEdit->setFocus(); - m_cmdEdit->selectAll(); } void VUniversalEntry::setAvailableRect(const QRect &p_rect) @@ -55,4 +146,193 @@ void VUniversalEntry::setAvailableRect(const QRect &p_rect) if (m_availableRect.width() < m_minimumWidth) { m_availableRect.setWidth(m_minimumWidth); } + + setMaximumSize(m_availableRect.size()); +} + +void VUniversalEntry::registerEntry(QChar p_key, IUniversalEntry *p_entry, int p_id) +{ + Q_ASSERT(!m_entries.contains(p_key)); + + p_entry->setParent(this); + p_entry->setWidgetParent(this); + connect(p_entry, &IUniversalEntry::widgetUpdated, + this, [this]() { + m_container->adjustSizeByWidget(); + adjustSize(); + }); + connect(p_entry, &IUniversalEntry::stateUpdated, + this, &VUniversalEntry::updateState); + connect(p_entry, &IUniversalEntry::requestHideUniversalEntry, + this, &VUniversalEntry::hide); + + m_entries.insert(p_key, Entry(p_entry, p_id)); + m_infoWidget->addItem(QString("%1: %2").arg(p_key) + .arg(p_entry->description(p_id))); + m_infoWidget->updateGeometry(); +} + +void VUniversalEntry::processCommand() +{ + if (!m_inQueue.testAndSetRelaxed(0, 1)) { + // There is already a job in queue. + qDebug() << "already a job in queue, pend a new job and ask to stop"; + m_pendingCommand = true; + if (m_lastEntry) { + m_lastEntry->m_entry->askToStop(m_lastEntry->m_id); + } + + return; + } + +redo: + QString cmd = m_cmdEdit->getEvaluatedText(); + processCommand(cmd); + if (m_pendingCommand && cmd != m_cmdEdit->getEvaluatedText()) { + // Handle pending job. + qDebug() << "handle pending job" << cmd; + m_pendingCommand = false; + goto redo; + } + + m_inQueue.store(0); +} + +void VUniversalEntry::processCommand(const QString &p_cmd) +{ + if (p_cmd.isEmpty()) { + clear(); + return; + } + + auto it = m_entries.find(p_cmd[0]); + if (it == m_entries.end()) { + clear(); + return; + } + + const Entry &entry = it.value(); + if (m_lastEntry && m_lastEntry != &entry) { + m_lastEntry->m_entry->clear(m_lastEntry->m_id); + } + + m_lastEntry = &entry; + m_container->setWidget(entry.m_entry->widget(entry.m_id)); + adjustSize(); + entry.m_entry->processCommand(entry.m_id, p_cmd.mid(1)); +} + +void VUniversalEntry::clear() +{ + if (m_lastEntry) { + m_lastEntry->m_entry->clear(m_lastEntry->m_id); + m_lastEntry = NULL; + } + + m_container->setWidget(m_infoWidget); + adjustSize(); +} + +void VUniversalEntry::keyPressEvent(QKeyEvent *p_event) +{ + int modifiers = p_event->modifiers(); + bool forward = true; + switch (p_event->key()) { + case Qt::Key_BracketLeft: + if (VUtils::isControlModifierForVim(modifiers)) { + // Hide. + hide(); + return; + } + + break; + + // Up/Down Ctrl+K/J to navigate to next item. + case Qt::Key_Up: + forward = false; + V_FALLTHROUGH; + case Qt::Key_Down: + if (m_lastEntry) { + m_lastEntry->m_entry->selectNextItem(m_lastEntry->m_id, forward); + return; + } + + break; + + case Qt::Key_K: + forward = false; + V_FALLTHROUGH; + case Qt::Key_J: + if (m_lastEntry && VUtils::isControlModifierForVim(modifiers)) { + m_lastEntry->m_entry->selectNextItem(m_lastEntry->m_id, forward); + return; + } + + break; + + case Qt::Key_Enter: + // Fall through. + V_FALLTHROUGH; + case Qt::Key_Return: + { + if (m_lastEntry) { + m_lastEntry->m_entry->activate(m_lastEntry->m_id); + return; + } + + break; + } + + case Qt::Key_E: + if (VUtils::isControlModifierForVim(modifiers)) { + // Ctrl+E to eliminate input except the command key. + QString cmd = m_cmdEdit->getEvaluatedText(); + if (!cmd.isEmpty()) { + m_cmdEdit->setText(cmd.left(1)); + m_cmdTimer->stop(); + processCommand(); + return; + } + } + + break; + + default: + break; + } + + QWidget::keyPressEvent(p_event); +} + +void VUniversalEntry::paintEvent(QPaintEvent *p_event) +{ + QStyleOption opt; + opt.init(this); + QPainter p(this); + style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); + + QWidget::paintEvent(p_event); +} + +void VUniversalEntry::updateState(IUniversalEntry::State p_state) +{ + QString fg; + switch (p_state) { + case IUniversalEntry::Busy: + fg = g_palette->color("ue_cmd_busy_border"); + break; + + case IUniversalEntry::Fail: + fg = g_palette->color("ue_cmd_fail_border"); + break; + + default: + break; + } + + if (fg.isEmpty()) { + m_cmdEdit->setStyleSheet(m_cmdStyleSheet); + } else { + m_cmdEdit->setStyleSheet(QString("border-color: %1;").arg(fg)); + } } diff --git a/src/vuniversalentry.h b/src/vuniversalentry.h index 55d60b65..517c1a19 100644 --- a/src/vuniversalentry.h +++ b/src/vuniversalentry.h @@ -3,11 +3,41 @@ #include #include +#include +#include -class VLineEdit; +#include "iuniversalentry.h" + +class VMetaWordLineEdit; class QVBoxLayout; class QHideEvent; class QShowEvent; +class QPaintEvent; +class QKeyEvent; +class QTimer; +class VListWidget; + +class VUniversalEntryContainer : public QWidget +{ + Q_OBJECT +public: + VUniversalEntryContainer(QWidget *p_parent = nullptr); + + void clear(); + + void setWidget(QWidget *p_widget); + + void adjustSizeByWidget(); + +protected: + QSize sizeHint() const Q_DECL_OVERRIDE; + +private: + QWidget *m_widget; + + QVBoxLayout *m_layout; +}; + class VUniversalEntry : public QWidget { @@ -18,6 +48,11 @@ public: // Set the availabel width and height. void setAvailableRect(const QRect &p_rect); + // Register an entry at @p_key. + // Different entries may use the same @p_entry, in which case they can use + // @p_id to distinguish. + void registerEntry(QChar p_key, IUniversalEntry *p_entry, int p_id = 0); + signals: // Exit Universal Entry. void exited(); @@ -27,17 +62,62 @@ protected: void showEvent(QShowEvent *p_event) Q_DECL_OVERRIDE; + void keyPressEvent(QKeyEvent *p_event) Q_DECL_OVERRIDE; + + void paintEvent(QPaintEvent *p_event) Q_DECL_OVERRIDE; + private: + struct Entry + { + Entry() + : m_entry(NULL), m_id(-1) + { + } + + Entry(IUniversalEntry *p_entry, int p_id) + : m_entry(p_entry), m_id(p_id) + { + } + + IUniversalEntry *m_entry; + int m_id; + }; + void setupUI(); - VLineEdit *m_cmdEdit; + void clear(); - QVBoxLayout *m_layout; + void processCommand(); + + void processCommand(const QString &p_cmd); + + void updateState(IUniversalEntry::State p_state); + + VMetaWordLineEdit *m_cmdEdit; + + VUniversalEntryContainer *m_container; // Rect availabel for the UE to use. QRect m_availableRect; int m_minimumWidth; + + QHash m_entries; + + // Widget to list all entries. + VListWidget *m_infoWidget; + + QTimer *m_cmdTimer; + + // Last used Entry. + const Entry *m_lastEntry; + + // The CMD edit's original style sheet. + QString m_cmdStyleSheet; + + QAtomicInt m_inQueue; + + bool m_pendingCommand; }; #endif // VUNIVERSALENTRY_H