mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-05 13:59:52 +08:00
[function] support advanced search in file list (#121)
spec: when focus in file list, 1. type any character or digit will trigger the advanced search mode 2. type Esc to exit the search mode 3. type Enter or mouse select will also exit the search mode
This commit is contained in:
parent
77c664b773
commit
74cb54e02b
@ -108,7 +108,8 @@ SOURCES += main.cpp\
|
|||||||
utils/vwebutils.cpp \
|
utils/vwebutils.cpp \
|
||||||
vlineedit.cpp \
|
vlineedit.cpp \
|
||||||
vcart.cpp \
|
vcart.cpp \
|
||||||
vvimcmdlineedit.cpp
|
vvimcmdlineedit.cpp \
|
||||||
|
vlistwidget.cpp
|
||||||
|
|
||||||
HEADERS += vmainwindow.h \
|
HEADERS += vmainwindow.h \
|
||||||
vdirectorytree.h \
|
vdirectorytree.h \
|
||||||
@ -203,7 +204,8 @@ HEADERS += vmainwindow.h \
|
|||||||
utils/vwebutils.h \
|
utils/vwebutils.h \
|
||||||
vlineedit.h \
|
vlineedit.h \
|
||||||
vcart.h \
|
vcart.h \
|
||||||
vvimcmdlineedit.h
|
vvimcmdlineedit.h \
|
||||||
|
vlistwidget.h
|
||||||
|
|
||||||
RESOURCES += \
|
RESOURCES += \
|
||||||
vnote.qrc \
|
vnote.qrc \
|
||||||
|
@ -61,7 +61,7 @@ VFileList::VFileList(QWidget *parent)
|
|||||||
|
|
||||||
void VFileList::setupUI()
|
void VFileList::setupUI()
|
||||||
{
|
{
|
||||||
fileList = new QListWidget(this);
|
fileList = new VListWidget(this);
|
||||||
fileList->setContextMenuPolicy(Qt::CustomContextMenu);
|
fileList->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
fileList->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
fileList->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||||
fileList->setObjectName("FileList");
|
fileList->setObjectName("FileList");
|
||||||
@ -229,6 +229,7 @@ void VFileList::updateFileList()
|
|||||||
VNoteFile *file = files[i];
|
VNoteFile *file = files[i];
|
||||||
insertFileListItem(file);
|
insertFileListItem(file);
|
||||||
}
|
}
|
||||||
|
fileList->refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
void VFileList::fileInfo()
|
void VFileList::fileInfo()
|
||||||
@ -698,6 +699,7 @@ void VFileList::activateItem(QListWidgetItem *p_item, bool p_restoreFocus)
|
|||||||
|
|
||||||
// Qt seems not to update the QListWidget correctly. Manually force it to repaint.
|
// Qt seems not to update the QListWidget correctly. Manually force it to repaint.
|
||||||
fileList->update();
|
fileList->update();
|
||||||
|
fileList->exitSearchMode(false);
|
||||||
emit fileClicked(getVFile(p_item), g_config->getNoteOpenMode());
|
emit fileClicked(getVFile(p_item), g_config->getNoteOpenMode());
|
||||||
|
|
||||||
if (p_restoreFocus) {
|
if (p_restoreFocus) {
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
#include "vdirectory.h"
|
#include "vdirectory.h"
|
||||||
#include "vnotefile.h"
|
#include "vnotefile.h"
|
||||||
#include "vnavigationmode.h"
|
#include "vnavigationmode.h"
|
||||||
|
#include "vlistwidget.h"
|
||||||
|
|
||||||
class QAction;
|
class QAction;
|
||||||
class VNote;
|
class VNote;
|
||||||
@ -166,7 +167,7 @@ private:
|
|||||||
void activateItem(QListWidgetItem *p_item, bool p_restoreFocus = false);
|
void activateItem(QListWidgetItem *p_item, bool p_restoreFocus = false);
|
||||||
|
|
||||||
VEditArea *editArea;
|
VEditArea *editArea;
|
||||||
QListWidget *fileList;
|
VListWidget *fileList;
|
||||||
QPointer<VDirectory> m_directory;
|
QPointer<VDirectory> m_directory;
|
||||||
|
|
||||||
// Magic number for clipboard operations.
|
// Magic number for clipboard operations.
|
||||||
|
@ -12,7 +12,6 @@ public:
|
|||||||
|
|
||||||
VLineEdit(const QString &p_contents, QWidget *p_parent = nullptr);
|
VLineEdit(const QString &p_contents, QWidget *p_parent = nullptr);
|
||||||
|
|
||||||
protected:
|
|
||||||
void keyPressEvent(QKeyEvent *p_event) Q_DECL_OVERRIDE;
|
void keyPressEvent(QKeyEvent *p_event) Q_DECL_OVERRIDE;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
206
src/vlistwidget.cpp
Normal file
206
src/vlistwidget.cpp
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
#include "vlistwidget.h"
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <QKeyEvent>
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QLabel>
|
||||||
|
#include "utils/vutils.h"
|
||||||
|
|
||||||
|
const QString searchPrefix("Search for: ");
|
||||||
|
//TODO: make the style configuable
|
||||||
|
const QString c_searchKeyStyle("border:none; background:#eaeaea; color:%1;");
|
||||||
|
const QString c_colorNotMatch("#fd676b");
|
||||||
|
const QString c_colorMatch("grey");
|
||||||
|
|
||||||
|
VListWidget::VListWidget(QWidget *parent):QListWidget(parent), m_isInSearch(false),
|
||||||
|
m_curItemIdx(-1), m_curItem(nullptr)
|
||||||
|
{
|
||||||
|
m_label = new QLabel(searchPrefix, this);
|
||||||
|
//TODO: make the style configuable
|
||||||
|
m_label->setStyleSheet(QString("color:gray;font-weight:bold;"));
|
||||||
|
m_searchKey = new VLineEdit(this);
|
||||||
|
m_searchKey->setStyleSheet(c_searchKeyStyle.arg(c_colorMatch));
|
||||||
|
|
||||||
|
QGridLayout *mainLayout = new QGridLayout;
|
||||||
|
QHBoxLayout *searchRowLayout = new QHBoxLayout;
|
||||||
|
searchRowLayout->addWidget(m_label);
|
||||||
|
searchRowLayout->addWidget(m_searchKey);
|
||||||
|
|
||||||
|
mainLayout->addLayout(searchRowLayout, 0, 0, -1, 1, Qt::AlignBottom);
|
||||||
|
setLayout(mainLayout);
|
||||||
|
m_label->hide();
|
||||||
|
m_searchKey->hide();
|
||||||
|
|
||||||
|
connect(m_searchKey, &VLineEdit::textChanged,
|
||||||
|
this, &VListWidget::handleSearchKeyChanged);
|
||||||
|
|
||||||
|
m_delegateObj = new VItemDelegate(this);
|
||||||
|
setItemDelegate(m_delegateObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VListWidget::keyPressEvent(QKeyEvent *p_event)
|
||||||
|
{
|
||||||
|
bool accept = false;
|
||||||
|
int modifiers = p_event->modifiers();
|
||||||
|
|
||||||
|
if (!m_isInSearch) {
|
||||||
|
bool isChar = (p_event->key() >= Qt::Key_A && p_event->key() <= Qt::Key_Z)
|
||||||
|
&& (modifiers == Qt::NoModifier || modifiers == Qt::ShiftModifier);
|
||||||
|
bool isDigit = (p_event->key() >= Qt::Key_0 && p_event->key() <= Qt::Key_9)
|
||||||
|
&& (modifiers == Qt::NoModifier);
|
||||||
|
m_isInSearch = isChar || isDigit;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool moveUp = false;
|
||||||
|
switch (p_event->key()) {
|
||||||
|
case Qt::Key_J:
|
||||||
|
if (VUtils::isControlModifierForVim(modifiers)) {
|
||||||
|
// focus to next item/selection
|
||||||
|
QKeyEvent *targetEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Down, Qt::NoModifier);
|
||||||
|
QCoreApplication::postEvent(this, targetEvent);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Qt::Key_K:
|
||||||
|
if (VUtils::isControlModifierForVim(modifiers)) {
|
||||||
|
// focus to previous item/selection
|
||||||
|
QKeyEvent *targetEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Up, Qt::NoModifier);
|
||||||
|
QCoreApplication::postEvent(this, targetEvent);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Qt::Key_H:
|
||||||
|
if (VUtils::isControlModifierForVim(modifiers)) {
|
||||||
|
// Ctrl+H, delete one char
|
||||||
|
accept = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Qt::Key_F:
|
||||||
|
case Qt::Key_B:
|
||||||
|
// disable ctrl+f/b for the search key
|
||||||
|
accept = VUtils::isControlModifierForVim(modifiers);
|
||||||
|
break;
|
||||||
|
case Qt::Key_Escape:
|
||||||
|
m_isInSearch = false;
|
||||||
|
break;
|
||||||
|
case Qt::Key_Up:
|
||||||
|
moveUp = true;
|
||||||
|
// fall through
|
||||||
|
case Qt::Key_Down:
|
||||||
|
if (m_hitCount > 1) {
|
||||||
|
int newIdx = m_curItemIdx;
|
||||||
|
if (moveUp) {
|
||||||
|
newIdx = (newIdx - 1 + m_hitCount) % m_hitCount;
|
||||||
|
} else {
|
||||||
|
newIdx = (newIdx + 1) % m_hitCount;
|
||||||
|
}
|
||||||
|
if (newIdx != m_curItemIdx) {
|
||||||
|
if (m_curItemIdx != -1) {
|
||||||
|
m_hitItems[m_curItemIdx]->setSelected(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_curItemIdx = newIdx;
|
||||||
|
m_curItem = m_hitItems[m_curItemIdx];
|
||||||
|
selectItem(m_curItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
accept = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (m_isInSearch) {
|
||||||
|
enterSearchMode();
|
||||||
|
} else {
|
||||||
|
exitSearchMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!accept) {
|
||||||
|
if (m_isInSearch) {
|
||||||
|
m_searchKey->keyPressEvent(p_event);
|
||||||
|
} else {
|
||||||
|
QListWidget::keyPressEvent(p_event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VListWidget::enterSearchMode() {
|
||||||
|
m_label->show();
|
||||||
|
m_searchKey->show();
|
||||||
|
setSelectionMode(QAbstractItemView::SingleSelection);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VListWidget::exitSearchMode(bool restoreSelection) {
|
||||||
|
m_searchKey->clear();
|
||||||
|
m_label->hide();
|
||||||
|
m_searchKey->hide();
|
||||||
|
setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||||
|
if (restoreSelection && m_curItem) {
|
||||||
|
selectItem(m_curItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void VListWidget::refresh() {
|
||||||
|
m_isInSearch = false;
|
||||||
|
m_hitItems = findItems("", Qt::MatchContains);
|
||||||
|
m_hitCount = m_hitItems.count();
|
||||||
|
|
||||||
|
for(const auto& it : selectedItems()) {
|
||||||
|
it->setSelected(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_hitCount > 0) {
|
||||||
|
if (selectedItems().isEmpty()) {
|
||||||
|
m_curItemIdx = 0;
|
||||||
|
m_curItem = m_hitItems.first();
|
||||||
|
selectItem(m_curItem);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
m_curItemIdx = -1;
|
||||||
|
m_curItem = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VListWidget::clear() {
|
||||||
|
QListWidget::clear();
|
||||||
|
m_hitCount = 0;
|
||||||
|
m_hitItems.clear();
|
||||||
|
m_isInSearch = false;
|
||||||
|
m_curItem = nullptr;
|
||||||
|
m_curItemIdx = 0;
|
||||||
|
exitSearchMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VListWidget::selectItem(QListWidgetItem *item) {
|
||||||
|
if (item) {
|
||||||
|
for(const auto& it : selectedItems()) {
|
||||||
|
it->setSelected(false);
|
||||||
|
}
|
||||||
|
setCurrentItem(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VListWidget::handleSearchKeyChanged(const QString& key)
|
||||||
|
{
|
||||||
|
m_delegateObj->setSearchKey(key);
|
||||||
|
// trigger repaint & update
|
||||||
|
update();
|
||||||
|
|
||||||
|
m_hitItems = findItems(key, Qt::MatchContains);
|
||||||
|
if (key.isEmpty()) {
|
||||||
|
if (m_curItem) {
|
||||||
|
m_curItemIdx = m_hitItems.indexOf(m_curItem);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bool hasSearchResult = !m_hitItems.isEmpty();
|
||||||
|
if (hasSearchResult) {
|
||||||
|
m_searchKey->setStyleSheet(c_searchKeyStyle.arg(c_colorMatch));
|
||||||
|
|
||||||
|
m_curItem = m_hitItems[0];
|
||||||
|
setCurrentItem(m_curItem);
|
||||||
|
m_curItemIdx = 0;
|
||||||
|
} else {
|
||||||
|
m_searchKey->setStyleSheet(c_searchKeyStyle.arg(c_colorNotMatch));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_hitCount = m_hitItems.count();
|
||||||
|
}
|
109
src/vlistwidget.h
Normal file
109
src/vlistwidget.h
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
#ifndef VLISTWIDGET_H
|
||||||
|
#define VLISTWIDGET_H
|
||||||
|
|
||||||
|
#include <QListWidget>
|
||||||
|
#include <QLineEdit>
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QStyleOptionViewItem>
|
||||||
|
#include <QModelIndex>
|
||||||
|
#include <QItemDelegate>
|
||||||
|
#include "vlineedit.h"
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QLabel>
|
||||||
|
|
||||||
|
class VItemDelegate : public QItemDelegate
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit VItemDelegate(QObject *parent = Q_NULLPTR):QItemDelegate(parent), m_searchKey() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
|
||||||
|
painter->save();
|
||||||
|
QPainter::CompositionMode oldCompMode = painter->compositionMode();
|
||||||
|
// set background color
|
||||||
|
painter->setPen(QPen(Qt::NoPen));
|
||||||
|
if (option.state & QStyle::State_Selected) {
|
||||||
|
// TODO: make it configuable
|
||||||
|
painter->setBrush(QBrush(QColor("#d3d3d3")));
|
||||||
|
} else {
|
||||||
|
// use default brush
|
||||||
|
}
|
||||||
|
painter->drawRect(option.rect);
|
||||||
|
|
||||||
|
Qt::GlobalColor hitPenColor = Qt::blue;
|
||||||
|
Qt::GlobalColor normalPenColor = Qt::black;
|
||||||
|
|
||||||
|
// set text color
|
||||||
|
QVariant value = index.data(Qt::DisplayRole);
|
||||||
|
QRectF rect(option.rect), boundRect;
|
||||||
|
if (value.isValid()) {
|
||||||
|
QString text = value.toString();
|
||||||
|
int idx;
|
||||||
|
bool isHit = !m_searchKey.isEmpty() && (idx=text.indexOf(m_searchKey, 0, Qt::CaseInsensitive)) != -1;
|
||||||
|
if (isHit) {
|
||||||
|
qDebug() << QString("highlight: %1 (with: %2)").arg(text).arg(m_searchKey);
|
||||||
|
// split the text by the search key
|
||||||
|
QString left = text.left(idx), right = text.mid(idx + m_searchKey.length());
|
||||||
|
drawText(painter, normalPenColor, rect, Qt::AlignLeft, left, boundRect);
|
||||||
|
drawText(painter, hitPenColor, rect, Qt::AlignLeft, m_searchKey, boundRect);
|
||||||
|
|
||||||
|
// highlight matched keyword
|
||||||
|
painter->setBrush(QBrush(QColor("#ffde7b")));
|
||||||
|
painter->setCompositionMode(QPainter::CompositionMode_Multiply);
|
||||||
|
painter->setPen(Qt::NoPen);
|
||||||
|
painter->drawRect(boundRect);
|
||||||
|
painter->setCompositionMode(oldCompMode);
|
||||||
|
|
||||||
|
drawText(painter, normalPenColor, rect, Qt::AlignLeft, right, boundRect);
|
||||||
|
} else {
|
||||||
|
drawText(painter, normalPenColor, rect, Qt::AlignLeft, text, boundRect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
painter->restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawText(QPainter *painter, Qt::GlobalColor penColor, QRectF& rect, int flags, QString text, QRectF& boundRect) const {
|
||||||
|
if (!text.isEmpty()) {
|
||||||
|
painter->setPen(QPen(penColor));
|
||||||
|
painter->drawText(rect, flags, text, &boundRect);
|
||||||
|
rect.adjust(boundRect.width(), 0, boundRect.width(), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setSearchKey(const QString& key) {
|
||||||
|
m_searchKey = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString m_searchKey;
|
||||||
|
};
|
||||||
|
|
||||||
|
class VListWidget : public QListWidget
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit VListWidget(QWidget *parent = Q_NULLPTR);
|
||||||
|
void keyPressEvent(QKeyEvent *event);
|
||||||
|
void selectItem(QListWidgetItem *item);
|
||||||
|
void exitSearchMode(bool restoreSelection=true);
|
||||||
|
void enterSearchMode();
|
||||||
|
void refresh();
|
||||||
|
|
||||||
|
public Q_SLOTS:
|
||||||
|
void handleSearchKeyChanged(const QString& updatedText);
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
QLabel *m_label;
|
||||||
|
VLineEdit* m_searchKey;
|
||||||
|
bool m_isInSearch;
|
||||||
|
|
||||||
|
VItemDelegate* m_delegateObj;
|
||||||
|
QList<QListWidgetItem*> m_hitItems; // items that are matched by the search key
|
||||||
|
int m_hitCount; // how many items are matched, if no search key or key is empty string, all items are matched
|
||||||
|
int m_curItemIdx; // current selected item index
|
||||||
|
QListWidgetItem* m_curItem; // current selected item
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // VLISTWIDGET_H
|
Loading…
x
Reference in New Issue
Block a user