Browse Source

Merge branch 'list-search' into dev

Add simple search for list widget.
Le Tan 7 years ago
parent
commit
0125251716

+ 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

+ 8 - 2
src/src.pro

@@ -108,7 +108,10 @@ SOURCES += main.cpp\
     utils/vwebutils.cpp \
     vlineedit.cpp \
     vcart.cpp \
-    vvimcmdlineedit.cpp
+    vvimcmdlineedit.cpp \
+    vlistwidget.cpp \
+    vsimplesearchinput.cpp \
+    vstyleditemdelegate.cpp
 
 HEADERS  += vmainwindow.h \
     vdirectorytree.h \
@@ -203,7 +206,10 @@ HEADERS  += vmainwindow.h \
     utils/vwebutils.h \
     vlineedit.h \
     vcart.h \
-    vvimcmdlineedit.h
+    vvimcmdlineedit.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

+ 4 - 10
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"
@@ -61,7 +60,7 @@ VFileList::VFileList(QWidget *parent)
 
 void VFileList::setupUI()
 {
-    fileList = new QListWidget(this);
+    fileList = new VListWidget(this);
     fileList->setContextMenuPolicy(Qt::CustomContextMenu);
     fileList->setSelectionMode(QAbstractItemView::ExtendedSelection);
     fileList->setObjectName("FileList");
@@ -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;
     }
@@ -921,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) {

+ 2 - 1
src/vfilelist.h

@@ -13,6 +13,7 @@
 #include "vdirectory.h"
 #include "vnotefile.h"
 #include "vnavigationmode.h"
+#include "vlistwidget.h"
 
 class QAction;
 class VNote;
@@ -166,7 +167,7 @@ private:
     void activateItem(QListWidgetItem *p_item, bool p_restoreFocus = false);
 
     VEditArea *editArea;
-    QListWidget *fileList;
+    VListWidget *fileList;
     QPointer<VDirectory> m_directory;
 
     // Magic number for clipboard operations.

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

+ 12 - 0
src/vlineedit.h

@@ -12,8 +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

+ 133 - 0
src/vlistwidget.cpp

@@ -0,0 +1,133 @@
+#include "vlistwidget.h"
+
+#include <QVBoxLayout>
+#include <QKeyEvent>
+#include <QCoreApplication>
+#include <QSet>
+#include <QScrollBar>
+
+#include "utils/vutils.h"
+#include "utils/vimnavigationforwidget.h"
+#include "vstyleditemdelegate.h"
+
+VListWidget::VListWidget(QWidget *parent)
+    : QListWidget(parent),
+      ISimpleSearch()
+{
+    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)
+{
+    if (m_searchInput->tryHandleKeyPressEvent(p_event)) {
+        return;
+    }
+
+    if (VimNavigationForWidget::injectKeyPressEventForVim(this, p_event)) {
+        return;
+    }
+
+    QListWidget::keyPressEvent(p_event);
+}
+
+void VListWidget::clearAll()
+{
+    m_searchInput->clear();
+    setSearchInputVisible(false);
+
+    QListWidget::clear();
+}
+
+void VListWidget::setSearchInputVisible(bool p_visible)
+{
+    m_searchInput->setVisible(p_visible);
+
+    int topMargin = 0;
+    if (p_visible) {
+        topMargin = m_searchInput->height();
+    }
+
+    setViewportMargins(0, topMargin, 0, 0);
+}
+
+void VListWidget::resizeEvent(QResizeEvent *p_event)
+{
+    QListWidget::resizeEvent(p_event);
+
+    QRect rect = contentsRect();
+    int width = rect.width();
+    QScrollBar *vbar = verticalScrollBar();
+    if (vbar && (vbar->minimum() != vbar->maximum())) {
+        width -= vbar->width();
+    }
+
+    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();
+    }
+}
+
+QList<void *> VListWidget::searchItems(const QString &p_text,
+                                       Qt::MatchFlags p_flags) const
+{
+    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::highlightHitItems(const QList<void *> &p_items)
+{
+    clearItemsHighlight();
+
+    QSet<QModelIndex> hitIndexes;
+    for (auto it : p_items) {
+        QModelIndex index = indexFromItem(static_cast<QListWidgetItem *>(it));
+        if (index.isValid()) {
+            hitIndexes.insert(index);
+        }
+    }
+
+    if (!hitIndexes.isEmpty()) {
+        m_delegate->setHitItems(hitIndexes);
+        update();
+    }
+}
+
+void VListWidget::clearItemsHighlight()
+{
+    m_delegate->clearHitItems();
+    update();
+}
+
+void VListWidget::selectHitItem(void *p_item)
+{
+    setCurrentItem(static_cast<QListWidgetItem *>(p_item),
+                   QItemSelectionModel::ClearAndSelect);
+}
+
+int VListWidget::totalNumberOfItems()
+{
+    return count();
+}

+ 49 - 0
src/vlistwidget.h

@@ -0,0 +1,49 @@
+#ifndef VLISTWIDGET_H
+#define VLISTWIDGET_H
+
+#include <QListWidget>
+
+#include "vsimplesearchinput.h"
+
+class VStyledItemDelegate;
+
+
+class VListWidget : public QListWidget, public ISimpleSearch
+{
+public:
+    explicit VListWidget(QWidget *parent = Q_NULLPTR);
+
+    // 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;
+
+    virtual void highlightHitItems(const QList<void *> &p_items) Q_DECL_OVERRIDE;
+
+    virtual void clearItemsHighlight() Q_DECL_OVERRIDE;
+
+    virtual void selectHitItem(void *p_item) Q_DECL_OVERRIDE;
+
+    virtual int totalNumberOfItems() Q_DECL_OVERRIDE;
+
+private slots:
+    void handleSearchModeTriggered(bool p_inSearchMode);
+
+protected:
+    void keyPressEvent(QKeyEvent *p_event) Q_DECL_OVERRIDE;
+
+    void resizeEvent(QResizeEvent *p_event) Q_DECL_OVERRIDE;
+
+private:
+    // Show or hide search input.
+    void setSearchInputVisible(bool p_visible);
+
+    VSimpleSearchInput *m_searchInput;
+
+    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)