refactor VListWidget

This commit is contained in:
Le Tan 2018-02-02 19:51:32 +08:00
parent abe40cc74f
commit 0f6e9e1905
17 changed files with 570 additions and 316 deletions

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -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 \

View File

@ -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)

View File

@ -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

View File

@ -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) {

View File

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

View File

@ -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

View File

@ -3,217 +3,131 @@
#include <QVBoxLayout>
#include <QKeyEvent>
#include <QCoreApplication>
#include <QDebug>
#include <QLabel>
#include <QSet>
#include <QScrollBar>
#include "vlineedit.h"
#include "utils/vutils.h"
const QString searchPrefix("Search for: ");
//TODO: make the style configuable
const QString c_searchKeyStyle("border:none; background:#eaeaea; color:%1;");
const QString c_colorNotMatch("#fd676b");
const QString c_colorMatch("grey");
#include "utils/vimnavigationforwidget.h"
#include "vstyleditemdelegate.h"
VListWidget::VListWidget(QWidget *parent)
: QListWidget(parent),
m_isInSearch(false),
m_curItemIdx(-1),
m_curItem(nullptr)
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;
}
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);
if (m_searchInput->tryHandleKeyPressEvent(p_event)) {
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);
if (VimNavigationForWidget::injectKeyPressEventForVim(this, p_event)) {
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()
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);
m_searchInput->setVisible(p_visible);
int topMargin = 0;
if (p_visible) {
topMargin = m_searchInput->height();
}
setCurrentItem(item);
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<void *> VListWidget::searchItems(const QString &p_text,
Qt::MatchFlags p_flags) const
{
m_delegateObj->setSearchKey(key);
// trigger repaint & update
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();
}
}
m_hitItems = findItems(key, Qt::MatchContains);
if (key.isEmpty()) {
if (m_curItem) {
m_curItemIdx = m_hitItems.indexOf(m_curItem);
void VListWidget::clearItemsHighlight()
{
m_delegate->clearHitItems();
update();
}
} 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));
void VListWidget::selectHitItem(void *p_item)
{
setCurrentItem(static_cast<QListWidgetItem *>(p_item),
QItemSelectionModel::ClearAndSelect);
}
}
m_hitCount = m_hitItems.count();
int VListWidget::totalNumberOfItems()
{
return count();
}

View File

@ -2,138 +2,48 @@
#define VLISTWIDGET_H
#include <QListWidget>
#include <QPainter>
#include <QStyleOptionViewItem>
#include <QModelIndex>
#include <QItemDelegate>
#include <QDebug>
#include <QLabel>
class VLineEdit;
#include "vsimplesearchinput.h"
class VItemDelegate : public QItemDelegate
{
public:
explicit VItemDelegate(QObject *parent = Q_NULLPTR)
: QItemDelegate(parent), m_searchKey()
{
}
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<void *> searchItems(const QString &p_text,
Qt::MatchFlags p_flags) const Q_DECL_OVERRIDE;
void enterSearchMode();
virtual void highlightHitItems(const QList<void *> &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<QListWidgetItem*> 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

216
src/vsimplesearchinput.cpp Normal file
View File

@ -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
src/vsimplesearchinput.h Normal file
View File

@ -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

View File

@ -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
src/vstyleditemdelegate.h Normal file
View File

@ -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

View File

@ -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)