Kaynağa Gözat

refactor VListWidget

Le Tan 7 yıl önce
ebeveyn
işleme
0f6e9e1905

+ 5 - 1
src/resources/themes/v_moonlight/v_moonlight.palette

@@ -6,7 +6,7 @@ qss_file=v_moonlight.qss
 mdhl_file=v_moonlight.mdhl
 css_file=v_moonlight.css
 codeblock_css_file=v_moonlight_codeblock.css
-version=1
+version=2
 
 ; This mapping will be used to translate colors when the content of HTML is copied
 ; without background. You could just specify the foreground colors mapping here.
@@ -108,6 +108,10 @@ tab_indicator_label_fg=@base_fg
 template_title_flash_light_fg=@master_light_bg
 template_title_flash_dark_fg=@master_bg
 
+; Search hit items in list or tree view.
+search_hit_item_fg=@selected_fg
+search_hit_item_bg=@master_dark_bg
+
 [widgets]
 ; Widget color attributes.
 

+ 5 - 1
src/resources/themes/v_pure/v_pure.palette

@@ -6,7 +6,7 @@ qss_file=v_pure.qss
 mdhl_file=v_pure.mdhl
 css_file=v_pure.css
 codeblock_css_file=v_pure_codeblock.css
-version=1
+version=2
 
 [phony]
 ; Abstract color attributes.
@@ -102,6 +102,10 @@ tab_indicator_label_fg=@base_fg
 template_title_flash_light_fg=@master_light_bg
 template_title_flash_dark_fg=@master_bg
 
+; Search hit items in list or tree view.
+search_hit_item_fg=@selected_fg
+search_hit_item_bg=@master_light_bg
+
 [widgets]
 ; Widget color attributes.
 

+ 5 - 1
src/resources/themes/v_white/v_white.palette

@@ -6,7 +6,7 @@ qss_file=v_white.qss
 mdhl_file=v_white.mdhl
 css_file=v_white.css
 codeblock_css_file=v_white_codeblock.css
-version=1
+version=2
 
 [phony]
 ; Abstract color attributes.
@@ -90,6 +90,10 @@ tab_indicator_label_fg=@base_fg
 template_title_flash_light_fg=#80CBC4
 template_title_flash_dark_fg=#00897B
 
+; Search hit items in list or tree view.
+search_hit_item_fg=@selected_fg
+search_hit_item_bg=#80CBC4
+
 [widgets]
 ; Widget color attributes.
 

+ 3 - 0
src/resources/vnote.ini

@@ -195,6 +195,9 @@ custom_colors=White:#FFFFFF,LightGrey:#EEEEEE
 ; Single click to open a file then close previous tab
 single_click_close_previous_tab=true
 
+; Whether enable auto wildcard match in simple search like list and tree widgets
+enable_wildcard_in_simple_search=true
+
 [web]
 ; Location and configuration for Mathjax
 mathjax_javascript=https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/MathJax.js?config=TeX-MML-AM_CHTML

+ 6 - 2
src/src.pro

@@ -109,7 +109,9 @@ SOURCES += main.cpp\
     vlineedit.cpp \
     vcart.cpp \
     vvimcmdlineedit.cpp \
-    vlistwidget.cpp
+    vlistwidget.cpp \
+    vsimplesearchinput.cpp \
+    vstyleditemdelegate.cpp
 
 HEADERS  += vmainwindow.h \
     vdirectorytree.h \
@@ -205,7 +207,9 @@ HEADERS  += vmainwindow.h \
     vlineedit.h \
     vcart.h \
     vvimcmdlineedit.h \
-    vlistwidget.h
+    vlistwidget.h \
+    vsimplesearchinput.h \
+    vstyleditemdelegate.h
 
 RESOURCES += \
     vnote.qrc \

+ 2 - 2
src/utils/viconutils.h

@@ -17,9 +17,9 @@ public:
                       const QString &p_fg = QString(),
                       bool p_addDisabled = true);
 
-    static QIcon toolButtonIcon(const QString &p_file)
+    static QIcon toolButtonIcon(const QString &p_file, bool p_addDisabled = true)
     {
-        return icon(p_file, g_palette->color("toolbutton_icon_fg"));
+        return icon(p_file, g_palette->color("toolbutton_icon_fg"), p_addDisabled);
     }
 
     static QIcon toolButtonDangerIcon(const QString &p_file)

+ 8 - 0
src/vconfigmanager.h

@@ -440,6 +440,8 @@ public:
 
     bool getSingleClickClosePreviousTab() const;
 
+    bool getEnableWildCardInSimpleSearch() const;
+
 private:
     // Look up a config from user and default settings.
     QVariant getConfigFromSettings(const QString &section, const QString &key) const;
@@ -2046,4 +2048,10 @@ inline bool VConfigManager::getSingleClickClosePreviousTab() const
 {
     return m_singleClickClosePreviousTab;
 }
