add VTreeWidget with simple search and convert VDirectoryTree to it

This commit is contained in:
Le Tan 2018-02-03 16:33:00 +08:00
parent 0125251716
commit 878a272777
13 changed files with 459 additions and 82 deletions

View File

@ -2,33 +2,6 @@
#include <QtWidgets> #include <QtWidgets>
void VTreeWidget::dropEvent(QDropEvent *p_event)
{
QList<QTreeWidgetItem *> dragItems = selectedItems();
int first = -1, last = -1;
QTreeWidgetItem *firstItem = NULL;
for (int i = 0; i < dragItems.size(); ++i) {
int row = indexFromItem(dragItems[i]).row();
if (row > last) {
last = row;
}
if (first == -1 || row < first) {
first = row;
firstItem = dragItems[i];
}
}
Q_ASSERT(firstItem);
QTreeWidget::dropEvent(p_event);
int target = indexFromItem(firstItem).row();
emit rowsMoved(first, last, target);
}
VSortDialog::VSortDialog(const QString &p_title, VSortDialog::VSortDialog(const QString &p_title,
const QString &p_info, const QString &p_info,
QWidget *p_parent) QWidget *p_parent)

View File

@ -3,33 +3,11 @@
#include <QDialog> #include <QDialog>
#include <QVector> #include <QVector>
#include <QTreeWidget>
#include "vtreewidget.h"
class QPushButton; class QPushButton;
class QDialogButtonBox; class QDialogButtonBox;
class QTreeWidget;
class QDropEvent;
// QTreeWidget won't emit the rowsMoved() signal after drag-and-drop.
// VTreeWidget will emit rowsMoved() signal.
class VTreeWidget : public QTreeWidget
{
Q_OBJECT
public:
explicit VTreeWidget(QWidget *p_parent = 0)
: QTreeWidget(p_parent)
{
setAttribute(Qt::WA_MacShowFocusRect, false);
}
protected:
void dropEvent(QDropEvent *p_event) Q_DECL_OVERRIDE;
signals:
// Rows [@p_first, @p_last] were moved to @p_row.
void rowsMoved(int p_first, int p_last, int p_row);
};
class VSortDialog : public QDialog class VSortDialog : public QDialog
{ {

View File

@ -111,7 +111,8 @@ SOURCES += main.cpp\
vvimcmdlineedit.cpp \ vvimcmdlineedit.cpp \
vlistwidget.cpp \ vlistwidget.cpp \
vsimplesearchinput.cpp \ vsimplesearchinput.cpp \
vstyleditemdelegate.cpp vstyleditemdelegate.cpp \
vtreewidget.cpp
HEADERS += vmainwindow.h \ HEADERS += vmainwindow.h \
vdirectorytree.h \ vdirectorytree.h \
@ -209,7 +210,8 @@ HEADERS += vmainwindow.h \
vvimcmdlineedit.h \ vvimcmdlineedit.h \
vlistwidget.h \ vlistwidget.h \
vsimplesearchinput.h \ vsimplesearchinput.h \
vstyleditemdelegate.h vstyleditemdelegate.h \
vtreewidget.h
RESOURCES += \ RESOURCES += \
vnote.qrc \ vnote.qrc \

View File

@ -149,15 +149,6 @@ QString VUtils::generateImageFileName(const QString &path,
return imageName; return imageName;
} }
void VUtils::processStyle(QString &style, const QVector<QPair<QString, QString> > &varMap)
{
// Process style
for (int i = 0; i < varMap.size(); ++i) {
const QPair<QString, QString> &map = varMap[i];
style.replace("@" + map.first, map.second);
}
}
QString VUtils::fileNameFromPath(const QString &p_path) QString VUtils::fileNameFromPath(const QString &p_path)
{ {
if (p_path.isEmpty()) { if (p_path.isEmpty()) {

View File

@ -104,8 +104,6 @@ public:
static QString generateCopiedDirName(const QString &p_parentDirPath, static QString generateCopiedDirName(const QString &p_parentDirPath,
const QString &p_dirName); const QString &p_dirName);
static void processStyle(QString &style, const QVector<QPair<QString, QString> > &varMap);
// Return the last directory name of @p_path. // Return the last directory name of @p_path.
static QString directoryNameFromPath(const QString& p_path); static QString directoryNameFromPath(const QString& p_path);

View File

@ -25,13 +25,13 @@ const QString VDirectoryTree::c_cutShortcutSequence = "Ctrl+X";
const QString VDirectoryTree::c_pasteShortcutSequence = "Ctrl+V"; const QString VDirectoryTree::c_pasteShortcutSequence = "Ctrl+V";
VDirectoryTree::VDirectoryTree(QWidget *parent) VDirectoryTree::VDirectoryTree(QWidget *parent)
: QTreeWidget(parent), VNavigationMode(), : VTreeWidget(parent),
VNavigationMode(),
m_editArea(NULL) m_editArea(NULL)
{ {
setColumnCount(1); setColumnCount(1);
setHeaderHidden(true); setHeaderHidden(true);
setContextMenuPolicy(Qt::CustomContextMenu); setContextMenuPolicy(Qt::CustomContextMenu);
setAttribute(Qt::WA_MacShowFocusRect, false);
initShortcuts(); initShortcuts();
initActions(); initActions();
@ -939,15 +939,11 @@ void VDirectoryTree::mousePressEvent(QMouseEvent *event)
setCurrentItem(NULL); setCurrentItem(NULL);
} }
QTreeWidget::mousePressEvent(event); VTreeWidget::mousePressEvent(event);
} }
void VDirectoryTree::keyPressEvent(QKeyEvent *event) void VDirectoryTree::keyPressEvent(QKeyEvent *event)
{ {
if (VimNavigationForWidget::injectKeyPressEventForVim(this, event)) {
return;
}
int key = event->key(); int key = event->key();
int modifiers = event->modifiers(); int modifiers = event->modifiers();
@ -980,7 +976,7 @@ void VDirectoryTree::keyPressEvent(QKeyEvent *event)
break; break;
} }
QTreeWidget::keyPressEvent(event); VTreeWidget::keyPressEvent(event);
} }
QTreeWidgetItem *VDirectoryTree::findVDirectory(const VDirectory *p_dir, bool *p_widget) QTreeWidgetItem *VDirectoryTree::findVDirectory(const VDirectory *p_dir, bool *p_widget)

View File

@ -1,13 +1,14 @@
#ifndef VDIRECTORYTREE_H #ifndef VDIRECTORYTREE_H
#define VDIRECTORYTREE_H #define VDIRECTORYTREE_H
#include <QTreeWidget>
#include <QJsonObject> #include <QJsonObject>
#include <QPointer> #include <QPointer>
#include <QVector> #include <QVector>
#include <QMap> #include <QMap>
#include <QList> #include <QList>
#include <QHash> #include <QHash>
#include "vtreewidget.h"
#include "vdirectory.h" #include "vdirectory.h"
#include "vnotebook.h" #include "vnotebook.h"
#include "vnavigationmode.h" #include "vnavigationmode.h"
@ -16,7 +17,7 @@
class VEditArea; class VEditArea;
class QLabel; class QLabel;
class VDirectoryTree : public QTreeWidget, public VNavigationMode class VDirectoryTree : public VTreeWidget, public VNavigationMode
{ {
Q_OBJECT Q_OBJECT
public: public:

View File

@ -1,6 +1,5 @@
#include "vlistwidget.h" #include "vlistwidget.h"
#include <QVBoxLayout>
#include <QKeyEvent> #include <QKeyEvent>
#include <QCoreApplication> #include <QCoreApplication>
#include <QSet> #include <QSet>
@ -49,12 +48,12 @@ void VListWidget::setSearchInputVisible(bool p_visible)
{ {
m_searchInput->setVisible(p_visible); m_searchInput->setVisible(p_visible);
int topMargin = 0; int bottomMargin = 0;
if (p_visible) { if (p_visible) {
topMargin = m_searchInput->height(); bottomMargin = m_searchInput->height();
} }
setViewportMargins(0, topMargin, 0, 0); setViewportMargins(0, 0, 0, bottomMargin);
} }
void VListWidget::resizeEvent(QResizeEvent *p_event) void VListWidget::resizeEvent(QResizeEvent *p_event)
@ -68,8 +67,14 @@ void VListWidget::resizeEvent(QResizeEvent *p_event)
width -= vbar->width(); width -= vbar->width();
} }
int y = rect.bottom() - m_searchInput->height();
QScrollBar *hbar = horizontalScrollBar();
if (hbar && (hbar->minimum() != hbar->maximum())) {
y -= hbar->height();
}
m_searchInput->setGeometry(QRect(rect.left(), m_searchInput->setGeometry(QRect(rect.left(),
rect.top(), y,
width, width,
m_searchInput->height())); m_searchInput->height()));
} }
@ -131,3 +136,9 @@ int VListWidget::totalNumberOfItems()
{ {
return count(); return count();
} }
void VListWidget::selectNextItem(bool p_forward)
{
Q_UNUSED(p_forward);
Q_ASSERT(false);
}

