Improve copy-paste behaviour for tree views

This commit is contained in:
baldurk
2020-05-29 19:10:20 +01:00
parent e472536c5a
commit 1c19deb825
4 changed files with 130 additions and 75 deletions
+123 -1
View File
@@ -23,6 +23,8 @@
******************************************************************************/
#include "RDTreeView.h"
#include <QApplication>
#include <QClipboard>
#include <QLabel>
#include <QMouseEvent>
#include <QPainter>
@@ -31,6 +33,51 @@
#include <QToolTip>
#include <QWheelEvent>
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<int> 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)
{
+7
View File
@@ -72,6 +72,8 @@ typedef std::function<uint(QModelIndex, uint)> 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<uint, RDTreeViewExpansionState> m_Expansions;
@@ -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
@@ -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<Qt::Alignment> m_alignments;
bool m_instantTooltips = false;
bool m_customCopyPaste = false;
int m_hoverColumn = -1;
QIcon m_normalHoverIcon;
QIcon m_activeHoverIcon;