+
+inline bool VConfigManager::getEnableWildCardInSimpleSearch() const
+{
+    return getConfigFromSettings("global",
+                                 "enable_wildcard_in_simple_search").toBool();
+}
 #endif // VCONFIGMANAGER_H

+ 3 - 11
src/vfilelist.cpp

@@ -17,7 +17,6 @@
 #include "dialog/vconfirmdeletiondialog.h"
 #include "dialog/vsortdialog.h"
 #include "vmainwindow.h"
-#include "utils/vimnavigationforwidget.h"
 #include "utils/viconutils.h"
 #include "dialog/vtipsdialog.h"
 #include "vcart.h"
@@ -202,7 +201,7 @@ void VFileList::setDirectory(VDirectory *p_directory)
     // be NULL.
     if (m_directory == p_directory) {
         if (!m_directory) {
-            fileList->clear();
+            fileList->clearAll();
         }
 
         return;
@@ -210,7 +209,7 @@ void VFileList::setDirectory(VDirectory *p_directory)
 
     m_directory = p_directory;
     if (!m_directory) {
-        fileList->clear();
+        fileList->clearAll();
         return;
     }
 
@@ -219,7 +218,7 @@ void VFileList::setDirectory(VDirectory *p_directory)
 
 void VFileList::updateFileList()
 {
-    fileList->clear();
+    fileList->clearAll();
     if (!m_directory->open()) {
         return;
     }
@@ -229,7 +228,6 @@ void VFileList::updateFileList()
         VNoteFile *file = files[i];
         insertFileListItem(file);
     }
-    fileList->refresh();
 }
 
 void VFileList::fileInfo()
@@ -699,7 +697,6 @@ void VFileList::activateItem(QListWidgetItem *p_item, bool p_restoreFocus)
 
     // Qt seems not to update the QListWidget correctly. Manually force it to repaint.
     fileList->update();
