|
|
@@ -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);
|
|
|
+ }
|
|
|
+}
|