From 74cb54e02bdd851da827d6b39f1754308510771d Mon Sep 17 00:00:00 2001 From: Xianzhong Wang Date: Mon, 29 Jan 2018 06:03:44 +0800 Subject: [PATCH 1/3] [function] support advanced search in file list (#121) spec: when focus in file list, 1. type any character or digit will trigger the advanced search mode 2. type Esc to exit the search mode 3. type Enter or mouse select will also exit the search mode --- src/src.pro | 6 +- src/vfilelist.cpp | 4 +- src/vfilelist.h | 3 +- src/vlineedit.h | 1 - src/vlistwidget.cpp | 206 ++++++++++++++++++++++++++++++++++++++++++++ src/vlistwidget.h | 109 +++++++++++++++++++++++ 6 files changed, 324 insertions(+), 5 deletions(-) create mode 100644 src/vlistwidget.cpp create mode 100644 src/vlistwidget.h diff --git a/src/src.pro b/src/src.pro index 330b627b..cd88541f 100644 --- a/src/src.pro +++ b/src/src.pro @@ -108,7 +108,8 @@ SOURCES += main.cpp\ utils/vwebutils.cpp \ vlineedit.cpp \ vcart.cpp \ - vvimcmdlineedit.cpp + vvimcmdlineedit.cpp \ + vlistwidget.cpp HEADERS += vmainwindow.h \ vdirectorytree.h \ @@ -203,7 +204,8 @@ HEADERS += vmainwindow.h \ utils/vwebutils.h \ vlineedit.h \ vcart.h \ - vvimcmdlineedit.h + vvimcmdlineedit.h \ + vlistwidget.h RESOURCES += \ vnote.qrc \ diff --git a/src/vfilelist.cpp b/src/vfilelist.cpp index 5c17fe2f..08fe4013 100644 --- a/src/vfilelist.cpp +++ b/src/vfilelist.cpp @@ -61,7 +61,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"); @@ -229,6 +229,7 @@ void VFileList::updateFileList() VNoteFile *file = files[i]; insertFileListItem(file); } + fileList->refresh(); } void VFileList::fileInfo() @@ -698,6 +699,7 @@ 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) { diff --git a/src/vfilelist.h b/src/vfilelist.h index 2238f75f..fcfdf83c 100644 --- a/src/vfilelist.h +++ b/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 m_directory; // Magic number for clipboard operations. diff --git a/src/vlineedit.h b/src/vlineedit.h index 2cc1b367..39f0c4b8 100644 --- a/src/vlineedit.h +++ b/src/vlineedit.h @@ -12,7 +12,6 @@ public: VLineEdit(const QString &p_contents, QWidget *p_parent = nullptr); -protected: void keyPressEvent(QKeyEvent *p_event) Q_DECL_OVERRIDE; }; diff --git a/src/vlistwidget.cpp b/src/vlistwidget.cpp new file mode 100644 index 00000000..c5d93a66 --- /dev/null +++ b/src/vlistwidget.cpp @@ -0,0 +1,206 @@ +#include "vlistwidget.h" +#include +#include +#include +#include +#include +#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"); + +VListWidget::VListWidget(QWidget *parent):QListWidget(parent), m_isInSearch(false), + m_curItemIdx(-1), m_curItem(nullptr) +{ + 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); +} + +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; + } + + 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 (!accept) { + if (m_isInSearch) { + m_searchKey->keyPressEvent(p_event); + } else { + QListWidget::keyPressEvent(p_event); + } + } +} + +void VListWidget::enterSearchMode() { + m_label->show(); + m_searchKey->show(); + setSelectionMode(QAbstractItemView::SingleSelection); +} + +void VListWidget::exitSearchMode(bool restoreSelection) { + m_searchKey->clear(); + m_label->hide(); + m_searchKey->hide(); + setSelectionMode(QAbstractItemView::ExtendedSelection); + if (restoreSelection && m_curItem) { + selectItem(m_curItem); + } +} + + +void VListWidget::refresh() { + m_isInSearch = false; + m_hitItems = findItems("", Qt::MatchContains); + m_hitCount = m_hitItems.count(); + + for(const auto& it : selectedItems()) { + it->setSelected(false); + } + + 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; + } +} + +void VListWidget::clear() { + QListWidget::clear(); + m_hitCount = 0; + m_hitItems.clear(); + m_isInSearch = false; + m_curItem = nullptr; + m_curItemIdx = 0; + exitSearchMode(); +} + +void VListWidget::selectItem(QListWidgetItem *item) { + if (item) { + for(const auto& it : selectedItems()) { + it->setSelected(false); + } + setCurrentItem(item); + } +} + +void VListWidget::handleSearchKeyChanged(const QString& key) +{ + m_delegateObj->setSearchKey(key); + // trigger repaint & update + 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(); +} diff --git a/src/vlistwidget.h b/src/vlistwidget.h new file mode 100644 index 00000000..f7c72afb --- /dev/null +++ b/src/vlistwidget.h @@ -0,0 +1,109 @@ +#ifndef VLISTWIDGET_H +#define VLISTWIDGET_H + +#include +#include +#include +#include +#include +#include +#include "vlineedit.h" +#include +#include + +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 { + 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; + } + +private: + QString m_searchKey; +}; + +class VListWidget : public QListWidget +{ +public: + explicit VListWidget(QWidget *parent = Q_NULLPTR); + void keyPressEvent(QKeyEvent *event); + void selectItem(QListWidgetItem *item); + void exitSearchMode(bool restoreSelection=true); + void enterSearchMode(); + void refresh(); + +public Q_SLOTS: + void handleSearchKeyChanged(const QString& updatedText); + void clear(); + + +private: + QLabel *m_label; + VLineEdit* m_searchKey; + bool m_isInSearch; + + VItemDelegate* m_delegateObj; + QList m_hitItems; // items that are matched by the search key + int m_hitCount; // how many items are matched, if no search key or key is empty string, all items are matched + int m_curItemIdx; // current selected item index + QListWidgetItem* m_curItem; // current selected item +}; + +#endif // VLISTWIDGET_H From abe40cc74fef18cea88b3fc6af6fcfa315cfb3cc Mon Sep 17 00:00:00 2001 From: Le Tan Date: Tue, 30 Jan 2018 19:28:52 +0800 Subject: [PATCH 2/3] clean up code styles --- src/vlistwidget.cpp | 29 ++++++++++++++++------- src/vlistwidget.h | 58 ++++++++++++++++++++++++++++++++++----------- 2 files changed, 65 insertions(+), 22 deletions(-) diff --git a/src/vlistwidget.cpp b/src/vlistwidget.cpp index c5d93a66..11a6a047 100644 --- a/src/vlistwidget.cpp +++ b/src/vlistwidget.cpp @@ -1,19 +1,28 @@ #include "vlistwidget.h" + #include #include #include #include #include + +#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"); -VListWidget::VListWidget(QWidget *parent):QListWidget(parent), m_isInSearch(false), - m_curItemIdx(-1), m_curItem(nullptr) +VListWidget::VListWidget(QWidget *parent) + : QListWidget(parent), + m_isInSearch(false), + m_curItemIdx(-1), + m_curItem(nullptr) { m_label = new QLabel(searchPrefix, this); //TODO: make the style configuable @@ -122,13 +131,15 @@ void VListWidget::keyPressEvent(QKeyEvent *p_event) } } -void VListWidget::enterSearchMode() { +void VListWidget::enterSearchMode() +{ m_label->show(); m_searchKey->show(); setSelectionMode(QAbstractItemView::SingleSelection); } -void VListWidget::exitSearchMode(bool restoreSelection) { +void VListWidget::exitSearchMode(bool restoreSelection) +{ m_searchKey->clear(); m_label->hide(); m_searchKey->hide(); @@ -138,8 +149,8 @@ void VListWidget::exitSearchMode(bool restoreSelection) { } } - -void VListWidget::refresh() { +void VListWidget::refresh() +{ m_isInSearch = false; m_hitItems = findItems("", Qt::MatchContains); m_hitCount = m_hitItems.count(); @@ -160,7 +171,8 @@ void VListWidget::refresh() { } } -void VListWidget::clear() { +void VListWidget::clear() +{ QListWidget::clear(); m_hitCount = 0; m_hitItems.clear(); @@ -170,7 +182,8 @@ void VListWidget::clear() { exitSearchMode(); } -void VListWidget::selectItem(QListWidgetItem *item) { +void VListWidget::selectItem(QListWidgetItem *item) +{ if (item) { for(const auto& it : selectedItems()) { it->setSelected(false); diff --git a/src/vlistwidget.h b/src/vlistwidget.h index f7c72afb..c7df808e 100644 --- a/src/vlistwidget.h +++ b/src/vlistwidget.h @@ -2,22 +2,27 @@ #define VLISTWIDGET_H #include -#include #include #include #include #include -#include "vlineedit.h" #include #include +class VLineEdit; + class VItemDelegate : public QItemDelegate { public: - explicit VItemDelegate(QObject *parent = Q_NULLPTR):QItemDelegate(parent), m_searchKey() { + explicit VItemDelegate(QObject *parent = Q_NULLPTR) + : QItemDelegate(parent), m_searchKey() + { } - void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { + void paint(QPainter *painter, + const QStyleOptionViewItem &option, + const QModelIndex &index) const Q_DECL_OVERRIDE + { painter->save(); QPainter::CompositionMode oldCompMode = painter->compositionMode(); // set background color @@ -28,6 +33,7 @@ public: } else { // use default brush } + painter->drawRect(option.rect); Qt::GlobalColor hitPenColor = Qt::blue; @@ -39,7 +45,8 @@ public: if (value.isValid()) { QString text = value.toString(); int idx; - bool isHit = !m_searchKey.isEmpty() && (idx=text.indexOf(m_searchKey, 0, Qt::CaseInsensitive)) != -1; + 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 @@ -63,7 +70,13 @@ public: painter->restore(); } - void drawText(QPainter *painter, Qt::GlobalColor penColor, QRectF& rect, int flags, QString text, QRectF& boundRect) const { + 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); @@ -71,7 +84,8 @@ public: } } - void setSearchKey(const QString& key) { + void setSearchKey(const QString& key) + { m_searchKey = key; } @@ -83,16 +97,23 @@ class VListWidget : public QListWidget { public: explicit VListWidget(QWidget *parent = Q_NULLPTR); - void keyPressEvent(QKeyEvent *event); + void selectItem(QListWidgetItem *item); + void exitSearchMode(bool restoreSelection=true); + void enterSearchMode(); + void refresh(); -public Q_SLOTS: - void handleSearchKeyChanged(const QString& updatedText); +public slots: void clear(); +private slots: + void handleSearchKeyChanged(const QString& updatedText); + +protected: + void keyPressEvent(QKeyEvent *p_event) Q_DECL_OVERRIDE; private: QLabel *m_label; @@ -100,10 +121,19 @@ private: bool m_isInSearch; VItemDelegate* m_delegateObj; - QList m_hitItems; // items that are matched by the search key - int m_hitCount; // how many items are matched, if no search key or key is empty string, all items are matched - int m_curItemIdx; // current selected item index - QListWidgetItem* m_curItem; // current selected item + + // Items that are matched by the search key. + QList m_hitItems; + + // How many items are matched, if no search key or key is empty string, + // all items are matched. + int m_hitCount; + + // Current selected item index. + int m_curItemIdx; + + // Current selected item. + QListWidgetItem* m_curItem; }; #endif // VLISTWIDGET_H From 0f6e9e19059f141eeb978b438b6793bb43f2f1f8 Mon Sep 17 00:00:00 2001 From: Le Tan Date: Fri, 2 Feb 2018 19:51:32 +0800 Subject: [PATCH 3/3] refactor VListWidget --- .../themes/v_moonlight/v_moonlight.palette | 6 +- src/resources/themes/v_pure/v_pure.palette | 6 +- src/resources/themes/v_white/v_white.palette | 6 +- src/resources/vnote.ini | 3 + src/src.pro | 8 +- src/utils/viconutils.h | 4 +- src/vconfigmanager.h | 8 + src/vfilelist.cpp | 14 +- src/vlineedit.cpp | 14 +- src/vlineedit.h | 13 + src/vlistwidget.cpp | 284 ++++++------------ src/vlistwidget.h | 132 ++------ src/vsimplesearchinput.cpp | 216 +++++++++++++ src/vsimplesearchinput.h | 89 ++++++ src/vstyleditemdelegate.cpp | 31 ++ src/vstyleditemdelegate.h | 50 +++ src/vvimcmdlineedit.cpp | 2 + 17 files changed, 570 insertions(+), 316 deletions(-) create mode 100644 src/vsimplesearchinput.cpp create mode 100644 src/vsimplesearchinput.h create mode 100644 src/vstyleditemdelegate.cpp create mode 100644 src/vstyleditemdelegate.h diff --git a/src/resources/themes/v_moonlight/v_moonlight.palette b/src/resources/themes/v_moonlight/v_moonlight.palette index d7722e2c..1fe3c1c0 100644 --- a/src/resources/themes/v_moonlight/v_moonlight.palette +++ b/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. diff --git a/src/resources/themes/v_pure/v_pure.palette b/src/resources/themes/v_pure/v_pure.palette index e89a182c..18deb20a 100644 --- a/src/resources/themes/v_pure/v_pure.palette +++ b/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. diff --git a/src/resources/themes/v_white/v_white.palette b/src/resources/themes/v_white/v_white.palette index 16fd1e60..f6e4abf3 100644 --- a/src/resources/themes/v_white/v_white.palette +++ b/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. diff --git a/src/resources/vnote.ini b/src/resources/vnote.ini index 005cd693..95c0a434 100644 --- a/src/resources/vnote.ini +++ b/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 diff --git a/src/src.pro b/src/src.pro index cd88541f..e3a40cd9 100644 --- a/src/src.pro +++ b/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 \ diff --git a/src/utils/viconutils.h b/src/utils/viconutils.h index 8a7422f7..e1b0b10a 100644 --- a/src/utils/viconutils.h +++ b/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) diff --git a/src/vconfigmanager.h b/src/vconfigmanager.h index 6e08f2ae..48185af4 100644 --- a/src/vconfigmanager.h +++ b/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 §ion, 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 diff --git a/src/vfilelist.cpp b/src/vfilelist.cpp index 08fe4013..7e8502bc 100644 --- a/src/vfilelist.cpp +++ b/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) { diff --git a/src/vlineedit.cpp b/src/vlineedit.cpp index f1616ced..79f43091 100644 --- a/src/vlineedit.cpp +++ b/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; } diff --git a/src/vlineedit.h b/src/vlineedit.h index 39f0c4b8..dc7349a8 100644 --- a/src/vlineedit.h +++ b/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 diff --git a/src/vlistwidget.cpp b/src/vlistwidget.cpp index 11a6a047..7198f2b8 100644 --- a/src/vlistwidget.cpp +++ b/src/vlistwidget.cpp @@ -3,217 +3,131 @@ #include #include #include -#include -#include +#include +#include -#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)); + m_searchInput = new VSimpleSearchInput(this, this); + connect(m_searchInput, &VSimpleSearchInput::triggered, + this, &VListWidget::handleSearchModeTriggered); - QGridLayout *mainLayout = new QGridLayout; - QHBoxLayout *searchRowLayout = new QHBoxLayout; - searchRowLayout->addWidget(m_label); - searchRowLayout->addWidget(m_searchKey); + m_searchInput->hide(); - 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_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); -void VListWidget::exitSearchMode(bool restoreSelection) -{ - m_searchKey->clear(); - m_label->hide(); - m_searchKey->hide(); - setSelectionMode(QAbstractItemView::ExtendedSelection); - if (restoreSelection && m_curItem) { - selectItem(m_curItem); - } -} - -void VListWidget::refresh() -{ - m_isInSearch = false; - m_hitItems = findItems("", Qt::MatchContains); - m_hitCount = m_hitItems.count(); - - for(const auto& it : selectedItems()) { - it->setSelected(false); - } - - 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; - } -} - -void VListWidget::clear() -{ QListWidget::clear(); - m_hitCount = 0; - m_hitItems.clear(); - m_isInSearch = false; - m_curItem = nullptr; - m_curItemIdx = 0; - exitSearchMode(); } -void VListWidget::selectItem(QListWidgetItem *item) +void VListWidget::setSearchInputVisible(bool p_visible) { - if (item) { - for(const auto& it : selectedItems()) { - it->setSelected(false); - } - setCurrentItem(item); + 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(); } } -void VListWidget::handleSearchKeyChanged(const QString& key) +QList VListWidget::searchItems(const QString &p_text, + Qt::MatchFlags p_flags) const { - m_delegateObj->setSearchKey(key); - // trigger repaint & update + QList items = findItems(p_text, p_flags); + + QList res; + res.reserve(items.size()); + for (int i = 0; i < items.size(); ++i) { + res.append(items[i]); + } + + return res; +} + +void VListWidget::highlightHitItems(const QList &p_items) +{ + clearItemsHighlight(); + + QSet hitIndexes; + for (auto it : p_items) { + QModelIndex index = indexFromItem(static_cast(it)); + if (index.isValid()) { + hitIndexes.insert(index); + } + } + + if (!hitIndexes.isEmpty()) { + m_delegate->setHitItems(hitIndexes); + update(); + } +} + +void VListWidget::clearItemsHighlight() +{ + 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(p_item), + QItemSelectionModel::ClearAndSelect); +} + +int VListWidget::totalNumberOfItems() +{ + return count(); } diff --git a/src/vlistwidget.h b/src/vlistwidget.h index c7df808e..ecf9a039 100644 --- a/src/vlistwidget.h +++ b/src/vlistwidget.h @@ -2,138 +2,48 @@ #define VLISTWIDGET_H #include -#include -#include -#include -#include -#include -#include -class VLineEdit; +#include "vsimplesearchinput.h" -class VItemDelegate : public QItemDelegate -{ -public: - explicit VItemDelegate(QObject *parent = Q_NULLPTR) - : QItemDelegate(parent), m_searchKey() - { - } +class VStyledItemDelegate; - 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; - } - -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(); - void exitSearchMode(bool restoreSelection=true); + // Implement ISimpleSearch. + virtual QList searchItems(const QString &p_text, + Qt::MatchFlags p_flags) const Q_DECL_OVERRIDE; - void enterSearchMode(); + virtual void highlightHitItems(const QList &p_items) Q_DECL_OVERRIDE; - void refresh(); + virtual void clearItemsHighlight() Q_DECL_OVERRIDE; -public slots: - void clear(); + virtual void selectHitItem(void *p_item) Q_DECL_OVERRIDE; + + 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; + void resizeEvent(QResizeEvent *p_event) Q_DECL_OVERRIDE; + private: - QLabel *m_label; - VLineEdit* m_searchKey; - bool m_isInSearch; + // Show or hide search input. + void setSearchInputVisible(bool p_visible); - VItemDelegate* m_delegateObj; + VSimpleSearchInput *m_searchInput; - // Items that are matched by the search key. - QList m_hitItems; - - // How many items are matched, if no search key or key is empty string, - // all items are matched. - int m_hitCount; - - // Current selected item index. - int m_curItemIdx; - - // Current selected item. - QListWidgetItem* m_curItem; + VStyledItemDelegate *m_delegate; }; #endif // VLISTWIDGET_H diff --git a/src/vsimplesearchinput.cpp b/src/vsimplesearchinput.cpp new file mode 100644 index 00000000..3b952003 --- /dev/null +++ b/src/vsimplesearchinput.cpp @@ -0,0 +1,216 @@ +#include "vsimplesearchinput.h" + +#include +#include +#include +#include +#include +#include + +#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(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)); +} diff --git a/src/vsimplesearchinput.h b/src/vsimplesearchinput.h new file mode 100644 index 00000000..87611ae8 --- /dev/null +++ b/src/vsimplesearchinput.h @@ -0,0 +1,89 @@ +#ifndef VSIMPLESEARCHINPUT_H +#define VSIMPLESEARCHINPUT_H + +#include +#include + +class VLineEdit; +class QLabel; + +class ISimpleSearch +{ +public: + // Return items matching the search. + virtual QList searchItems(const QString &p_text, + Qt::MatchFlags p_flags) const = 0; + + // Highlight hit items to denote the search result. + virtual void highlightHitItems(const QList &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 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 diff --git a/src/vstyleditemdelegate.cpp b/src/vstyleditemdelegate.cpp new file mode 100644 index 00000000..d0dd0a36 --- /dev/null +++ b/src/vstyleditemdelegate.cpp @@ -0,0 +1,31 @@ +#include "vstyleditemdelegate.h" + +#include +#include + +#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); + } +} diff --git a/src/vstyleditemdelegate.h b/src/vstyleditemdelegate.h new file mode 100644 index 00000000..a6c062ca --- /dev/null +++ b/src/vstyleditemdelegate.h @@ -0,0 +1,50 @@ +#ifndef VSTYLEDITEMDELEGATE_H +#define VSTYLEDITEMDELEGATE_H + +#include +#include +#include + + +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 &p_hitItems); + + void clearHitItems(); + +private: + bool isHit(const QModelIndex &p_index) const; + + QBrush m_itemHitBg; + + QBrush m_itemHitFg; + + QSet m_hitItems; +}; + +inline void VStyledItemDelegate::setHitItems(const QSet &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 diff --git a/src/vvimcmdlineedit.cpp b/src/vvimcmdlineedit.cpp index 4cacd867..99e67100 100644 --- a/src/vvimcmdlineedit.cpp +++ b/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)