mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-05 13:59:52 +08:00
refactor VListWidget
This commit is contained in:
parent
abe40cc74f
commit
0f6e9e1905
@ -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.
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 \
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -5,12 +5,12 @@
|
||||
#include "utils/vutils.h"
|
||||
|
||||
VLineEdit::VLineEdit(QWidget *p_parent)
|
||||
: QLineEdit(p_parent)
|
||||
: QLineEdit(p_parent), m_ctrlKEnabled(true)
|
||||
{
|
||||
}
|
||||
|
||||
VLineEdit::VLineEdit(const QString &p_contents, QWidget *p_parent)
|
||||
: QLineEdit(p_contents, p_parent)
|
||||
: QLineEdit(p_contents, p_parent), m_ctrlKEnabled(true)
|
||||
{
|
||||
}
|
||||
|
||||
@ -64,6 +64,16 @@ void VLineEdit::keyPressEvent(QKeyEvent *p_event)
|
||||
break;
|
||||
}
|
||||
|
||||
case Qt::Key_K:
|
||||
{
|
||||
if (VUtils::isControlModifierForVim(modifiers) && !m_ctrlKEnabled) {
|
||||
QWidget::keyPressEvent(p_event);
|
||||
accept = true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -12,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
|
||||
|
@ -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)
|
||||
: 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<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();
|
||||
}
|
||||
}
|
||||
|
||||
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<QListWidgetItem *>(p_item),
|
||||
QItemSelectionModel::ClearAndSelect);
|
||||
}
|
||||
|
||||
int VListWidget::totalNumberOfItems()
|
||||
{
|
||||
return count();
|
||||
}
|
||||
|
@ -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
216
src/vsimplesearchinput.cpp
Normal 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
89
src/vsimplesearchinput.h
Normal 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
|
31
src/vstyleditemdelegate.cpp
Normal file
31
src/vstyleditemdelegate.cpp
Normal 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
50
src/vstyleditemdelegate.h
Normal 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
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user