From 1c19deb825afc87c167966259eb6808aca6b8469 Mon Sep 17 00:00:00 2001 From: baldurk Date: Fri, 29 May 2020 19:10:20 +0100 Subject: [PATCH] Improve copy-paste behaviour for tree views --- qrenderdoc/Widgets/Extended/RDTreeView.cpp | 124 ++++++++++++++++++- qrenderdoc/Widgets/Extended/RDTreeView.h | 7 ++ qrenderdoc/Widgets/Extended/RDTreeWidget.cpp | 67 ---------- qrenderdoc/Widgets/Extended/RDTreeWidget.h | 7 -- 4 files changed, 130 insertions(+), 75 deletions(-) diff --git a/qrenderdoc/Widgets/Extended/RDTreeView.cpp b/qrenderdoc/Widgets/Extended/RDTreeView.cpp index 7c897e3c1..ff780a46d 100644 --- a/qrenderdoc/Widgets/Extended/RDTreeView.cpp +++ b/qrenderdoc/Widgets/Extended/RDTreeView.cpp @@ -23,6 +23,8 @@ ******************************************************************************/ #include "RDTreeView.h" +#include +#include #include #include #include @@ -31,6 +33,51 @@ #include #include +static int GetDepth(const QAbstractItemModel *model, const QModelIndex &idx) +{ + if(idx == QModelIndex()) + return 0; + + return 1 + GetDepth(model, model->parent(idx)); +} + +static bool CompareModelIndex(const QModelIndex &a, const QModelIndex &b) +{ + if(a == b) + return false; + + if(a == QModelIndex()) + return true; + else if(b == QModelIndex()) + return false; + + if(a.model() != b.model()) + return false; + + QModelIndex ap = a.model()->parent(a); + QModelIndex bp = b.model()->parent(b); + if(ap == bp) + { + if(a.row() == b.row()) + return a.column() < b.column(); + return a.row() < b.row(); + } + + if(a == bp) + return true; + if(b == ap) + return false; + + int ad = GetDepth(a.model(), a); + int bd = GetDepth(b.model(), b); + + if(ad > bd) + return CompareModelIndex(ap, b); + else if(ad < bd) + return CompareModelIndex(a, bp); + return CompareModelIndex(ap, bp); +} + RDTreeViewDelegate::RDTreeViewDelegate(RDTreeView *view) : ForwardingDelegate(view), m_View(view) { } @@ -167,7 +214,14 @@ void RDTreeView::leaveEvent(QEvent *e) void RDTreeView::keyPressEvent(QKeyEvent *e) { - QTreeView::keyPressEvent(e); + if(e->matches(QKeySequence::Copy)) + { + copySelection(); + } + else + { + QTreeView::keyPressEvent(e); + } emit(keyPress(e)); } @@ -262,6 +316,74 @@ void RDTreeView::applyExpansion(const RDTreeViewExpansionState &state, const Exp applyExpansionToRow(state, model()->index(i, 0), 0, keygen); } +void RDTreeView::copySelection() +{ + QModelIndexList sel = selectionModel()->selectedRows(); + + std::sort(sel.begin(), sel.end(), CompareModelIndex); + + QVector widths; + + ICaptureContext *ctx = getCaptureContext(this); + + int minDepth = INT_MAX; + int maxDepth = 0; + + // align the copied data so that each column is the same width + for(QModelIndex idx : sel) + { + int colCount = model()->columnCount(idx); + + widths.resize(qMax(widths.size(), colCount)); + + for(int i = 0; i < colCount; i++) + { + QVariant var = model()->data(model()->index(idx.row(), i, idx.parent())); + QString text = ctx ? RichResourceTextFormat(*ctx, var) : var.toString(); + widths[i] = qMax(widths[i], text.count()); + } + + int depth = GetDepth(model(), idx); + + minDepth = qMin(minDepth, depth); + maxDepth = qMax(maxDepth, depth); + } + + // add on two characters for every depth, for indent + for(int &i : widths) + i += 2 * (maxDepth - minDepth - 1); + + // only align up to 50 characters so one really long item doesn't mess up the whole thing + for(int &i : widths) + i = qMin(50, i); + + QString clipData; + for(QModelIndex idx : sel) + { + int colCount = model()->columnCount(idx); + + int depth = GetDepth(model(), idx); + + for(int i = 0; i < colCount; i++) + { + QString format = i == 0 ? QFormatStr("%1") : QFormatStr(" %1"); + + QVariant var = model()->data(model()->index(idx.row(), i, idx.parent())); + QString text = ctx ? RichResourceTextFormat(*ctx, var) : var.toString(); + + if(i == 0) + text.prepend(QString((depth - minDepth) * 2, QLatin1Char(' '))); + + clipData += format.arg(text, -widths[i]); + } + + clipData += lit("\n"); + } + + QClipboard *clipboard = QApplication::clipboard(); + clipboard->setText(clipData.trimmed()); +} + void RDTreeView::saveExpansionFromRow(RDTreeViewExpansionState &state, QModelIndex idx, uint seed, const ExpansionKeyGen &keygen) { diff --git a/qrenderdoc/Widgets/Extended/RDTreeView.h b/qrenderdoc/Widgets/Extended/RDTreeView.h index 6aa2e0321..c43c8338b 100644 --- a/qrenderdoc/Widgets/Extended/RDTreeView.h +++ b/qrenderdoc/Widgets/Extended/RDTreeView.h @@ -72,6 +72,8 @@ typedef std::function ExpansionKeyGen; class RDTreeView : public QTreeView { Q_OBJECT + + Q_PROPERTY(bool customCopyPasteHandler READ customCopyPasteHandler WRITE setCustomCopyPasteHandler) public: explicit RDTreeView(QWidget *parent = 0); virtual ~RDTreeView(); @@ -87,6 +89,8 @@ public: int verticalItemMargin() { return m_VertMargin; } void setIgnoreIconSize(bool ignore) { m_IgnoreIconSize = ignore; } bool ignoreIconSize() { return m_IgnoreIconSize; } + bool customCopyPasteHandler() { return m_customCopyPaste; } + void setCustomCopyPasteHandler(bool custom) { m_customCopyPaste = custom; } QModelIndex currentHoverIndex() const { return m_currentHoverIndex; } void setItemDelegate(QAbstractItemDelegate *delegate); QAbstractItemDelegate *itemDelegate() const; @@ -121,6 +125,8 @@ public: } bool hasInternalExpansion(uint expansionID) { return m_Expansions.contains(expansionID); } void clearInternalExpansions() { m_Expansions.clear(); } + virtual void copySelection(); + signals: void leave(QEvent *e); void keyPress(QKeyEvent *e); @@ -147,6 +153,7 @@ private: bool m_VisibleBranches = true; bool m_VisibleGridLines = true; bool m_TooltipElidedItems = true; + bool m_customCopyPaste = false; QMap m_Expansions; diff --git a/qrenderdoc/Widgets/Extended/RDTreeWidget.cpp b/qrenderdoc/Widgets/Extended/RDTreeWidget.cpp index ee24a60c9..f768d7781 100644 --- a/qrenderdoc/Widgets/Extended/RDTreeWidget.cpp +++ b/qrenderdoc/Widgets/Extended/RDTreeWidget.cpp @@ -853,73 +853,6 @@ void RDTreeWidget::focusOutEvent(QFocusEvent *event) RDTreeView::focusOutEvent(event); } -void RDTreeWidget::keyPressEvent(QKeyEvent *e) -{ - if(!m_customCopyPaste && e->matches(QKeySequence::Copy)) - { - copySelection(); - } - else - { - RDTreeView::keyPressEvent(e); - } -} - -void RDTreeWidget::copySelection() -{ - QModelIndexList sel = selectionModel()->selectedRows(); - - int stackWidths[16]; - int *heapWidths = NULL; - - int colCount = m_model->columnCount(); - - if(colCount >= 16) - heapWidths = new int[colCount]; - - int *widths = heapWidths ? heapWidths : stackWidths; - - for(int i = 0; i < colCount; i++) - widths[i] = 0; - - // align the copied data so that each column is the same width - for(QModelIndex idx : sel) - { - RDTreeWidgetItem *item = m_model->itemForIndex(idx); - - for(int i = 0; i < qMin(colCount, item->m_text.count()); i++) - { - QString text = item->m_text[i].toString(); - widths[i] = qMax(widths[i], text.count()); - } - } - - // only align up to 50 characters so one really long item doesn't mess up the whole thing - for(int i = 0; i < colCount; i++) - widths[i] = qMin(50, widths[i]); - - QString clipData; - for(QModelIndex idx : sel) - { - RDTreeWidgetItem *item = m_model->itemForIndex(idx); - - for(int i = 0; i < qMin(colCount, item->m_text.count()); i++) - { - QString format = i == 0 ? QFormatStr("%1") : QFormatStr(" %1"); - QString text = item->m_text[i].toString(); - - clipData += format.arg(text, -widths[i]); - } - - clipData += lit("\n"); - } - - QClipboard *clipboard = QApplication::clipboard(); - clipboard->setText(clipData.trimmed()); - - delete[] heapWidths; -} - void RDTreeWidget::drawBranches(QPainter *painter, const QRect &rect, const QModelIndex &index) const { // we do our own custom branch rendering to ensure the backgrounds for the +/- markers are diff --git a/qrenderdoc/Widgets/Extended/RDTreeWidget.h b/qrenderdoc/Widgets/Extended/RDTreeWidget.h index afa4e9cb3..927a7e57d 100644 --- a/qrenderdoc/Widgets/Extended/RDTreeWidget.h +++ b/qrenderdoc/Widgets/Extended/RDTreeWidget.h @@ -205,7 +205,6 @@ class RDTreeWidget : public RDTreeView Q_OBJECT Q_PROPERTY(bool instantTooltips READ instantTooltips WRITE setInstantTooltips) - Q_PROPERTY(bool customCopyPasteHandler READ customCopyPasteHandler WRITE setCustomCopyPasteHandler) public: explicit RDTreeWidget(QWidget *parent = 0); ~RDTreeWidget(); @@ -223,8 +222,6 @@ public: void setClearSelectionOnFocusLoss(bool clear) { m_clearSelectionOnFocusLoss = clear; } bool instantTooltips() { return m_instantTooltips; } void setInstantTooltips(bool instant) { m_instantTooltips = instant; } - bool customCopyPasteHandler() { return m_customCopyPaste; } - void setCustomCopyPasteHandler(bool custom) { m_customCopyPaste = custom; } RDTreeWidgetItem *invisibleRootItem() { return m_root; } void addTopLevelItem(RDTreeWidgetItem *item) { m_root->addChild(item); } RDTreeWidgetItem *topLevelItem(int index) const { return m_root->child(index); } @@ -254,8 +251,6 @@ public: void collapseAllItems(RDTreeWidgetItem *item); void scrollToItem(RDTreeWidgetItem *node); - void copySelection(); - void clear(); signals: @@ -275,7 +270,6 @@ private: void mouseReleaseEvent(QMouseEvent *e) override; void leaveEvent(QEvent *e) override; void focusOutEvent(QFocusEvent *event) override; - void keyPressEvent(QKeyEvent *e) override; void drawBranches(QPainter *painter, const QRect &rect, const QModelIndex &index) const override; void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) override; @@ -313,7 +307,6 @@ private: QVector m_alignments; bool m_instantTooltips = false; - bool m_customCopyPaste = false; int m_hoverColumn = -1; QIcon m_normalHoverIcon; QIcon m_activeHoverIcon;