mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-05-13 13:30:44 +00:00
3b6f63be0d
* Annoyingly although Qt has an internal bool allowUserMoveOfSection0 which does exactly what we want allowing the tree column to be movable, we can't enable it because it's private. So instead we have to re-implement section moving ourselves. * Minor tweak - also made RDTreeWidgets non-movable by default since usually we don't want to allow it.
846 lines
22 KiB
C++
846 lines
22 KiB
C++
/******************************************************************************
|
|
* The MIT License (MIT)
|
|
*
|
|
* Copyright (c) 2016-2017 Baldur Karlsson
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
******************************************************************************/
|
|
|
|
#include "RDTreeWidget.h"
|
|
#include <QApplication>
|
|
#include <QClipboard>
|
|
#include <QColor>
|
|
#include <QDebug>
|
|
#include <QHeaderView>
|
|
#include <QMouseEvent>
|
|
#include <QPainter>
|
|
#include <QPen>
|
|
#include <QStack>
|
|
#include <QToolTip>
|
|
#include "Code/Interface/QRDInterface.h"
|
|
|
|
class RDTreeWidgetModel : public QAbstractItemModel
|
|
{
|
|
public:
|
|
RDTreeWidgetModel(RDTreeWidget *parent) : QAbstractItemModel(parent), widget(parent) {}
|
|
QModelIndex indexForItem(RDTreeWidgetItem *item, int column) const
|
|
{
|
|
if(item->m_parent == NULL)
|
|
return QModelIndex();
|
|
|
|
int row = item->m_parent->indexOfChild(item);
|
|
|
|
return createIndex(row, column, item);
|
|
}
|
|
|
|
RDTreeWidgetItem *itemForIndex(QModelIndex idx) const
|
|
{
|
|
if(!idx.isValid())
|
|
return widget->m_root;
|
|
|
|
return (RDTreeWidgetItem *)idx.internalPointer();
|
|
}
|
|
|
|
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override
|
|
{
|
|
if(row < 0 || column < 0 || row >= rowCount(parent) || column >= columnCount(parent))
|
|
return QModelIndex();
|
|
|
|
RDTreeWidgetItem *par = itemForIndex(parent);
|
|
|
|
if(par == NULL)
|
|
par = widget->m_root;
|
|
|
|
return createIndex(row, column, par->m_children[row]);
|
|
}
|
|
|
|
void beginAddChild(RDTreeWidgetItem *item)
|
|
{
|
|
QModelIndex index = indexForItem(item, 0);
|
|
beginInsertRows(index, item->childCount(), item->childCount());
|
|
}
|
|
|
|
void endAddChild(RDTreeWidgetItem *item) { endInsertRows(); }
|
|
void beginRemoveChildren(RDTreeWidgetItem *parent, int first, int last)
|
|
{
|
|
beginRemoveRows(indexForItem(parent, 0), first, last);
|
|
}
|
|
|
|
void endRemoveChildren() { endRemoveRows(); }
|
|
void itemChanged(RDTreeWidgetItem *item, const QVector<int> &roles)
|
|
{
|
|
QModelIndex topLeft = indexForItem(item, 0);
|
|
QModelIndex bottomRight = indexForItem(item, columnCount() - 1);
|
|
emit dataChanged(topLeft, bottomRight, roles);
|
|
}
|
|
|
|
void refresh()
|
|
{
|
|
emit beginResetModel();
|
|
emit endResetModel();
|
|
}
|
|
|
|
void headerRefresh() { emit headerDataChanged(Qt::Horizontal, 0, columnCount() - 1); }
|
|
void itemsChanged(RDTreeWidgetItem *p, QPair<int, int> minRowColumn, QPair<int, int> maxRowColumn,
|
|
const QVector<int> &roles)
|
|
{
|
|
QModelIndex topLeft = createIndex(minRowColumn.first, minRowColumn.second, p);
|
|
QModelIndex bottomRight = createIndex(maxRowColumn.first, maxRowColumn.second, p);
|
|
emit dataChanged(topLeft, bottomRight, roles);
|
|
}
|
|
|
|
QModelIndex parent(const QModelIndex &index) const override
|
|
{
|
|
if(index.internalPointer() == NULL)
|
|
return QModelIndex();
|
|
|
|
RDTreeWidgetItem *item = itemForIndex(index);
|
|
|
|
if(item)
|
|
return indexForItem(item->m_parent, 0);
|
|
|
|
return QModelIndex();
|
|
}
|
|
int rowCount(const QModelIndex &parent = QModelIndex()) const override
|
|
{
|
|
if(!parent.isValid())
|
|
return widget->m_root->childCount();
|
|
|
|
RDTreeWidgetItem *parentItem = itemForIndex(parent);
|
|
|
|
if(parentItem)
|
|
return parentItem->childCount();
|
|
return 0;
|
|
}
|
|
int columnCount(const QModelIndex &parent = QModelIndex()) const override
|
|
{
|
|
return widget->m_headers.count();
|
|
}
|
|
|
|
bool hasChildren(const QModelIndex &parent) const override
|
|
{
|
|
if(!parent.isValid())
|
|
return widget->m_root->childCount() > 0;
|
|
|
|
RDTreeWidgetItem *parentItem = itemForIndex(parent);
|
|
|
|
if(parentItem)
|
|
return parentItem->childCount() > 0;
|
|
return false;
|
|
}
|
|
Qt::ItemFlags flags(const QModelIndex &index) const override
|
|
{
|
|
if(!index.isValid())
|
|
return 0;
|
|
|
|
return QAbstractItemModel::flags(index);
|
|
}
|
|
|
|
QVariant headerData(int section, Qt::Orientation orientation, int role) const override
|
|
{
|
|
if(orientation == Qt::Horizontal && role == Qt::DisplayRole && section < widget->m_headers.count())
|
|
return widget->m_headers[section];
|
|
|
|
return QVariant();
|
|
}
|
|
|
|
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override
|
|
{
|
|
RDTreeWidgetItem *item = itemForIndex(index);
|
|
|
|
// invisible root element has no data
|
|
if(!item->m_parent)
|
|
return QVariant();
|
|
|
|
if(role == Qt::DisplayRole)
|
|
{
|
|
if(index.column() < item->m_text.count())
|
|
return item->m_text[index.column()];
|
|
}
|
|
else if(role == Qt::DecorationRole)
|
|
{
|
|
if(widget->m_hoverColumn == index.column())
|
|
{
|
|
if(itemForIndex(widget->m_currentHoverIndex) == item)
|
|
return widget->m_activeHoverIcon;
|
|
else
|
|
return widget->m_normalHoverIcon;
|
|
}
|
|
|
|
if(index.column() < item->m_icons.count())
|
|
return item->m_icons[index.column()];
|
|
}
|
|
else if(role == Qt::BackgroundRole)
|
|
{
|
|
// item's background color takes priority but only if not selected
|
|
if(item->m_back != QBrush() && !widget->selectionModel()->isSelected(index))
|
|
return item->m_back;
|
|
|
|
// otherwise if we're hover-highlighting, use the highlight color at 20% opacity
|
|
if(itemForIndex(widget->m_currentHoverIndex) == item && widget->m_hoverColumn >= 0)
|
|
{
|
|
QColor col = widget->palette().color(QPalette::Highlight);
|
|
col.setAlphaF(0.2f);
|
|
return QBrush(col);
|
|
}
|
|
|
|
// otherwise, no special background
|
|
return QVariant();
|
|
}
|
|
else if(role == Qt::ForegroundRole)
|
|
{
|
|
// same priority order as background role above
|
|
if(item->m_fore != QBrush())
|
|
return item->m_fore;
|
|
|
|
return QVariant();
|
|
}
|
|
else if(role == Qt::ToolTipRole && !widget->m_instantTooltips)
|
|
{
|
|
return item->m_tooltip;
|
|
}
|
|
else if(role == Qt::FontRole)
|
|
{
|
|
QFont font; // TODO should this come from some default?
|
|
font.setItalic(item->m_italic);
|
|
font.setBold(item->m_bold);
|
|
return font;
|
|
}
|
|
else if(role < 64 && item->m_customData & 1ULL << role)
|
|
{
|
|
return item->data(index.column(), role);
|
|
}
|
|
|
|
return QVariant();
|
|
}
|
|
|
|
private:
|
|
RDTreeWidget *widget;
|
|
};
|
|
|
|
RDTreeWidgetItem::RDTreeWidgetItem(const QVariantList &values)
|
|
{
|
|
m_text.reserve(values.size());
|
|
for(const QVariant &v : values)
|
|
m_text.push_back(v);
|
|
m_icons.resize(m_text.size());
|
|
}
|
|
|
|
RDTreeWidgetItem::RDTreeWidgetItem(const std::initializer_list<QVariant> &values)
|
|
{
|
|
m_text = values;
|
|
m_icons.resize(m_text.size());
|
|
}
|
|
|
|
RDTreeWidgetItem::~RDTreeWidgetItem()
|
|
{
|
|
if(m_parent)
|
|
m_parent->removeChild(this);
|
|
|
|
clear();
|
|
|
|
delete m_data;
|
|
}
|
|
|
|
QVariant RDTreeWidgetItem::data(int column, int role) const
|
|
{
|
|
if(column >= m_data->count())
|
|
return QVariant();
|
|
|
|
const QVector<RoleData> &dataVec = (*m_data)[column];
|
|
|
|
for(const RoleData &d : dataVec)
|
|
{
|
|
if(d.role == role)
|
|
return d.data;
|
|
}
|
|
|
|
return QVariant();
|
|
}
|
|
|
|
void RDTreeWidgetItem::setData(int column, int role, const QVariant &value)
|
|
{
|
|
// we lazy allocate
|
|
if(!m_data)
|
|
{
|
|
m_data = new QVector<QVector<RoleData>>;
|
|
m_data->resize(qMax(m_text.count(), column + 1));
|
|
}
|
|
|
|
if(role < Qt::UserRole)
|
|
{
|
|
Q_ASSERT(role < 64);
|
|
m_customData |= 1ULL << role;
|
|
}
|
|
|
|
// data is allowed to resize above the column count in the widget
|
|
if(m_data->count() <= column)
|
|
m_data->resize(column + 1);
|
|
|
|
QVector<RoleData> &dataVec = (*m_data)[column];
|
|
|
|
for(RoleData &d : dataVec)
|
|
{
|
|
if(d.role == role)
|
|
{
|
|
bool different = (d.data != value);
|
|
|
|
d.data = value;
|
|
|
|
if(different && role < Qt::UserRole)
|
|
m_widget->m_model->itemChanged(this, {role});
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
dataVec.push_back(RoleData(role, value));
|
|
|
|
if(role < Qt::UserRole)
|
|
m_widget->m_model->itemChanged(this, {role});
|
|
}
|
|
|
|
void RDTreeWidgetItem::addChild(RDTreeWidgetItem *item)
|
|
{
|
|
int colCount = item->m_text.count();
|
|
|
|
// remove it from any previous parent
|
|
if(item->m_parent)
|
|
item->m_parent->removeChild(this);
|
|
|
|
// set up its new parent to us
|
|
item->m_parent = this;
|
|
|
|
// set the widget in case this changed
|
|
item->setWidget(m_widget);
|
|
|
|
// resize per-column vectors to column count
|
|
item->m_text.resize(colCount);
|
|
item->m_icons.resize(colCount);
|
|
|
|
// data can resize up, but we don't resize it down.
|
|
if(item->m_data)
|
|
item->m_data->resize(qMax(item->m_data->count(), colCount));
|
|
|
|
if(m_widget)
|
|
m_widget->m_model->beginAddChild(this);
|
|
|
|
// add to our list of children
|
|
m_children.push_back(item);
|
|
|
|
if(m_widget)
|
|
m_widget->m_model->endAddChild(this);
|
|
}
|
|
|
|
void RDTreeWidgetItem::setWidget(RDTreeWidget *widget)
|
|
{
|
|
if(widget == m_widget)
|
|
return;
|
|
|
|
// if the widget is different, we need to recurse to children
|
|
m_widget = widget;
|
|
for(RDTreeWidgetItem *item : m_children)
|
|
item->setWidget(widget);
|
|
}
|
|
|
|
void RDTreeWidgetItem::dataChanged(int role)
|
|
{
|
|
if(m_widget)
|
|
m_widget->itemDataChanged(this, role);
|
|
}
|
|
|
|
RDTreeWidgetItem *RDTreeWidgetItem::takeChild(int index)
|
|
{
|
|
if(m_widget)
|
|
m_widget->m_model->beginRemoveChildren(this, index, index);
|
|
|
|
m_children[index]->m_parent = NULL;
|
|
RDTreeWidgetItem *ret = m_children.takeAt(index);
|
|
|
|
if(m_widget)
|
|
m_widget->m_model->endRemoveChildren();
|
|
|
|
return ret;
|
|
}
|
|
|
|
void RDTreeWidgetItem::removeChild(RDTreeWidgetItem *child)
|
|
{
|
|
if(m_widget)
|
|
{
|
|
int row = m_children.indexOf(child);
|
|
m_widget->m_model->beginRemoveChildren(this, row, row);
|
|
}
|
|
|
|
m_children.removeOne(child);
|
|
|
|
if(m_widget)
|
|
m_widget->m_model->endRemoveChildren();
|
|
}
|
|
|
|
void RDTreeWidgetItem::clear()
|
|
{
|
|
if(!childCount())
|
|
return;
|
|
|
|
if(m_widget)
|
|
m_widget->m_model->beginRemoveChildren(this, 0, childCount() - 1);
|
|
|
|
while(childCount() > 0)
|
|
{
|
|
RDTreeWidgetItem *child = takeChild(0);
|
|
child->clear();
|
|
delete child;
|
|
}
|
|
|
|
if(m_widget)
|
|
m_widget->m_model->endRemoveChildren();
|
|
}
|
|
|
|
RDTreeWidget::RDTreeWidget(QWidget *parent) : RDTreeView(parent)
|
|
{
|
|
// we'll call this ourselves in drawBranches()
|
|
RDTreeView::enableBranchRectFill(false);
|
|
|
|
header()->setSectionsMovable(false);
|
|
|
|
m_root = new RDTreeWidgetItem;
|
|
m_root->m_widget = this;
|
|
|
|
m_model = new RDTreeWidgetModel(this);
|
|
RDTreeView::setModel(m_model);
|
|
|
|
QObject::connect(this, &RDTreeWidget::activated, [this](const QModelIndex &idx) {
|
|
emit itemActivated(m_model->itemForIndex(idx), idx.column());
|
|
});
|
|
QObject::connect(this, &RDTreeWidget::clicked, [this](const QModelIndex &idx) {
|
|
emit itemClicked(m_model->itemForIndex(idx), idx.column());
|
|
});
|
|
QObject::connect(this, &RDTreeWidget::doubleClicked, [this](const QModelIndex &idx) {
|
|
emit itemDoubleClicked(m_model->itemForIndex(idx), idx.column());
|
|
});
|
|
}
|
|
|
|
RDTreeWidget::~RDTreeWidget()
|
|
{
|
|
delete m_root;
|
|
delete m_model;
|
|
}
|
|
|
|
void RDTreeWidget::beginUpdate()
|
|
{
|
|
m_queueUpdates = true;
|
|
|
|
m_queuedItem = NULL;
|
|
m_lowestIndex = m_highestIndex = qMakePair<int, int>(-1, -1);
|
|
m_queuedRoles = 0;
|
|
}
|
|
|
|
void RDTreeWidget::endUpdate()
|
|
{
|
|
m_queueUpdates = false;
|
|
|
|
if(m_queuedRoles)
|
|
{
|
|
// if we updated multiple different trees we can't issue a single dataChanged for everything
|
|
// under a parent. Refresh the whole model.
|
|
if(m_queuedItem == NULL)
|
|
{
|
|
m_model->refresh();
|
|
}
|
|
else
|
|
{
|
|
QVector<int> roles;
|
|
for(int r = 0; r < 64; r++)
|
|
{
|
|
if(m_queuedRoles & (1ULL << r))
|
|
roles.push_back(r);
|
|
}
|
|
m_model->itemsChanged(m_queuedItem, m_lowestIndex, m_highestIndex, roles);
|
|
}
|
|
}
|
|
}
|
|
|
|
void RDTreeWidget::setColumns(const QStringList &columns)
|
|
{
|
|
m_headers = columns;
|
|
m_model->refresh();
|
|
}
|
|
|
|
void RDTreeWidget::setHeaderText(int column, const QString &text)
|
|
{
|
|
m_headers[column] = text;
|
|
m_model->headerRefresh();
|
|
}
|
|
|
|
RDTreeWidgetItem *RDTreeWidget::selectedItem() const
|
|
{
|
|
QModelIndexList sel = selectionModel()->selectedIndexes();
|
|
|
|
if(sel.isEmpty())
|
|
return NULL;
|
|
|
|
return m_model->itemForIndex(sel[0]);
|
|
}
|
|
|
|
RDTreeWidgetItem *RDTreeWidget::currentItem() const
|
|
{
|
|
return m_model->itemForIndex(currentIndex());
|
|
}
|
|
|
|
void RDTreeWidget::setSelectedItem(RDTreeWidgetItem *node)
|
|
{
|
|
selectionModel()->select(m_model->indexForItem(node, 0),
|
|
QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
|
|
}
|
|
|
|
void RDTreeWidget::setCurrentItem(RDTreeWidgetItem *node)
|
|
{
|
|
setCurrentIndex(m_model->indexForItem(node, 0));
|
|
}
|
|
|
|
RDTreeWidgetItem *RDTreeWidget::itemAt(const QPoint &p) const
|
|
{
|
|
return m_model->itemForIndex(indexAt(p));
|
|
}
|
|
|
|
void RDTreeWidget::expandItem(RDTreeWidgetItem *item)
|
|
{
|
|
expand(m_model->indexForItem(item, 0));
|
|
}
|
|
|
|
void RDTreeWidget::expandAllItems(RDTreeWidgetItem *item)
|
|
{
|
|
expandItem(item);
|
|
|
|
for(int c = 0; c < item->childCount(); c++)
|
|
{
|
|
RDTreeWidgetItem *child = item->child(c);
|
|
expandAllItems(child);
|
|
}
|
|
}
|
|
|
|
void RDTreeWidget::collapseItem(RDTreeWidgetItem *item)
|
|
{
|
|
collapse(m_model->indexForItem(item, 0));
|
|
}
|
|
|
|
void RDTreeWidget::collapseAllItems(RDTreeWidgetItem *item)
|
|
{
|
|
collapseItem(item);
|
|
|
|
for(int c = 0; c < item->childCount(); c++)
|
|
{
|
|
RDTreeWidgetItem *child = item->child(c);
|
|
collapseAllItems(child);
|
|
}
|
|
}
|
|
|
|
void RDTreeWidget::scrollToItem(RDTreeWidgetItem *node)
|
|
{
|
|
scrollTo(m_model->indexForItem(node, 0));
|
|
}
|
|
|
|
void RDTreeWidget::clear()
|
|
{
|
|
m_root->clear();
|
|
}
|
|
|
|
void RDTreeWidget::mouseMoveEvent(QMouseEvent *e)
|
|
{
|
|
RDTreeWidgetItem *oldHover = m_model->itemForIndex(m_currentHoverIndex);
|
|
|
|
RDTreeView::mouseMoveEvent(e);
|
|
|
|
RDTreeWidgetItem *newHover = m_model->itemForIndex(m_currentHoverIndex);
|
|
|
|
if(m_currentHoverIndex.column() == m_hoverColumn && m_hoverHandCursor)
|
|
setCursor(QCursor(Qt::PointingHandCursor));
|
|
else
|
|
unsetCursor();
|
|
|
|
if(oldHover == newHover)
|
|
return;
|
|
|
|
// it's only two items, don't try and make a range but just change them both
|
|
QVector<int> roles = {Qt::DecorationRole, Qt::BackgroundRole, Qt::ForegroundRole};
|
|
if(oldHover)
|
|
m_model->itemChanged(oldHover, roles);
|
|
m_model->itemChanged(newHover, roles);
|
|
|
|
if(m_instantTooltips)
|
|
{
|
|
QToolTip::hideText();
|
|
|
|
if(newHover && !newHover->m_tooltip.isEmpty())
|
|
{
|
|
// the documentation says:
|
|
//
|
|
// "If text is empty the tool tip is hidden. If the text is the same as the currently shown
|
|
// tooltip, the tip will not move. You can force moving by first hiding the tip with an empty
|
|
// text, and then showing the new tip at the new position."
|
|
//
|
|
// However the actual implementation has some kind of 'fading' check, so if you hide then
|
|
// immediately show, it will try to reuse the tooltip and end up not moving it at all if the
|
|
// text hasn't changed.
|
|
QToolTip::showText(QCursor::pos(), lit(" "), this);
|
|
QToolTip::showText(QCursor::pos(), newHover->m_tooltip, this);
|
|
}
|
|
}
|
|
|
|
emit mouseMove(e);
|
|
|
|
RDTreeView::mouseMoveEvent(e);
|
|
}
|
|
|
|
void RDTreeWidget::mouseReleaseEvent(QMouseEvent *e)
|
|
{
|
|
QModelIndex idx = indexAt(e->pos());
|
|
|
|
if(idx.isValid() && idx.column() == m_hoverColumn && m_activateOnClick)
|
|
{
|
|
emit itemActivated(itemAt(e->pos()), idx.column());
|
|
}
|
|
|
|
RDTreeView::mouseReleaseEvent(e);
|
|
}
|
|
|
|
void RDTreeWidget::leaveEvent(QEvent *e)
|
|
{
|
|
unsetCursor();
|
|
|
|
if(m_currentHoverIndex.isValid())
|
|
{
|
|
RDTreeWidgetItem *item = m_model->itemForIndex(m_currentHoverIndex);
|
|
if(!item->m_tooltip.isEmpty() && m_instantTooltips)
|
|
QToolTip::hideText();
|
|
m_model->itemChanged(item, {Qt::DecorationRole, Qt::BackgroundRole, Qt::ForegroundRole});
|
|
}
|
|
|
|
emit leave(e);
|
|
|
|
RDTreeView::leaveEvent(e);
|
|
}
|
|
|
|
void RDTreeWidget::focusOutEvent(QFocusEvent *event)
|
|
{
|
|
if(m_clearSelectionOnFocusLoss)
|
|
clearSelection();
|
|
|
|
RDTreeView::focusOutEvent(event);
|
|
}
|
|
|
|
void RDTreeWidget::keyPressEvent(QKeyEvent *e)
|
|
{
|
|
if(!m_customCopyPaste && e->matches(QKeySequence::Copy))
|
|
{
|
|
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++)
|
|
widths[i] = qMax(widths[i], item->m_text[i].toString().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");
|
|
clipData += format.arg(item->m_text[i].toString(), -widths[i]);
|
|
}
|
|
|
|
clipData += lit("\n");
|
|
}
|
|
|
|
QClipboard *clipboard = QApplication::clipboard();
|
|
clipboard->setText(clipData.trimmed());
|
|
|
|
delete[] heapWidths;
|
|
}
|
|
else
|
|
{
|
|
RDTreeView::keyPressEvent(e);
|
|
}
|
|
|
|
emit(keyPress(e));
|
|
}
|
|
|
|
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 filled
|
|
// (as otherwise they don't show up well over selection or background fills) as well as to draw
|
|
// any vertical branch colors.
|
|
|
|
painter->save();
|
|
|
|
// start at the left-most side of the rect
|
|
QRect branchRect(rect.left(), rect.top(), indentation(), rect.height());
|
|
|
|
RDTreeWidgetItem *item = m_model->itemForIndex(index);
|
|
|
|
// first draw the coloured lines - we're only interested in parents for this, so push all the
|
|
// parents onto a stack
|
|
QStack<RDTreeWidgetItem *> parents;
|
|
|
|
RDTreeWidgetItem *parent = item->parent();
|
|
|
|
while(parent && parent != m_root)
|
|
{
|
|
parents.push(parent);
|
|
parent = parent->parent();
|
|
}
|
|
|
|
// fill in the background behind the lines for the whole row, since by default it doesn't show up
|
|
// behind the tree lines.
|
|
|
|
QRect allLinesRect(rect.left(), rect.top(), (parents.count() + 1) * indentation(), rect.height());
|
|
|
|
// calling this manually here means it won't be called later in RDTreeView::drawBranches, and
|
|
// allows us to overwrite it with our background filling if we want to.
|
|
RDTreeWidget::fillBranchesRect(painter, rect, index);
|
|
|
|
if(!selectionModel()->isSelected(index) && item->m_back != QBrush())
|
|
{
|
|
painter->fillRect(allLinesRect, item->m_back);
|
|
}
|
|
|
|
// we now iterate from the top-most parent down, moving in from the left
|
|
QPen oldPen = painter->pen();
|
|
while(!parents.isEmpty())
|
|
{
|
|
parent = parents.pop();
|
|
|
|
if(parent->m_treeCol.isValid())
|
|
{
|
|
// draw a centred pen vertically down the middle of branchRect
|
|
|
|
painter->setPen(QPen(QBrush(parent->m_treeCol), parent->m_treeColWidth));
|
|
|
|
QPointF topCentre = QRectF(branchRect).center();
|
|
QPointF bottomCentre = topCentre;
|
|
|
|
topCentre.setY(branchRect.top());
|
|
bottomCentre.setY(branchRect.bottom());
|
|
|
|
painter->drawLine(topCentre, bottomCentre);
|
|
}
|
|
|
|
branchRect.moveLeft(branchRect.left() + indentation());
|
|
}
|
|
painter->setPen(oldPen);
|
|
|
|
// branchRect is now over the box/lines for the current item.
|
|
|
|
// draw a rect of QPalette Base color behind the branch indicator if we have children, since by
|
|
// default there might not be one and the indicator won't always show up well over the background
|
|
// color
|
|
/*
|
|
if(item->childCount() > 0)
|
|
{
|
|
// TODO find a portable rect
|
|
const int radius = 9 / 2;
|
|
QPoint topleft = branchRect.center();
|
|
topleft.setX(topleft.x() - radius + 1);
|
|
topleft.setY(topleft.y() - radius + 1);
|
|
|
|
painter->fillRect(QRect(topleft, QSize(radius * 2, radius * 2)),
|
|
palette().brush(QPalette::Base));
|
|
}
|
|
*/
|
|
|
|
painter->restore();
|
|
|
|
RDTreeView::drawBranches(painter, rect, index);
|
|
}
|
|
|
|
void RDTreeWidget::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
|
|
{
|
|
emit itemSelectionChanged();
|
|
|
|
RDTreeView::selectionChanged(selected, deselected);
|
|
}
|
|
|
|
void RDTreeWidget::currentChanged(const QModelIndex ¤t, const QModelIndex &previous)
|
|
{
|
|
emit currentItemChanged(m_model->itemForIndex(current), m_model->itemForIndex(previous));
|
|
|
|
RDTreeView::currentChanged(current, previous);
|
|
}
|
|
|
|
void RDTreeWidget::itemDataChanged(RDTreeWidgetItem *item, int role)
|
|
{
|
|
if(m_queueUpdates)
|
|
{
|
|
m_queuedRoles |= (1ULL << role);
|
|
|
|
// for now we only support updating the whole row, with all columns, even if only one column
|
|
// changed.
|
|
int row = item->m_parent->indexOfChild(item);
|
|
|
|
// no queued updates yet, set up this one
|
|
if(m_lowestIndex.first == -1)
|
|
{
|
|
m_queuedItem = item;
|
|
m_lowestIndex = qMakePair<int, int>(row, 0);
|
|
m_highestIndex = qMakePair<int, int>(m_lowestIndex.first, m_headers.count() - 1);
|
|
}
|
|
else
|
|
{
|
|
// there's already an update. Check if we can expand it
|
|
if(m_queuedItem == item)
|
|
{
|
|
m_lowestIndex.first = qMin(m_lowestIndex.first, row);
|
|
m_highestIndex.first = qMax(m_highestIndex.first, row);
|
|
}
|
|
else
|
|
{
|
|
// can't batch updates across multiple parents, so we just fallback to full model refresh
|
|
m_queuedItem = NULL;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_model->itemChanged(item, {role});
|
|
}
|
|
}
|