Browse Source

add VTreeWidget with simple search and convert VDirectoryTree to it

Le Tan 7 years ago
parent
commit
878a272777

+ 0 - 27
src/dialog/vsortdialog.cpp

@@ -2,33 +2,6 @@
 
 #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,
                          const QString &p_info,
                          QWidget *p_parent)

+ 2 - 24
src/dialog/vsortdialog.h

@@ -3,33 +3,11 @@
 
 #include <QDialog>
 #include <QVector>
-#include <QTreeWidget>
+
+#include "vtreewidget.h"
 
 class QPushButton;
 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
 {

+ 4 - 2
src/src.pro

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

+ 0 - 9
src/utils/vutils.cpp

@@ -149,15 +149,6 @@ QString VUtils::generateImageFileName(const QString &path,
     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)
 {
     if (p_path.isEmpty()) {

+ 0 - 2
src/utils/vutils.h

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

+ 4 - 8
src/vdirectorytree.cpp

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

+ 3 - 2
src/vdirectorytree.h

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

+ 16 - 5
src/vlistwidget.cpp

@@ -1,6 +1,5 @@
 #include "vlistwidget.h"
 
-#include <QVBoxLayout>
 #include <QKeyEvent>
 #include <QCoreApplication>
 #include <QSet>
@@ -49,12 +48,12 @@ void VListWidget::setSearchInputVisible(bool p_visible)
 {
     m_searchInput->setVisible(p_visible);
 
-    int topMargin = 0;
+    int bottomMargin = 0;
     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)
@@ -68,8 +67,14 @@ void VListWidget::resizeEvent(QResizeEvent *p_event)
         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(),
-                                     rect.top(),
+                                     y,
                                      width,
                                      m_searchInput->height()));
 }
@@ -131,3 +136,9 @@ int VListWidget::totalNumberOfItems()
 {
     return count();
 }
+
+void VListWidget::selectNextItem(bool p_forward)
+{
+    Q_UNUSED(p_forward);
+    Q_ASSERT(false);
+}

+ 2 - 0
src/vlistwidget.h

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

+ 24 - 3
src/vsimplesearchinput.cpp

@@ -18,7 +18,8 @@ VSimpleSearchInput::VSimpleSearchInput(ISimpleSearch *p_obj, QWidget *p_parent)
       m_obj(p_obj),
       m_inSearchMode(false),
       m_currentIdx(-1),
-      m_wildCardEnabled(g_config->getEnableWildCardInSimpleSearch())
+      m_wildCardEnabled(g_config->getEnableWildCardInSimpleSearch()),
+      m_navigationKeyEnabled(false)
 {
     if (m_wildCardEnabled) {
         m_matchFlags = Qt::MatchWildcard | Qt::MatchWrap | Qt::MatchRecursive;
@@ -151,6 +152,23 @@ bool VSimpleSearchInput::tryHandleKeyPressEvent(QKeyEvent *p_event)
         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;
 }
 
@@ -181,13 +199,13 @@ bool VSimpleSearchInput::eventFilter(QObject *p_watched, QEvent *p_event)
 void VSimpleSearchInput::handleEditTextChanged(const QString &p_text)
 {
     if (!m_inSearchMode) {
-        return;
+        goto exit;
     }
 
     if (p_text.isEmpty()) {
         clearSearch();
         m_obj->selectHitItem(NULL);
-        return;
+        goto exit;
     }
 
     if (m_wildCardEnabled) {
@@ -208,6 +226,9 @@ void VSimpleSearchInput::handleEditTextChanged(const QString &p_text)
     m_currentIdx = m_hitItems.isEmpty() ? -1 : 0;
 
     m_obj->selectHitItem(currentItem());
+
+exit:
+    emit inputTextChanged(p_text);
 }
 
 void VSimpleSearchInput::updateInfoLabel(int p_nrHit, int p_total)

+ 15 - 0
src/vsimplesearchinput.h

@@ -26,6 +26,9 @@ public:
 
     // Get the total number of all the items.
     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.
     bool tryHandleKeyPressEvent(QKeyEvent *p_event);
 
+    void setNavigationKeyEnabled(bool p_enabled);
+
 signals:
     // Search mode is triggered.
     void triggered(bool p_inSearchMode);
 
+    void inputTextChanged(const QString &p_text);
+
 protected:
     bool eventFilter(QObject *p_watched, QEvent *p_event) Q_DECL_OVERRIDE;
 
@@ -76,6 +83,9 @@ private:
     Qt::MatchFlags m_matchFlags;
 
     bool m_wildCardEnabled;
+
+    // Down/Up/Ctrl+J/Ctrl+K to navigate.
+    bool m_navigationKeyEnabled;
 };
 
 inline void *VSimpleSearchInput::currentItem() const
@@ -86,4 +96,9 @@ inline void *VSimpleSearchInput::currentItem() const
 
     return NULL;
 }
+
+inline void VSimpleSearchInput::setNavigationKeyEnabled(bool p_enabled)
+{
+    m_navigationKeyEnabled = p_enabled;
+}
 #endif // VSIMPLESEARCHINPUT_H

+ 321 - 0
src/vtreewidget.cpp

@@ -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 - 0
src/vtreewidget.h

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