View File

@ -29,6 +29,8 @@ public:
virtual int totalNumberOfItems() Q_DECL_OVERRIDE; virtual int totalNumberOfItems() Q_DECL_OVERRIDE;
virtual void selectNextItem(bool p_forward) Q_DECL_OVERRIDE;
private slots: private slots:
void handleSearchModeTriggered(bool p_inSearchMode); void handleSearchModeTriggered(bool p_inSearchMode);

View File

@ -18,7 +18,8 @@ VSimpleSearchInput::VSimpleSearchInput(ISimpleSearch *p_obj, QWidget *p_parent)
m_obj(p_obj), m_obj(p_obj),
m_inSearchMode(false), m_inSearchMode(false),
m_currentIdx(-1), m_currentIdx(-1),
m_wildCardEnabled(g_config->getEnableWildCardInSimpleSearch()) m_wildCardEnabled(g_config->getEnableWildCardInSimpleSearch()),
m_navigationKeyEnabled(false)
{ {
if (m_wildCardEnabled) { if (m_wildCardEnabled) {
m_matchFlags = Qt::MatchWildcard | Qt::MatchWrap | Qt::MatchRecursive; m_matchFlags = Qt::MatchWildcard | Qt::MatchWrap | Qt::MatchRecursive;
@ -151,6 +152,23 @@ bool VSimpleSearchInput::tryHandleKeyPressEvent(QKeyEvent *p_event)
return true; return true;
} }
// Up/Down Ctrl+K/J to navigate to next item.
// QTreeWidget may not response to the key event if it does not have the focus.
if (m_inSearchMode && m_navigationKeyEnabled) {
if (key == Qt::Key_Down
|| key == Qt::Key_Up
|| (VUtils::isControlModifierForVim(modifiers)
&& (key == Qt::Key_J || key == Qt::Key_K))) {
bool forward = true;
if (key == Qt::Key_Up || key == Qt::Key_K) {
forward = false;
}
m_obj->selectNextItem(forward);
return true;
}
}
return false; return false;
} }
@ -181,13 +199,13 @@ bool VSimpleSearchInput::eventFilter(QObject *p_watched, QEvent *p_event)
void VSimpleSearchInput::handleEditTextChanged(const QString &p_text) void VSimpleSearchInput::handleEditTextChanged(const QString &p_text)
{ {
if (!m_inSearchMode) { if (!m_inSearchMode) {
return; goto exit;
} }
if (p_text.isEmpty()) { if (p_text.isEmpty()) {
clearSearch(); clearSearch();
m_obj->selectHitItem(NULL); m_obj->selectHitItem(NULL);
return; goto exit;
} }
if (m_wildCardEnabled) { if (m_wildCardEnabled) {
@ -208,6 +226,9 @@ void VSimpleSearchInput::handleEditTextChanged(const QString &p_text)
m_currentIdx = m_hitItems.isEmpty() ? -1 : 0; m_currentIdx = m_hitItems.isEmpty() ? -1 : 0;
m_obj->selectHitItem(currentItem()); m_obj->selectHitItem(currentItem());
exit:
emit inputTextChanged(p_text);
} }
void VSimpleSearchInput::updateInfoLabel(int p_nrHit, int p_total) void VSimpleSearchInput::updateInfoLabel(int p_nrHit, int p_total)

View File

@ -26,6 +26,9 @@ public:
// Get the total number of all the items. // Get the total number of all the items.
virtual int totalNumberOfItems() = 0; virtual int totalNumberOfItems() = 0;
// Select next item.
virtual void selectNextItem(bool p_forward) = 0;
}; };
@ -42,10 +45,14 @@ public:
// Return true if @p_event is consumed and do not need further process. // Return true if @p_event is consumed and do not need further process.
bool tryHandleKeyPressEvent(QKeyEvent *p_event); bool tryHandleKeyPressEvent(QKeyEvent *p_event);
void setNavigationKeyEnabled(bool p_enabled);
signals: signals:
// Search mode is triggered. // Search mode is triggered.
void triggered(bool p_inSearchMode); void triggered(bool p_inSearchMode);
void inputTextChanged(const QString &p_text);
protected: protected:
bool eventFilter(QObject *p_watched, QEvent *p_event) Q_DECL_OVERRIDE; bool eventFilter(QObject *p_watched, QEvent *p_event) Q_DECL_OVERRIDE;
@ -76,6 +83,9 @@ private:
Qt::MatchFlags m_matchFlags; Qt::MatchFlags m_matchFlags;
bool m_wildCardEnabled; bool m_wildCardEnabled;
// Down/Up/Ctrl+J/Ctrl+K to navigate.
bool m_navigationKeyEnabled;
}; };
inline void *VSimpleSearchInput::currentItem() const inline void *VSimpleSearchInput::currentItem() const
@ -86,4 +96,9 @@ inline void *VSimpleSearchInput::currentItem() const
return NULL; return NULL;
} }
inline void VSimpleSearchInput::setNavigationKeyEnabled(bool p_enabled)
{
m_navigationKeyEnabled = p_enabled;
}
#endif // VSIMPLESEARCHINPUT_H #endif // VSIMPLESEARCHINPUT_H

