diff --git a/src/dialog/vsortdialog.cpp b/src/dialog/vsortdialog.cpp index 74f37790..21080fb0 100644 --- a/src/dialog/vsortdialog.cpp +++ b/src/dialog/vsortdialog.cpp @@ -2,33 +2,6 @@ #include -void VTreeWidget::dropEvent(QDropEvent *p_event) -{ - QList 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) diff --git a/src/dialog/vsortdialog.h b/src/dialog/vsortdialog.h index 213a1df7..7e77ed57 100644 --- a/src/dialog/vsortdialog.h +++ b/src/dialog/vsortdialog.h @@ -3,33 +3,11 @@ #include #include -#include + +#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 { diff --git a/src/src.pro b/src/src.pro index e3a40cd9..61997933 100644 --- a/src/src.pro +++ b/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 \ diff --git a/src/utils/vutils.cpp b/src/utils/vutils.cpp index 82786d45..a52b70ca 100644 --- a/src/utils/vutils.cpp +++ b/src/utils/vutils.cpp @@ -149,15 +149,6 @@ QString VUtils::generateImageFileName(const QString &path, return imageName; } -void VUtils::processStyle(QString &style, const QVector > &varMap) -{ - // Process style - for (int i = 0; i < varMap.size(); ++i) { - const QPair &map = varMap[i]; - style.replace("@" + map.first, map.second); - } -} - QString VUtils::fileNameFromPath(const QString &p_path) { if (p_path.isEmpty()) { diff --git a/src/utils/vutils.h b/src/utils/vutils.h index ab8a588a..064fb69c 100644 --- a/src/utils/vutils.h +++ b/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 > &varMap); - // Return the last directory name of @p_path. static QString directoryNameFromPath(const QString& p_path); diff --git a/src/vdirectorytree.cpp b/src/vdirectorytree.cpp index aeb0777b..0898e472 100644 --- a/src/vdirectorytree.cpp +++ b/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) diff --git a/src/vdirectorytree.h b/src/vdirectorytree.h index c8c95e6c..a0a6b341 100644 --- a/src/vdirectorytree.h +++ b/src/vdirectorytree.h @@ -1,13 +1,14 @@ #ifndef VDIRECTORYTREE_H #define VDIRECTORYTREE_H -#include #include #include #include #include #include #include + +#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: diff --git a/src/vlistwidget.cpp b/src/vlistwidget.cpp index 7198f2b8..bf250fe0 100644 --- a/src/vlistwidget.cpp +++ b/src/vlistwidget.cpp @@ -1,6 +1,5 @@ #include "vlistwidget.h" -#include #include #include #include @@ -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); +} diff --git a/src/vlistwidget.h b/src/vlistwidget.h index ecf9a039..2cc87b04 100644 --- a/src/vlistwidget.h +++ b/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); diff --git a/src/vsimplesearchinput.cpp b/src/vsimplesearchinput.cpp index 3b952003..2dd0a593 100644 --- a/src/vsimplesearchinput.cpp +++ b/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) diff --git a/src/vsimplesearchinput.h b/src/vsimplesearchinput.h index 87611ae8..39a561bb 100644 --- a/src/vsimplesearchinput.h +++ b/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 diff --git a/src/vtreewidget.cpp b/src/vtreewidget.cpp new file mode 100644 index 00000000..3bb536dd --- /dev/null +++ b/src/vtreewidget.cpp @@ -0,0 +1,321 @@ +#include "vtreewidget.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 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 VTreeWidget::searchItems(const QString &p_text, + Qt::MatchFlags p_flags) const +{ + QList items = findItems(p_text, p_flags); + + QList res; + res.reserve(items.size()); + for (int i = 0; i < items.size(); ++i) { + res.append(items[i]); + } + + return res; +} + +void VTreeWidget::highlightHitItems(const QList &p_items) +{ + clearItemsHighlight(); + + QSet hitIndexes; + for (auto it : p_items) { + QModelIndex index = indexFromItem(static_cast(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(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(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); + } +} diff --git a/src/vtreewidget.h b/src/vtreewidget.h new file mode 100644 index 00000000..5f19694e --- /dev/null +++ b/src/vtreewidget.h @@ -0,0 +1,68 @@ +#ifndef VTREEWIDGET_H +#define VTREEWIDGET_H + +#include + +#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 searchItems(const QString &p_text, + Qt::MatchFlags p_flags) const Q_DECL_OVERRIDE; + + virtual void highlightHitItems(const QList &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