From 0f6e9e19059f141eeb978b438b6793bb43f2f1f8 Mon Sep 17 00:00:00 2001 From: Le Tan Date: Fri, 2 Feb 2018 19:51:32 +0800 Subject: [PATCH] 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)