Merge branch 'list-search' into dev

Add simple search for list widget.
This commit is contained in:
Le Tan 2018-02-03 12:11:25 +08:00
commit 0125251716
18 changed files with 636 additions and 20 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

@ -108,7 +108,10 @@ SOURCES += main.cpp\
utils/vwebutils.cpp \
vlineedit.cpp \
vcart.cpp \
vvimcmdlineedit.cpp
vvimcmdlineedit.cpp \
vlistwidget.cpp \
vsimplesearchinput.cpp \
vstyleditemdelegate.cpp
HEADERS += vmainwindow.h \
vdirectorytree.h \
@ -203,7 +206,10 @@ HEADERS += vmainwindow.h \
utils/vwebutils.h \
vlineedit.h \
vcart.h \
vvimcmdlineedit.h
vvimcmdlineedit.h \
vlistwidget.h \
vsimplesearchinput.h \
vstyleditemdelegate.h
RESOURCES += \
vnote.qrc \

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"
@ -61,7 +60,7 @@ VFileList::VFileList(QWidget *parent)
void VFileList::setupUI()
{
fileList = new QListWidget(this);
fileList = new VListWidget(this);
fileList->setContextMenuPolicy(Qt::CustomContextMenu);
fileList->setSelectionMode(QAbstractItemView::ExtendedSelection);
fileList->setObjectName("FileList");
@ -202,7 +201,7 @@ void VFileList::setDirectory(VDirectory *p_directory)
// be NULL.
if (m_directory == p_directory) {
if (!m_directory) {
fileList->clear();
fileList->clearAll();
}
return;
@ -210,7 +209,7 @@ void VFileList::setDirectory(VDirectory *p_directory)
m_directory = p_directory;
if (!m_directory) {
fileList->clear();
fileList->clearAll();
return;
}
@ -219,7 +218,7 @@ void VFileList::setDirectory(VDirectory *p_directory)
void VFileList::updateFileList()
{
fileList->clear();
fileList->clearAll();
if (!m_directory->open()) {
return;
}
@ -921,11 +920,6 @@ void VFileList::pasteFiles(VDirectory *p_destDir,
void VFileList::keyPressEvent(QKeyEvent *p_event)
{
if (VimNavigationForWidget::injectKeyPressEventForVim(fileList,
p_event)) {
return;
}
if (p_event->key() == Qt::Key_Return) {
QListWidgetItem *item = fileList->currentItem();
if (item) {

View File

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

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

133
src/vlistwidget.cpp Normal file
View File

@ -0,0 +1,133 @@
#include "vlistwidget.h"
#include <QVBoxLayout>
#include <QKeyEvent>
#include <QCoreApplication>
#include <QSet>
#include <QScrollBar>
#include "utils/vutils.h"
#include "utils/vimnavigationforwidget.h"
#include "vstyleditemdelegate.h"
VListWidget::VListWidget(QWidget *parent)
: QListWidget(parent),
ISimpleSearch()
{
m_searchInput = new VSimpleSearchInput(this, this);
connect(m_searchInput, &VSimpleSearchInput::triggered,
this, &VListWidget::handleSearchModeTriggered);
m_searchInput->hide();
m_delegate = new VStyledItemDelegate(this);
setItemDelegate(m_delegate);
}
void VListWidget::keyPressEvent(QKeyEvent *p_event)
{
if (m_searchInput->tryHandleKeyPressEvent(p_event)) {
return;
}
if (VimNavigationForWidget::injectKeyPressEventForVim(this, p_event)) {
return;
}
QListWidget::keyPressEvent(p_event);
}
void VListWidget::clearAll()
{
m_searchInput->clear();
setSearchInputVisible(false);
QListWidget::clear();
}
void VListWidget::setSearchInputVisible(bool p_visible)
{
m_searchInput->setVisible(p_visible);
int topMargin = 0;
if (p_visible) {
topMargin = m_searchInput->height();
}
setViewportMargins(0, topMargin, 0, 0);
}
void VListWidget::resizeEvent(QResizeEvent *p_event)
{
QListWidget::resizeEvent(p_event);
QRect rect = contentsRect();
int width = rect.width();
QScrollBar *vbar = verticalScrollBar();
if (vbar && (vbar->minimum() != vbar->maximum())) {
width -= vbar->width();
}
m_searchInput->setGeometry(QRect(rect.left(),
rect.top(),
width,
m_searchInput->height()));
}
void VListWidget::handleSearchModeTriggered(bool p_inSearchMode)
{
setSearchInputVisible(p_inSearchMode);
if (!p_inSearchMode) {
clearItemsHighlight();
setFocus();
}
}
QList<void *> VListWidget::searchItems(const QString &p_text,
Qt::MatchFlags p_flags) const
{
QList<QListWidgetItem *> items = findItems(p_text, p_flags);
QList<void *> res;
res.reserve(items.size());
for (int i = 0; i < items.size(); ++i) {
res.append(items[i]);
}
return res;
}
void VListWidget::highlightHitItems(const QList<void *> &p_items)
{
clearItemsHighlight();
QSet<QModelIndex> hitIndexes;
for (auto it : p_items) {
QModelIndex index = indexFromItem(static_cast<QListWidgetItem *>(it));
if (index.isValid()) {
hitIndexes.insert(index);
}
}
if (!hitIndexes.isEmpty()) {
m_delegate->setHitItems(hitIndexes);
update();
}
}
void VListWidget::clearItemsHighlight()
{
m_delegate->clearHitItems();
update();
}
void VListWidget::selectHitItem(void *p_item)
{
setCurrentItem(static_cast<QListWidgetItem *>(p_item),
QItemSelectionModel::ClearAndSelect);
}
int VListWidget::totalNumberOfItems()
{
return count();
}

49
src/vlistwidget.h Normal file
View File

@ -0,0 +1,49 @@
#ifndef VLISTWIDGET_H
#define VLISTWIDGET_H
#include <QListWidget>
#include "vsimplesearchinput.h"
class VStyledItemDelegate;
class VListWidget : public QListWidget, public ISimpleSearch
{
public:
explicit VListWidget(QWidget *parent = Q_NULLPTR);
// Clear list widget as well as other data.
// clear() is not virtual to override.
void clearAll();
// Implement ISimpleSearch.
virtual QList<void *> searchItems(const QString &p_text,
Qt::MatchFlags p_flags) const Q_DECL_OVERRIDE;
virtual void highlightHitItems(const QList<void *> &p_items) Q_DECL_OVERRIDE;
virtual void clearItemsHighlight() Q_DECL_OVERRIDE;
virtual void selectHitItem(void *p_item) Q_DECL_OVERRIDE;
virtual int totalNumberOfItems() Q_DECL_OVERRIDE;
private slots:
void handleSearchModeTriggered(bool p_inSearchMode);
protected:
void keyPressEvent(QKeyEvent *p_event) Q_DECL_OVERRIDE;
void resizeEvent(QResizeEvent *p_event) Q_DECL_OVERRIDE;
private:
// Show or hide search input.
void setSearchInputVisible(bool p_visible);
VSimpleSearchInput *m_searchInput;
VStyledItemDelegate *m_delegate;
};
#endif // VLISTWIDGET_H

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