Browse Source

UniversalEntry: add q to list all notebooks and a to search name of folder/note

Le Tan 7 years ago
parent
commit
b46a8f4f39

+ 73 - 0
src/iuniversalentry.h

@@ -0,0 +1,73 @@
+#ifndef IUNIVERSALENTRY_H
+#define IUNIVERSALENTRY_H
+
+#include <QObject>
+#include <QString>
+
+// 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

+ 8 - 0
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

+ 14 - 0
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;
+}

+ 4 - 0
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.
 

+ 8 - 0
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

+ 14 - 0
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;
+}

+ 9 - 2
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 \

+ 37 - 0
src/vdoublerowitemwidget.cpp

@@ -0,0 +1,37 @@
+#include "vdoublerowitemwidget.h"
+
+#include <QVariant>
+#include <QLabel>
+#include <QVBoxLayout>
+
+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();
+    }
+}

+ 21 - 0
src/vdoublerowitemwidget.h

@@ -0,0 +1,21 @@
+#ifndef VDOUBLEROWITEMWIDGET_H
+#define VDOUBLEROWITEMWIDGET_H
+
+#include <QWidget>
+
+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

+ 48 - 5
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<int> &p_sortedIdx)
@@ -161,3 +173,34 @@ void VListWidget::sortListWidget(QListWidget *p_list, const QVector<int> &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);
+    }
+}
+

+ 13 - 2
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<void *> 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<int> &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

+ 55 - 0
src/vlistwidgetdoublerows.cpp

@@ -0,0 +1,55 @@
+#include "vlistwidgetdoublerows.h"
+
+#include <QListWidgetItem>
+#include <QScrollBar>
+
+#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());
+}

+ 24 - 0
src/vlistwidgetdoublerows.h

@@ -0,0 +1,24 @@
+#ifndef VLISTWIDGETDOUBLEROWS_H
+#define VLISTWIDGETDOUBLEROWS_H
+
+#include "vlistwidget.h"
+
+#include <QIcon>
+
+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

+ 20 - 9
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);
+}

+ 2 - 0
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.

+ 1 - 0
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)) {

+ 5 - 0
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);
+}

+ 2 - 0
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);
 

+ 384 - 0
src/vsearchue.cpp

@@ -0,0 +1,384 @@
+#include "vsearchue.h"
+
+#include <QDebug>
+#include <QVector>
+
+#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<VNotebook *> &notebooks = g_vnote->getNotebooks();
+    if (p_cmd.isEmpty()) {
+        // List all the notebooks.
+        for (auto const & nb : notebooks) {
+            QSharedPointer<VSearchResultItem> 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<VSearchConfig> config(new VSearchConfig(VSearchConfig::AllNotebooks,
+                                                               VSearchConfig::Name,
+                                                               VSearchConfig::Notebook,
+                                                               VSearchConfig::Internal,
+                                                               opt,
+                                                               p_cmd,
+                                                               QString()));
+        m_search->setConfig(config);
+
+        QSharedPointer<VSearchResult> result = m_search->search(notebooks);
+        handleSearchFinished(result);
+    }
+}
+
+void VSearchUE::searchNameOfFolderNoteInAllNotebooks(const QString &p_cmd)
+{
+    const QVector<VNotebook *> &notebooks = g_vnote->getNotebooks();
+    if (p_cmd.isEmpty()) {
+        m_inSearch = false;
+        emit stateUpdated(State::Success);
+    } else {
+        VSearchConfig::Option opt = VSearchConfig::NoneOption;
+        QSharedPointer<VSearchConfig> config(new VSearchConfig(VSearchConfig::AllNotebooks,
+                                                               VSearchConfig::Name,
+                                                               VSearchConfig::Folder | VSearchConfig::Note,
+                                                               VSearchConfig::Internal,
+                                                               opt,
+                                                               p_cmd,
+                                                               QString()));
+        m_search->setConfig(config);
+        QSharedPointer<VSearchResult> 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<VSearchResultItem> &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<VSearchResultItem> &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<VSearchResult> &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<VSearchResultItem> &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<VSearchResultItem> &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();
+}

+ 85 - 0
src/vsearchue.h

@@ -0,0 +1,85 @@
+#ifndef VSEARCHUE_H
+#define VSEARCHUE_H
+
+
+#include "iuniversalentry.h"
+
+#include <QIcon>
+
+#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<VSearchResultItem> &p_item);
+
+    void handleSearchFinished(const QSharedPointer<VSearchResult> &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<VSearchResultItem> &p_item);
+
+    void activateItem(const QListWidgetItem *p_item);
+
+    const QSharedPointer<VSearchResultItem> &itemResultData(const QListWidgetItem *p_item) const;
+
+    VSearch *m_search;
+
+    bool m_inSearch;
+
+    // Current instance ID.
+    int m_id;
+
+    QVector<QSharedPointer<VSearchResultItem> > m_data;
+
+    QIcon m_noteIcon;
+    QIcon m_folderIcon;
+    QIcon m_notebookIcon;
+
+    VListWidgetDoubleRows *m_listWidget;
+};
+
+#endif // VSEARCHUE_H

+ 288 - 8
src/vuniversalentry.cpp

@@ -5,31 +5,119 @@
 #include <QDebug>
 #include <QHideEvent>
 #include <QShowEvent>
+#include <QPaintEvent>
+#include <QKeyEvent>
+#include <QListWidgetItem>
+#include <QStyleOption>
+#include <QPainter>
+#include <QTimer>
+#include <QAtomicInt>
 
-#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);
+
+    mainLayout->setContentsMargins(1, 1, 1, 1);
+    mainLayout->setSpacing(0);
 
-	setLayout(m_layout);
+    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));
+    }
 }

+ 83 - 3
src/vuniversalentry.h

@@ -3,11 +3,41 @@
 
 #include <QWidget>
 #include <QRect>
+#include <QHash>
+#include <QAtomicInt>
 
-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<QChar, Entry> 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