321
src/vtreewidget.cpp Normal file
View File

@ -0,0 +1,321 @@
#include "vtreewidget.h"
#include <QDropEvent>
#include <QKeyEvent>
#include <QCoreApplication>
#include <QSet>
#include <QScrollBar>
#include <QDebug>
#include <QGraphicsOpacityEffect>
#include <QTimer>
#include "utils/vutils.h"
#include "utils/vimnavigationforwidget.h"
#include "vstyleditemdelegate.h"
#define SEARCH_INPUT_NORMAL_OPACITY 0.8
#define SEARCH_INPUT_IDLE_OPACITY 0.2
VTreeWidget::VTreeWidget(QWidget *p_parent)
: QTreeWidget(p_parent),
ISimpleSearch()
{
setAttribute(Qt::WA_MacShowFocusRect, false);
m_searchInput = new VSimpleSearchInput(this, this);
m_searchInput->setNavigationKeyEnabled(true);
connect(m_searchInput, &VSimpleSearchInput::triggered,
this, &VTreeWidget::handleSearchModeTriggered);
connect(m_searchInput, &VSimpleSearchInput::inputTextChanged,
this, &VTreeWidget::handleSearchInputTextChanged);
QGraphicsOpacityEffect * effect = new QGraphicsOpacityEffect(m_searchInput);
effect->setOpacity(SEARCH_INPUT_NORMAL_OPACITY);
m_searchInput->setGraphicsEffect(effect);
m_searchInput->hide();
m_searchColdTimer = new QTimer(this);
m_searchColdTimer->setSingleShot(true);
m_searchColdTimer->setInterval(1000);
connect(m_searchColdTimer, &QTimer::timeout,
this, [this]() {
QGraphicsOpacityEffect *effect = getSearchInputEffect();
Q_ASSERT(effect);
effect->setOpacity(SEARCH_INPUT_IDLE_OPACITY);
});
m_delegate = new VStyledItemDelegate(this);
setItemDelegate(m_delegate);
}
void VTreeWidget::keyPressEvent(QKeyEvent *p_event)
{
if (m_searchInput->tryHandleKeyPressEvent(p_event)) {
return;
}
if (VimNavigationForWidget::injectKeyPressEventForVim(this, p_event)) {
return;
}
QTreeWidget::keyPressEvent(p_event);
}
void VTreeWidget::clearAll()
{
m_searchInput->clear();
setSearchInputVisible(false);
VTreeWidget::clear();
}
void VTreeWidget::setSearchInputVisible(bool p_visible)
{
m_searchInput->setVisible(p_visible);
// setViewportMargins() and setContentsMargins() do not work for QTreeWidget.
// setStyleSheet(QString("padding-bottom: %1px").arg(bottomMargin));
QGraphicsOpacityEffect *effect = getSearchInputEffect();
Q_ASSERT(effect);
effect->setOpacity(SEARCH_INPUT_NORMAL_OPACITY);
}
void VTreeWidget::resizeEvent(QResizeEvent *p_event)
{
QTreeWidget::resizeEvent(p_event);
QRect contentRect = contentsRect();
int width = contentRect.width();
QScrollBar *vbar = verticalScrollBar();
if (vbar && (vbar->minimum() != vbar->maximum())) {
width -= vbar->width();
}
int y = height() - m_searchInput->height();
QScrollBar *hbar = horizontalScrollBar();
if (hbar && (hbar->minimum() != hbar->maximum())) {
y -= hbar->height();
}
m_searchInput->setGeometry(QRect(contentRect.left(),
y,
width,
m_searchInput->height()));
}
void VTreeWidget::handleSearchModeTriggered(bool p_inSearchMode)
{
setSearchInputVisible(p_inSearchMode);
if (!p_inSearchMode) {
clearItemsHighlight();
setFocus();
QTreeWidgetItem *item = currentItem();
if (item) {
setCurrentItem(item);
} else if (topLevelItemCount() > 0) {
setCurrentItem(topLevelItem(0));
}
}
}
void VTreeWidget::dropEvent(QDropEvent *p_event)
{
QList<QTreeWidgetItem *> dragItems = selectedItems();
int first = -1, last = -1;
QTreeWidgetItem *firstItem = NULL;
for (int i = 0; i < dragItems.size(); ++i) {
int row = indexFromItem(dragItems[i]).row();
if (row > last) {
last = row;
}
if (first == -1 || row < first) {
first = row;
firstItem = dragItems[i];
}
}
Q_ASSERT(firstItem);
QTreeWidget::dropEvent(p_event);
int target = indexFromItem(firstItem).row();
emit rowsMoved(first, last, target);
}
QList<void *> VTreeWidget::searchItems(const QString &p_text,
Qt::MatchFlags p_flags) const
{
QList<QTreeWidgetItem *> 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 VTreeWidget::highlightHitItems(const QList<void *> &p_items)
{
clearItemsHighlight();
QSet<QModelIndex> hitIndexes;
for (auto it : p_items) {
QModelIndex index = indexFromItem(static_cast<QTreeWidgetItem *>(it));
if (index.isValid()) {
hitIndexes.insert(index);
}
}
if (!hitIndexes.isEmpty()) {
m_delegate->setHitItems(hitIndexes);
update();
}
}
void VTreeWidget::clearItemsHighlight()
{
m_delegate->clearHitItems();
update();
}
void VTreeWidget::selectHitItem(void *p_item)
{
setCurrentItem(static_cast<QTreeWidgetItem *>(p_item),
QItemSelectionModel::ClearAndSelect);
}
// Count the total number of tree @p_item.
static int treeItemCount(QTreeWidgetItem *p_item)
{
if (!p_item) {
return 0;
}
int child = p_item->childCount();
int total = 1;
for (int i = 0; i < child; ++i) {
total += treeItemCount(p_item->child(i));
}
return total;
}
int VTreeWidget::totalNumberOfItems()
{
int total = 0;
int cn = topLevelItemCount();
for (int i = 0; i < cn; ++i) {
total += treeItemCount(topLevelItem(i));
}
return total;
}
void VTreeWidget::handleSearchInputTextChanged(const QString &p_text)
{
m_searchColdTimer->stop();
m_searchColdTimer->start();
Q_UNUSED(p_text);
QGraphicsOpacityEffect *effect = getSearchInputEffect();
Q_ASSERT(effect);
effect->setOpacity(0.8);
}
QGraphicsOpacityEffect *VTreeWidget::getSearchInputEffect() const
{
return static_cast<QGraphicsOpacityEffect *>(m_searchInput->graphicsEffect());
}
static QTreeWidgetItem *lastItemOfTree(QTreeWidgetItem *p_item)
{
if (p_item->isExpanded()) {
Q_ASSERT(p_item->childCount() > 0);
return p_item->child(p_item->childCount() - 1);
} else {
return p_item;
}
}
QTreeWidgetItem *VTreeWidget::nextSibling(QTreeWidgetItem *p_item, bool p_forward)
{
if (!p_item) {
return NULL;
}
QTreeWidgetItem *pa = p_item->parent();
if (pa) {
int idx = pa->indexOfChild(p_item);
if (p_forward) {
++idx;
if (idx >= pa->childCount()) {
return NULL;
}
} else {
--idx;
if (idx < 0) {
return NULL;
}
}
return pa->child(idx);
} else {
// Top level item.
int idx = indexOfTopLevelItem(p_item);
if (p_forward) {
++idx;
if (idx >= topLevelItemCount()) {
return NULL;
}
} else {
--idx;
if (idx < 0) {
return NULL;
}
}
return topLevelItem(idx);
}
}
void VTreeWidget::selectNextItem(bool p_forward)
{
if (topLevelItemCount() == 0) {
return;
}
QTreeWidgetItem *item = currentItem();
if (!item) {
setCurrentItem(topLevelItem(0));
return;
}
QTreeWidgetItem *nextItem = NULL;
if (p_forward) {
if (item->isExpanded()) {
nextItem = item->child(0);
} else {
while (!nextItem && item) {
nextItem = nextSibling(item, true);
item = item->parent();
}
}
} else {
nextItem = nextSibling(item, false);
if (!nextItem) {
nextItem = item->parent();
} else {
nextItem = lastItemOfTree(nextItem);
}
}
if (nextItem) {
setCurrentItem(nextItem);
}
}

68
src/vtreewidget.h Normal file
View File

@ -0,0 +1,68 @@
#ifndef VTREEWIDGET_H
#define VTREEWIDGET_H
#include <QTreeWidget>
#include "vsimplesearchinput.h"
class QDropEvent;
class VStyledItemDelegate;
class QTimer;
class QGraphicsOpacityEffect;
// QTreeWidget won't emit the rowsMoved() signal after drag-and-drop.
// VTreeWidget will emit rowsMoved() signal.
class VTreeWidget : public QTreeWidget, public ISimpleSearch
{
Q_OBJECT
public:
explicit VTreeWidget(QWidget *p_parent = nullptr);
// Clear tree widget as well as other data.
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;
virtual void selectNextItem(bool p_forward) Q_DECL_OVERRIDE;
protected:
void keyPressEvent(QKeyEvent *p_event) Q_DECL_OVERRIDE;
void resizeEvent(QResizeEvent *p_event) Q_DECL_OVERRIDE;
void dropEvent(QDropEvent *p_event) Q_DECL_OVERRIDE;
signals:
// Rows [@p_first, @p_last] were moved to @p_row.
void rowsMoved(int p_first, int p_last, int p_row);
private slots:
void handleSearchModeTriggered(bool p_inSearchMode);
void handleSearchInputTextChanged(const QString &p_text);
private:
// Show or hide search input.
void setSearchInputVisible(bool p_visible);
QGraphicsOpacityEffect *getSearchInputEffect() const;
QTreeWidgetItem *nextSibling(QTreeWidgetItem *p_item, bool p_forward);
VSimpleSearchInput *m_searchInput;
VStyledItemDelegate *m_delegate;
QTimer *m_searchColdTimer;
};
#endif // VTREEWIDGET_H