-    fileList->exitSearchMode(false);
     emit fileClicked(getVFile(p_item), g_config->getNoteOpenMode());
 
     if (p_restoreFocus) {
@@ -923,11 +920,6 @@ void VFileList::pasteFiles(VDirectory *p_destDir,
 
 void VFileList::keyPressEvent(QKeyEvent *p_event)
 {
-    if (VimNavigationForWidget::injectKeyPressEventForVim(fileList,
-                                                          p_event)) {
-        return;
-    }
-
     if (p_event->key() == Qt::Key_Return) {
         QListWidgetItem *item = fileList->currentItem();
         if (item) {

+ 12 - 2
src/vlineedit.cpp

@@ -5,12 +5,12 @@
 #include "utils/vutils.h"
 
 VLineEdit::VLineEdit(QWidget *p_parent)
-    : QLineEdit(p_parent)
+    : QLineEdit(p_parent), m_ctrlKEnabled(true)
 {
 }
 
 VLineEdit::VLineEdit(const QString &p_contents, QWidget *p_parent)
-    : QLineEdit(p_contents, p_parent)
+    : QLineEdit(p_contents, p_parent), m_ctrlKEnabled(true)
 {
 }
 
@@ -64,6 +64,16 @@ void VLineEdit::keyPressEvent(QKeyEvent *p_event)
         break;
     }
 
+    case Qt::Key_K:
+    {
+        if (VUtils::isControlModifierForVim(modifiers) && !m_ctrlKEnabled) {
+            QWidget::keyPressEvent(p_event);
+            accept = true;
+        }
+
+        break;
+    }
+
     default:
         break;
     }

+ 13 - 0
src/vlineedit.h

@@ -12,7 +12,20 @@ public:
 
     VLineEdit(const QString &p_contents, QWidget *p_parent = nullptr);
 
+    void setCtrlKEnabled(bool p_enabled);
+
+protected:
     void keyPressEvent(QKeyEvent *p_event) Q_DECL_OVERRIDE;
+
+private:
+    // Enable Ctrl+K shortcut.
+    // In QLineEdit, Ctrl+K will delete till the end.
+    bool m_ctrlKEnabled;
 };
 
+inline void VLineEdit::setCtrlKEnabled(bool p_enabled)
+{
+    m_ctrlKEnabled = p_enabled;
+}
+
 #endif // VLINEEDIT_H

+ 87 - 173
src/vlistwidget.cpp

@@ -3,217 +3,131 @@
 #include <QVBoxLayout>
 #include <QKeyEvent>
 #include <QCoreApplication>
-#include <QDebug>
-#include <QLabel>
+#include <QSet>
+#include <QScrollBar>
 
-#include "vlineedit.h"
 #include "utils/vutils.h"
-
-const QString searchPrefix("Search for: ");
-
-//TODO: make the style configuable
-const QString c_searchKeyStyle("border:none; background:#eaeaea; color:%1;");
-
-const QString c_colorNotMatch("#fd676b");
-
-const QString c_colorMatch("grey");
+#include "utils/vimnavigationforwidget.h"
+#include "vstyleditemdelegate.h"
 
 VListWidget::VListWidget(QWidget *parent)
-        : QListWidget(parent),
-          m_isInSearch(false),
-          m_curItemIdx(-1),
-          m_curItem(nullptr)
+    : QListWidget(parent),
+      ISimpleSearch()
 {
-    m_label = new QLabel(searchPrefix, this);
-    //TODO: make the style configuable
-    m_label->setStyleSheet(QString("color:gray;font-weight:bold;"));
-    m_searchKey = new VLineEdit(this);
-    m_searchKey->setStyleSheet(c_searchKeyStyle.arg(c_colorMatch));
-
-    QGridLayout *mainLayout = new QGridLayout;
-    QHBoxLayout *searchRowLayout = new QHBoxLayout;
-    searchRowLayout->addWidget(m_label);
-    searchRowLayout->addWidget(m_searchKey);
-
-    mainLayout->addLayout(searchRowLayout, 0, 0, -1, 1, Qt::AlignBottom);
-    setLayout(mainLayout);
-    m_label->hide();
-    m_searchKey->hide();
-
-    connect(m_searchKey, &VLineEdit::textChanged,
-            this, &VListWidget::handleSearchKeyChanged);
-
-    m_delegateObj = new VItemDelegate(this);
-    setItemDelegate(m_delegateObj);
+    m_searchInput = new VSimpleSearchInput(this, this);
+    connect(m_searchInput, &VSimpleSearchInput::triggered,
+            this, &VListWidget::handleSearchModeTriggered);
+
+    m_searchInput->hide();
+
+    m_delegate = new VStyledItemDelegate(this);
+    setItemDelegate(m_delegate);
 }
 
 void VListWidget::keyPressEvent(QKeyEvent *p_event)
 {
-    bool accept = false;
-    int modifiers = p_event->modifiers();
-
-    if (!m_isInSearch) {
-        bool isChar = (p_event->key() >= Qt::Key_A && p_event->key() <= Qt::Key_Z)
-                && (modifiers == Qt::NoModifier || modifiers == Qt::ShiftModifier);
-        bool isDigit = (p_event->key() >= Qt::Key_0 && p_event->key() <= Qt::Key_9)
-                && (modifiers == Qt::NoModifier);
-        m_isInSearch = isChar || isDigit;
+    if (m_searchInput->tryHandleKeyPressEvent(p_event)) {
+        return;
     }
 
-    bool moveUp = false;
-    switch (p_event->key()) {
-    case Qt::Key_J:
-        if (VUtils::isControlModifierForVim(modifiers)) {
-            // focus to next item/selection
-            QKeyEvent *targetEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Down, Qt::NoModifier);
-            QCoreApplication::postEvent(this, targetEvent);
-            return;
-        }
-        break;
-    case Qt::Key_K:
-        if (VUtils::isControlModifierForVim(modifiers)) {
-            // focus to previous item/selection
-            QKeyEvent *targetEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Up, Qt::NoModifier);
-            QCoreApplication::postEvent(this, targetEvent);
-            return;
-        }
-        break;
-    case Qt::Key_H:
-        if (VUtils::isControlModifierForVim(modifiers)) {
-            // Ctrl+H, delete one char
-            accept = false;
-        }
-        break;
-    case Qt::Key_F:
-    case Qt::Key_B:
-        // disable ctrl+f/b for the search key
-        accept = VUtils::isControlModifierForVim(modifiers);
-        break;
-    case Qt::Key_Escape:
-        m_isInSearch = false;
-        break;
-    case Qt::Key_Up:
-        moveUp = true;
-        // fall through
-    case Qt::Key_Down:
-        if (m_hitCount > 1) {
-            int newIdx = m_curItemIdx;
-            if (moveUp) {
-                newIdx = (newIdx - 1 + m_hitCount) % m_hitCount;
-            } else {
-                newIdx = (newIdx  + 1) % m_hitCount;
-            }
-            if (newIdx != m_curItemIdx) {
-                if (m_curItemIdx != -1) {
-                    m_hitItems[m_curItemIdx]->setSelected(false);
-                }
-
-                m_curItemIdx = newIdx;
-                m_curItem = m_hitItems[m_curItemIdx];
-                selectItem(m_curItem);
-            }
-        }
-        accept = true;
-        break;
-    }
-    if (m_isInSearch) {
-        enterSearchMode();
-    } else {
-        exitSearchMode();
+    if (VimNavigationForWidget::injectKeyPressEventForVim(this, p_event)) {
+        return;
     }
 
-    if (!accept) {
-        if (m_isInSearch) {
-            m_searchKey->keyPressEvent(p_event);
-        } else {
-            QListWidget::keyPressEvent(p_event);
-        }
-    }
+    QListWidget::keyPressEvent(p_event);
 }
 
-void VListWidget::enterSearchMode()
+void VListWidget::clearAll()
 {
-    m_label->show();
-    m_searchKey->show();
-    setSelectionMode(QAbstractItemView::SingleSelection);
+    m_searchInput->clear();
+    setSearchInputVisible(false);
+
+    QListWidget::clear();
 }
 
-void VListWidget::exitSearchMode(bool restoreSelection)
+void VListWidget::setSearchInputVisible(bool p_visible)
 {
-    m_searchKey->clear();
-    m_label->hide();
-    m_searchKey->hide();
-    setSelectionMode(QAbstractItemView::ExtendedSelection);
-    if (restoreSelection && m_curItem) {
-        selectItem(m_curItem);
+    m_searchInput->setVisible(p_visible);
+
+    int topMargin = 0;
+    if (p_visible) {
+        topMargin = m_searchInput->height();
     }
+
+    setViewportMargins(0, topMargin, 0, 0);
 }
 
-void VListWidget::refresh()
+void VListWidget::resizeEvent(QResizeEvent *p_event)
 {
-    m_isInSearch = false;
-    m_hitItems = findItems("", Qt::MatchContains);
-    m_hitCount = m_hitItems.count();
+    QListWidget::resizeEvent(p_event);
 
-    for(const auto& it : selectedItems()) {
-        it->setSelected(false);
+    QRect rect = contentsRect();
+    int width = rect.width();
+    QScrollBar *vbar = verticalScrollBar();
+    if (vbar && (vbar->minimum() != vbar->maximum())) {
+        width -= vbar->width();
     }
 
-    if (m_hitCount > 0) {
-        if (selectedItems().isEmpty()) {
-            m_curItemIdx = 0;
-            m_curItem = m_hitItems.first();
-            selectItem(m_curItem);
-        }
-    } else {
-        m_curItemIdx = -1;
-        m_curItem = nullptr;
+    m_searchInput->setGeometry(QRect(rect.left(),
+                                     rect.top(),
+                                     width,
+                                     m_searchInput->height()));
+}
+
+void VListWidget::handleSearchModeTriggered(bool p_inSearchMode)
+{
+    setSearchInputVisible(p_inSearchMode);
+    if (!p_inSearchMode) {
+        clearItemsHighlight();
+        setFocus();
     }
 }
 
-void VListWidget::clear()
+QList<void *> VListWidget::searchItems(const QString &p_text,
+                                       Qt::MatchFlags p_flags) const
 {
-    QListWidget::clear();
-    m_hitCount = 0;
-    m_hitItems.clear();
-    m_isInSearch = false;
-    m_curItem = nullptr;
-    m_curItemIdx = 0;
-    exitSearchMode();
+    QList<QListWidgetItem *> items = findItems(p_text, p_flags);
+
+    QList<void *> res;
+    res.reserve(items.size());
+    for (int i = 0; i < items.size(); ++i) {
+        res.append(items[i]);
+    }
+
+    return res;
 }
 
-void VListWidget::selectItem(QListWidgetItem *item)
+void VListWidget::highlightHitItems(const QList<void *> &p_items)
 {
-    if (item) {
-        for(const auto& it : selectedItems()) {
-            it->setSelected(false);
+    clearItemsHighlight();
+
+    QSet<QModelIndex> hitIndexes;
+    for (auto it : p_items) {
+        QModelIndex index = indexFromItem(static_cast<QListWidgetItem *>(it));
+        if (index.isValid()) {
+            hitIndexes.insert(index);
         }
-        setCurrentItem(item);
+    }
+
+    if (!hitIndexes.isEmpty()) {
+        m_delegate->setHitItems(hitIndexes);
+        update();
     }
 }
 
-void VListWidget::handleSearchKeyChanged(const QString& key)
+void VListWidget::clearItemsHighlight()
 {
-    m_delegateObj->setSearchKey(key);
-    // trigger repaint & update
+    m_delegate->clearHitItems();
     update();
+}
 
-    m_hitItems = findItems(key, Qt::MatchContains);
-    if (key.isEmpty()) {
-        if (m_curItem) {
-            m_curItemIdx = m_hitItems.indexOf(m_curItem);
-        }
-    } else {
-       bool hasSearchResult = !m_hitItems.isEmpty();
-       if (hasSearchResult) {
-           m_searchKey->setStyleSheet(c_searchKeyStyle.arg(c_colorMatch));
-
-           m_curItem = m_hitItems[0];
-           setCurrentItem(m_curItem);
-           m_curItemIdx = 0;
-       } else {
-           m_searchKey->setStyleSheet(c_searchKeyStyle.arg(c_colorNotMatch));
-       }
-    }
-    m_hitCount = m_hitItems.count();
+void VListWidget::selectHitItem(void *p_item)
+{
+    setCurrentItem(static_cast<QListWidgetItem *>(p_item),
+                   QItemSelectionModel::ClearAndSelect);
+}
+
+int VListWidget::totalNumberOfItems()
+{
+    return count();
 }

+ 21 - 111
src/vlistwidget.h

@@ -2,138 +2,48 @@
 #define VLISTWIDGET_H
 
 #include <QListWidget>
-#include <QPainter>
-#include <QStyleOptionViewItem>
-#include <QModelIndex>
-#include <QItemDelegate>
-#include <QDebug>
-#include <QLabel>
 
-class VLineEdit;
+#include "vsimplesearchinput.h"
 
-class VItemDelegate : public QItemDelegate
-{
-public:
-    explicit VItemDelegate(QObject *parent = Q_NULLPTR)
-        : QItemDelegate(parent), m_searchKey()
-    {
-    }
-
-    void paint(QPainter *painter,
-               const QStyleOptionViewItem &option,
-               const QModelIndex &index) const Q_DECL_OVERRIDE
-    {
-        painter->save();
-        QPainter::CompositionMode oldCompMode = painter->compositionMode();
-        // set background color
-        painter->setPen(QPen(Qt::NoPen));
-        if (option.state & QStyle::State_Selected) {
-            // TODO: make it configuable
-            painter->setBrush(QBrush(QColor("#d3d3d3")));
-        } else {
-            // use default brush
-        }
-
-        painter->drawRect(option.rect);
-
-        Qt::GlobalColor hitPenColor = Qt::blue;
-        Qt::GlobalColor normalPenColor = Qt::black;
-
-        // set text color
-        QVariant value = index.data(Qt::DisplayRole);
-        QRectF rect(option.rect), boundRect;
-        if (value.isValid()) {
-            QString text = value.toString();
-            int idx;
-            bool isHit = !m_searchKey.isEmpty()
-                         && (idx = text.indexOf(m_searchKey, 0, Qt::CaseInsensitive)) != -1;
-            if (isHit) {
-                qDebug() << QString("highlight: %1 (with: %2)").arg(text).arg(m_searchKey);
-                // split the text by the search key
-                QString left = text.left(idx), right = text.mid(idx + m_searchKey.length());
-                drawText(painter, normalPenColor, rect, Qt::AlignLeft, left, boundRect);
-                drawText(painter, hitPenColor, rect, Qt::AlignLeft, m_searchKey, boundRect);
-
-                // highlight matched keyword
-                painter->setBrush(QBrush(QColor("#ffde7b")));
-                painter->setCompositionMode(QPainter::CompositionMode_Multiply);
-                painter->setPen(Qt::NoPen);
-                painter->drawRect(boundRect);
-                painter->setCompositionMode(oldCompMode);
-
-                drawText(painter, normalPenColor, rect, Qt::AlignLeft, right, boundRect);
-            } else {
-                drawText(painter, normalPenColor, rect, Qt::AlignLeft, text, boundRect);
-            }
-        }
-
-        painter->restore();
-    }
-
-    void drawText(QPainter *painter,
-                  Qt::GlobalColor penColor,
-                  QRectF& rect,
-                  int flags,
-                  QString text,
-                  QRectF& boundRect) const
-    {
-        if (!text.isEmpty()) {
-            painter->setPen(QPen(penColor));
-            painter->drawText(rect, flags, text, &boundRect);
-            rect.adjust(boundRect.width(), 0, boundRect.width(), 0);
-        }
-    }
-
-    void setSearchKey(const QString& key)
-    {
-        m_searchKey = key;
-    }
+class VStyledItemDelegate;
 
-private:
-    QString m_searchKey;
-};
 
-class VListWidget : public QListWidget
+class VListWidget : public QListWidget, public ISimpleSearch
 {
 public:
     explicit VListWidget(QWidget *parent = Q_NULLPTR);
 
-    void selectItem(QListWidgetItem *item);
+    // Clear list widget as well as other data.
+    // clear() is not virtual to override.
+    void clearAll();
+
+    // Implement ISimpleSearch.
+    virtual QList<void *> searchItems(const QString &p_text,
+                                      Qt::MatchFlags p_flags) const Q_DECL_OVERRIDE;
 
-    void exitSearchMode(bool restoreSelection=true);
+    virtual void highlightHitItems(const QList<void *> &p_items) Q_DECL_OVERRIDE;
 
-    void enterSearchMode();
+    virtual void clearItemsHighlight() Q_DECL_OVERRIDE;
 
-    void refresh();
+    virtual void selectHitItem(void *p_item) Q_DECL_OVERRIDE;
 
-public slots:
-    void clear();
+    virtual int totalNumberOfItems() Q_DECL_OVERRIDE;
 
 private slots:
-    void handleSearchKeyChanged(const QString& updatedText);
+    void handleSearchModeTriggered(bool p_inSearchMode);
 
 protected:
     void keyPressEvent(QKeyEvent *p_event) Q_DECL_OVERRIDE;
 
-private:
-    QLabel *m_label;
-    VLineEdit* m_searchKey;
-    bool m_isInSearch;
-
-    VItemDelegate* m_delegateObj;
+    void resizeEvent(QResizeEvent *p_event) Q_DECL_OVERRIDE;
 
-    // Items that are matched by the search key.
-    QList<QListWidgetItem*> m_hitItems;
-
-    // How many items are matched, if no search key or key is empty string,
-    // all items are matched.
-    int m_hitCount;
+private:
+    // Show or hide search input.
+    void setSearchInputVisible(bool p_visible);
 
-    // Current selected item index.
-    int m_curItemIdx;
+    VSimpleSearchInput *m_searchInput;
 
-    // Current selected item.
-    QListWidgetItem* m_curItem;
+    VStyledItemDelegate *m_delegate;
 };
 
 #endif // VLISTWIDGET_H

+ 216 - 0
src/vsimplesearchinput.cpp

@@ -0,0 +1,216 @@
+#include "vsimplesearchinput.h"
+
+#include <QHBoxLayout>
+#include <QKeyEvent>
+#include <QFocusEvent>
+#include <QRegExpValidator>
+#include <QRegExp>
+#include <QLabel>
+
+#include "vlineedit.h"
+#include "utils/vutils.h"
+#include "vconfigmanager.h"
+
+extern VConfigManager *g_config;
+
+VSimpleSearchInput::VSimpleSearchInput(ISimpleSearch *p_obj, QWidget *p_parent)
+    : QWidget(p_parent),
+      m_obj(p_obj),
+      m_inSearchMode(false),
+      m_currentIdx(-1),
+      m_wildCardEnabled(g_config->getEnableWildCardInSimpleSearch())
+{
+    if (m_wildCardEnabled) {
+        m_matchFlags = Qt::MatchWildcard | Qt::MatchWrap | Qt::MatchRecursive;
+    } else {
+        m_matchFlags = Qt::MatchContains | Qt::MatchWrap | Qt::MatchRecursive;
+    }
+
+    m_searchEdit = new VLineEdit();
+    m_searchEdit->setPlaceholderText(tr("Type to search"));
+    m_searchEdit->setCtrlKEnabled(false);
+    connect(m_searchEdit, &QLineEdit::textChanged,
+            this, &VSimpleSearchInput::handleEditTextChanged);
+
+    QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp),
+                                                 m_searchEdit);
+    m_searchEdit->setValidator(validator);
+    m_searchEdit->installEventFilter(this);
+
+    m_infoLabel = new QLabel();
+
+    QLayout *layout = new QHBoxLayout();
+    layout->addWidget(m_searchEdit);
+    layout->addWidget(m_infoLabel);
+    layout->setContentsMargins(0, 0, 0, 0);
+
+    setLayout(layout);
+}
+
+void VSimpleSearchInput::clear()
+{
+    m_inSearchMode = false;
+    clearSearch();
+}
+
+// If it is the / leader key to trigger search mode.
+static bool isLeaderKey(int p_key, int p_modifiers)
+{
+    return p_key == Qt::Key_Slash && p_modifiers == Qt::NoModifier;
+}
+
+static bool isCharKey(int p_key, int p_modifiers)
+{
+    return p_key >= Qt::Key_A
+           && p_key <= Qt::Key_Z
+           && (p_modifiers == Qt::NoModifier || p_modifiers == Qt::ShiftModifier);
+}
+
+static bool isDigitKey(int p_key, int p_modifiers)
+{
+    return p_key >= Qt::Key_0
+           && p_key <= Qt::Key_9
+           && (p_modifiers == Qt::NoModifier || p_modifiers == Qt::KeypadModifier);
+}
+
+static QChar keyToChar(int p_key, int p_modifiers)
+{
+    if (isCharKey(p_key, p_modifiers)) {
+        char ch = p_modifiers == Qt::ShiftModifier ? 'A' : 'a';
+        return QChar(ch + (p_key - Qt::Key_A));
+    } else if (isDigitKey(p_key, p_modifiers)) {
+        return QChar('0' + (p_key - Qt::Key_0));
+    }
+
+    return QChar();
+}
+
+bool VSimpleSearchInput::tryHandleKeyPressEvent(QKeyEvent *p_event)
+{
+    int key = p_event->key();
+    Qt::KeyboardModifiers modifiers = p_event->modifiers();
+
+    if (!m_inSearchMode) {
+        // Try to trigger search mode.
+        QChar ch;
+        if (isCharKey(key, modifiers)
+            || isDigitKey(key, modifiers)) {
+            m_inSearchMode = true;
+            ch = keyToChar(key, modifiers);
+        } else if (isLeaderKey(key, modifiers)) {
+            m_inSearchMode = true;
+        }
+
+        if (m_inSearchMode) {
+            emit triggered(m_inSearchMode);
+
+            clearSearch();
+            m_searchEdit->setFocus();
+
+            if (!ch.isNull()) {
+                m_searchEdit->setText(ch);
+            }
+
+            return true;
+        }
+    } else {
+        // Try to exit search mode.
+        if (key == Qt::Key_Escape
+            || (key == Qt::Key_BracketLeft
+                && VUtils::isControlModifierForVim(modifiers))) {
+            m_inSearchMode = false;
+            emit triggered(m_inSearchMode);
+            return true;
+        }
+    }
+
+    // Ctrl+N/P to activate next hit item.
+    if (VUtils::isControlModifierForVim(modifiers)
+        && (key == Qt::Key_N || key == Qt::Key_P)) {
+        int delta = key == Qt::Key_N ? 1 : -1;
+
+        if (!m_inSearchMode) {
+            m_inSearchMode = true;
+            emit triggered(m_inSearchMode);
+            m_searchEdit->setFocus();
+
+            m_obj->highlightHitItems(m_hitItems);
+        }
+
+        if (!m_hitItems.isEmpty()) {
+            m_currentIdx += delta;
+            if (m_currentIdx < 0) {
+                m_currentIdx = m_hitItems.size() - 1;
+            } else if (m_currentIdx >= m_hitItems.size()) {
+                m_currentIdx = 0;
+            }
+
+            m_obj->selectHitItem(currentItem());
+        }
+
+        return true;
+    }
+
+    return false;
+}
+
+void VSimpleSearchInput::clearSearch()
+{
+    m_searchEdit->clear();
+    m_hitItems.clear();
+    m_currentIdx = -1;
+    m_obj->clearItemsHighlight();
+
+    updateInfoLabel(0, m_obj->totalNumberOfItems());
+}
+
+bool VSimpleSearchInput::eventFilter(QObject *p_watched, QEvent *p_event)
+{
+    Q_UNUSED(p_watched);
+    if (p_event->type() == QEvent::FocusOut) {
+        QFocusEvent *eve = static_cast<QFocusEvent *>(p_event);
+        if (eve->reason() != Qt::ActiveWindowFocusReason) {
+            m_inSearchMode = false;
+            emit triggered(m_inSearchMode);
+        }
+    }
+
+    return QWidget::eventFilter(p_watched, p_event);
+}
+
+void VSimpleSearchInput::handleEditTextChanged(const QString &p_text)
+{
+    if (!m_inSearchMode) {
+        return;
+    }
+
+    if (p_text.isEmpty()) {
+        clearSearch();
+        m_obj->selectHitItem(NULL);
+        return;
+    }
+
+    if (m_wildCardEnabled) {
+        QString wildcardText(p_text.size() * 2 + 1, '*');
+        for (int i = 0, j = 1; i < p_text.size(); ++i, j += 2) {
+            wildcardText[j] = p_text[i];
+        }
+
+        m_hitItems = m_obj->searchItems(wildcardText, m_matchFlags);
+    } else {
+        m_hitItems = m_obj->searchItems(p_text, m_matchFlags);
+    }
+
+    updateInfoLabel(m_hitItems.size(), m_obj->totalNumberOfItems());
+
+    m_obj->highlightHitItems(m_hitItems);
+
+    m_currentIdx = m_hitItems.isEmpty() ? -1 : 0;
+
+    m_obj->selectHitItem(currentItem());
+}
+
+void VSimpleSearchInput::updateInfoLabel(int p_nrHit, int p_total)
+{
+    m_infoLabel->setText(tr("%1/%2").arg(p_nrHit).arg(p_total));
+}

+ 89 - 0
src/vsimplesearchinput.h

@@ -0,0 +1,89 @@
+#ifndef VSIMPLESEARCHINPUT_H
+#define VSIMPLESEARCHINPUT_H
+
+#include <QWidget>
+#include <QList>
+
+class VLineEdit;
+class QLabel;
+
+class ISimpleSearch
+{
+public:
+    // Return items matching the search.
+    virtual QList<void *> searchItems(const QString &p_text,
+                                      Qt::MatchFlags p_flags) const = 0;
+
+    // Highlight hit items to denote the search result.
+    virtual void highlightHitItems(const QList<void *> &p_items) = 0;
+
+    // Clear the highlight.
+    virtual void clearItemsHighlight() = 0;
+
+    // Select @p_item.
+    // @p_item sets to NULL to clear selection.
+    virtual void selectHitItem(void *p_item) = 0;
+
+    // Get the total number of all the items.
+    virtual int totalNumberOfItems() = 0;
+};
+
+
+class VSimpleSearchInput : public QWidget
+{
+    Q_OBJECT
+public:
+    explicit VSimpleSearchInput(ISimpleSearch *p_obj, QWidget *p_parent = nullptr);
+
+    // Clear input.
+    void clear();
+
+    // Try to handle key press event from outside widget.
+    // Return true if @p_event is consumed and do not need further process.
+    bool tryHandleKeyPressEvent(QKeyEvent *p_event);
+
+signals:
+    // Search mode is triggered.
+    void triggered(bool p_inSearchMode);
+
+protected:
+    bool eventFilter(QObject *p_watched, QEvent *p_event) Q_DECL_OVERRIDE;
+
+private slots:
+    // Text in the input changed.
+    void handleEditTextChanged(const QString &p_text);
+
+private:
+    // Clear last search.
+    void clearSearch();
+
+    void *currentItem() const;
+
+    void updateInfoLabel(int p_nrHit, int p_total);
+
+    ISimpleSearch *m_obj;
+
+    VLineEdit *m_searchEdit;
+
+    QLabel *m_infoLabel;
+
+    bool m_inSearchMode;
+
+    QList<void *> m_hitItems;
+
+    int m_currentIdx;
+
+    Qt::MatchFlags m_matchFlags;
+
+    bool m_wildCardEnabled;
+};
+
+inline void *VSimpleSearchInput::currentItem() const
+{
+    if (m_currentIdx >= 0 && m_currentIdx < m_hitItems.size()) {
+        return m_hitItems[m_currentIdx];
+    }
+
+    return NULL;
+}
+#endif // VSIMPLESEARCHINPUT_H

+ 31 - 0
src/vstyleditemdelegate.cpp

@@ -0,0 +1,31 @@
+#include "vstyleditemdelegate.h"
+
+#include <QPainter>
+#include <QDebug>
+
+#include "vpalette.h"
+
+extern VPalette *g_palette;
+
+VStyledItemDelegate::VStyledItemDelegate(QObject *p_parent)
+    : QStyledItemDelegate(p_parent)
+{
+    m_itemHitBg = QBrush(QColor(g_palette->color("search_hit_item_bg")));
+    m_itemHitFg = QBrush(QColor(g_palette->color("search_hit_item_fg")));
+}
+
+void VStyledItemDelegate::paint(QPainter *p_painter,
+                                const QStyleOptionViewItem &p_option,
+                                const QModelIndex &p_index) const
+{
+    if (isHit(p_index)) {
+        QStyleOptionViewItem option(p_option);
+        p_painter->fillRect(option.rect, m_itemHitBg);
+        // Does not work anyway.
+        // option.palette.setBrush(QPalette::Base, m_itemHitBg);
+        option.palette.setBrush(QPalette::Text, m_itemHitFg);
+        QStyledItemDelegate::paint(p_painter, option, p_index);
+    } else {
+        QStyledItemDelegate::paint(p_painter, p_option, p_index);
+    }
+}

+ 50 - 0
src/vstyleditemdelegate.h

@@ -0,0 +1,50 @@
+#ifndef VSTYLEDITEMDELEGATE_H
+#define VSTYLEDITEMDELEGATE_H
+
+#include <QStyledItemDelegate>
+#include <QBrush>
+#include <QSet>
+
+
+class VStyledItemDelegate : public QStyledItemDelegate
+{
+public:
+    explicit VStyledItemDelegate(QObject *p_parent = Q_NULLPTR);
+
+    virtual void paint(QPainter *p_painter,
+                       const QStyleOptionViewItem &p_option,
+                       const QModelIndex &p_index) const Q_DECL_OVERRIDE;
+
+    void setHitItems(const QSet<QModelIndex> &p_hitItems);
+
+    void clearHitItems();
+
+private:
+    bool isHit(const QModelIndex &p_index) const;
+
+    QBrush m_itemHitBg;
+
+    QBrush m_itemHitFg;
+
+    QSet<QModelIndex> m_hitItems;
+};
+
+inline void VStyledItemDelegate::setHitItems(const QSet<QModelIndex> &p_hitItems)
+{
+    m_hitItems = p_hitItems;
+}
+
+inline void VStyledItemDelegate::clearHitItems()
+{
+    m_hitItems.clear();
+}
+
+inline bool VStyledItemDelegate::isHit(const QModelIndex &p_index) const
+{
+    if (m_hitItems.isEmpty()) {
+        return false;
+    }
+
+    return m_hitItems.contains(p_index);
+}
+#endif // VSTYLEDITEMDELEGATE_H

+ 2 - 0
src/vvimcmdlineedit.cpp

@@ -206,6 +206,8 @@ void VVimCmdLineEdit::focusOutEvent(QFocusEvent *p_event)
     if (p_event->reason() != Qt::ActiveWindowFocusReason) {
         emit commandCancelled();
     }
+
+    VLineEdit::focusOutEvent(p_event);
 }
 
 void VVimCmdLineEdit::setCommand(const QString &p_cmd)