mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-08-24 07:26:34 +00:00
Switch RDTreeWidget to inherit from QTreeView instead of QTreeWidget
* We add our own custom item model to handle the cases we need. We can also make a few assumptions and optimisations around things we can safely assume like e.g. nodel columns won't really change after init. * This lets us have full control over batching updates, which was the main motivation, but it makes it easier to extend in future (e.g. adding single per-item tags instead of the heavyweight Qt::UserRole data elements).
This commit is contained in:
@@ -27,72 +27,517 @@
|
||||
#include <QDebug>
|
||||
#include <QMouseEvent>
|
||||
|
||||
RDTreeWidget::RDTreeWidget(QWidget *parent) : QTreeWidget(parent)
|
||||
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
|
||||
{
|
||||
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(widget->m_currentHoverItem == 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
|
||||
if(item->m_back != QBrush())
|
||||
return item->m_back;
|
||||
|
||||
// otherwise if we're hover-highlighting, use the window color
|
||||
if(widget->m_currentHoverItem == item)
|
||||
return widget->palette().brush(QPalette::Window);
|
||||
|
||||
// otherwise, no special background
|
||||
return QBrush();
|
||||
}
|
||||
else if(role == Qt::ForegroundRole)
|
||||
{
|
||||
// same priority order as background role above
|
||||
if(item->m_fore != QBrush())
|
||||
return item->m_fore;
|
||||
|
||||
if(widget->m_currentHoverItem == item)
|
||||
return widget->palette().brush(QPalette::WindowText);
|
||||
|
||||
return QBrush();
|
||||
}
|
||||
else if(role == Qt::ToolTipRole)
|
||||
{
|
||||
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 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->dataChanged(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) : QTreeView(parent)
|
||||
{
|
||||
setMouseTracking(true);
|
||||
|
||||
m_root = new RDTreeWidgetItem;
|
||||
m_root->m_widget = this;
|
||||
|
||||
m_model = new RDTreeWidgetModel(this);
|
||||
QTreeView::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());
|
||||
});
|
||||
}
|
||||
|
||||
void RDTreeWidget::setHoverIcons(QTreeWidgetItem *item, QIcon normal, QIcon hover)
|
||||
RDTreeWidget::~RDTreeWidget()
|
||||
{
|
||||
item->setData(m_hoverColumn, Qt::DecorationRole, normal);
|
||||
item->setData(m_hoverColumn, hoverIconRole, hover);
|
||||
delete m_root;
|
||||
delete m_model;
|
||||
}
|
||||
|
||||
void RDTreeWidget::setHoverColour(QTreeWidgetItem *item, QColor col)
|
||||
void RDTreeWidget::beginUpdate()
|
||||
{
|
||||
item->setData(m_hoverColumn, hoverBackColourRole, col);
|
||||
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::scrollToItem(RDTreeWidgetItem *node)
|
||||
{
|
||||
scrollTo(m_model->indexForItem(node, 0));
|
||||
}
|
||||
|
||||
void RDTreeWidget::clear()
|
||||
{
|
||||
m_root->clear();
|
||||
}
|
||||
|
||||
void RDTreeWidget::mouseMoveEvent(QMouseEvent *e)
|
||||
{
|
||||
QTreeWidgetItem *item = itemAt(e->pos());
|
||||
QModelIndex idx = indexAt(e->pos());
|
||||
RDTreeWidgetItem *item = m_model->itemForIndex(idx);
|
||||
|
||||
clearHovers(invisibleRootItem(), item);
|
||||
if(idx.column() == m_hoverColumn && m_hoverHandCursor)
|
||||
setCursor(QCursor(Qt::PointingHandCursor));
|
||||
else
|
||||
unsetCursor();
|
||||
|
||||
if(item)
|
||||
{
|
||||
QVariant normalIcon = item->data(m_hoverColumn, Qt::DecorationRole);
|
||||
QVariant hoverIcon = item->data(m_hoverColumn, hoverIconRole);
|
||||
if(m_currentHoverItem == item || m_hoverColumn < 0)
|
||||
return;
|
||||
|
||||
if(normalIcon.isValid() && hoverIcon.isValid())
|
||||
{
|
||||
item->setData(m_hoverColumn, hoverIconRole, QVariant());
|
||||
item->setData(m_hoverColumn, Qt::DecorationRole, hoverIcon);
|
||||
item->setData(m_hoverColumn, backupNormalIconRole, normalIcon);
|
||||
}
|
||||
RDTreeWidgetItem *old = m_currentHoverItem;
|
||||
m_currentHoverItem = item;
|
||||
|
||||
if(!item->data(m_hoverColumn, backupBackColourRole).isValid())
|
||||
{
|
||||
QVariant backHoverColor = item->data(m_hoverColumn, hoverBackColourRole);
|
||||
|
||||
QColor col = item->data(m_hoverColumn, hoverBackColourRole).value<QColor>();
|
||||
if(!col.isValid())
|
||||
col = m_defaultHoverColour;
|
||||
|
||||
if(col.isValid())
|
||||
{
|
||||
item->setData(m_hoverColumn, backupBackColourRole, item->background(m_hoverColumn));
|
||||
for(int c = 0; c < item->columnCount(); c++)
|
||||
item->setData(c, Qt::BackgroundRole, QBrush(col));
|
||||
}
|
||||
}
|
||||
|
||||
QModelIndex idx = indexAt(e->pos());
|
||||
|
||||
if(idx.column() == m_hoverColumn && item->data(m_hoverColumn, Qt::DecorationRole).isValid() &&
|
||||
m_hoverHandCursor)
|
||||
{
|
||||
setCursor(QCursor(Qt::PointingHandCursor));
|
||||
}
|
||||
else
|
||||
{
|
||||
unsetCursor();
|
||||
}
|
||||
}
|
||||
// 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(old)
|
||||
m_model->itemChanged(old, roles);
|
||||
m_model->itemChanged(item, roles);
|
||||
|
||||
emit mouseMove(e);
|
||||
|
||||
QTreeWidget::mouseMoveEvent(e);
|
||||
QTreeView::mouseMoveEvent(e);
|
||||
}
|
||||
|
||||
void RDTreeWidget::mouseReleaseEvent(QMouseEvent *e)
|
||||
@@ -104,18 +549,23 @@ void RDTreeWidget::mouseReleaseEvent(QMouseEvent *e)
|
||||
emit itemActivated(itemAt(e->pos()), idx.column());
|
||||
}
|
||||
|
||||
QTreeWidget::mouseReleaseEvent(e);
|
||||
QTreeView::mouseReleaseEvent(e);
|
||||
}
|
||||
|
||||
void RDTreeWidget::leaveEvent(QEvent *e)
|
||||
{
|
||||
unsetCursor();
|
||||
|
||||
clearHovers(invisibleRootItem(), NULL);
|
||||
if(m_currentHoverItem)
|
||||
{
|
||||
RDTreeWidgetItem *item = m_currentHoverItem;
|
||||
m_currentHoverItem = NULL;
|
||||
m_model->itemChanged(item, {Qt::DecorationRole, Qt::BackgroundRole, Qt::ForegroundRole});
|
||||
}
|
||||
|
||||
emit leave(e);
|
||||
|
||||
QTreeWidget::leaveEvent(e);
|
||||
QTreeView::leaveEvent(e);
|
||||
}
|
||||
|
||||
void RDTreeWidget::focusOutEvent(QFocusEvent *event)
|
||||
@@ -123,45 +573,63 @@ void RDTreeWidget::focusOutEvent(QFocusEvent *event)
|
||||
if(m_clearSelectionOnFocusLoss)
|
||||
clearSelection();
|
||||
|
||||
QTreeWidget::focusOutEvent(event);
|
||||
QTreeView::focusOutEvent(event);
|
||||
}
|
||||
|
||||
void RDTreeWidget::keyPressEvent(QKeyEvent *e)
|
||||
{
|
||||
emit(keyPress(e));
|
||||
QTreeWidget::keyPressEvent(e);
|
||||
QTreeView::keyPressEvent(e);
|
||||
}
|
||||
|
||||
void RDTreeWidget::clearHovers(QTreeWidgetItem *root, QTreeWidgetItem *exception)
|
||||
void RDTreeWidget::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
|
||||
{
|
||||
for(int i = 0; i < root->childCount(); i++)
|
||||
emit itemSelectionChanged();
|
||||
|
||||
QTreeView::selectionChanged(selected, deselected);
|
||||
}
|
||||
|
||||
void RDTreeWidget::currentChanged(const QModelIndex ¤t, const QModelIndex &previous)
|
||||
{
|
||||
emit currentItemChanged(m_model->itemForIndex(current), m_model->itemForIndex(previous));
|
||||
|
||||
QTreeView::currentChanged(current, previous);
|
||||
}
|
||||
|
||||
void RDTreeWidget::dataChanged(RDTreeWidgetItem *item, int role)
|
||||
{
|
||||
if(m_queueUpdates)
|
||||
{
|
||||
QTreeWidgetItem *n = root->child(i);
|
||||
m_queuedRoles |= (1ULL << role);
|
||||
|
||||
if(n == exception)
|
||||
continue;
|
||||
// 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);
|
||||
|
||||
clearHovers(n, exception);
|
||||
|
||||
QVariant original = n->data(m_hoverColumn, backupNormalIconRole);
|
||||
|
||||
if(original.isValid())
|
||||
// no queued updates yet, set up this one
|
||||
if(m_lowestIndex.first == -1)
|
||||
{
|
||||
QVariant icon = n->data(m_hoverColumn, Qt::DecorationRole);
|
||||
|
||||
n->setData(m_hoverColumn, hoverIconRole, icon);
|
||||
n->setData(m_hoverColumn, Qt::DecorationRole, original);
|
||||
n->setData(m_hoverColumn, backupNormalIconRole, QVariant());
|
||||
m_queuedItem = item;
|
||||
m_lowestIndex = qMakePair<int, int>(row, 0);
|
||||
m_highestIndex = qMakePair<int, int>(m_lowestIndex.first, m_headers.count() - 1);
|
||||
}
|
||||
|
||||
original = n->data(m_hoverColumn, backupBackColourRole);
|
||||
|
||||
if(original.isValid())
|
||||
else
|
||||
{
|
||||
QBrush orig = original.value<QBrush>();
|
||||
for(int c = 0; c < n->columnCount(); c++)
|
||||
n->setBackground(c, orig);
|
||||
n->setData(m_hoverColumn, backupBackColourRole, QVariant());
|
||||
// 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});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,31 +24,170 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QTreeWidget>
|
||||
#include <QTreeView>
|
||||
|
||||
class RDTreeWidget : public QTreeWidget
|
||||
class RDTreeWidget;
|
||||
class RDTreeWidgetModel;
|
||||
|
||||
class RDTreeWidgetItem
|
||||
{
|
||||
public:
|
||||
RDTreeWidgetItem() = default;
|
||||
RDTreeWidgetItem(const std::initializer_list<QVariant> &values);
|
||||
~RDTreeWidgetItem();
|
||||
|
||||
QVariant data(int column, int role) const;
|
||||
void setData(int column, int role, const QVariant &value);
|
||||
|
||||
void addChild(RDTreeWidgetItem *item);
|
||||
|
||||
// the data above requires allocating a bunch of vectors since it's stored per-column. Where
|
||||
// possible, just use this single per-item tag
|
||||
inline QVariant tag() const { return m_tag; }
|
||||
inline void setTag(const QVariant &value) { m_tag = value; }
|
||||
// inline accessors to the private data
|
||||
inline void setIcon(int column, const QIcon &icon)
|
||||
{
|
||||
if(column >= m_icons.size())
|
||||
return;
|
||||
|
||||
m_icons[column] = icon;
|
||||
dataChanged(Qt::DecorationRole);
|
||||
}
|
||||
inline RDTreeWidgetItem *child(int index) const { return m_children[index]; }
|
||||
inline int indexOfChild(RDTreeWidgetItem *child) const { return m_children.indexOf(child); }
|
||||
RDTreeWidgetItem *takeChild(int index);
|
||||
void removeChild(RDTreeWidgetItem *child);
|
||||
void clear();
|
||||
inline int childCount() const { return m_children.count(); }
|
||||
inline RDTreeWidgetItem *parent() const { return m_parent; }
|
||||
inline RDTreeWidget *treeWidget() const { return m_widget; }
|
||||
inline void setBold(bool bold)
|
||||
{
|
||||
m_bold = bold;
|
||||
dataChanged(Qt::FontRole);
|
||||
}
|
||||
inline void setItalic(bool italic)
|
||||
{
|
||||
m_italic = italic;
|
||||
dataChanged(Qt::FontRole);
|
||||
}
|
||||
inline void setBackgroundColor(QColor background) { setBackground(QBrush(background)); }
|
||||
inline void setForegroundColor(QColor foreground) { setForeground(QBrush(foreground)); }
|
||||
inline void setBackground(QBrush background)
|
||||
{
|
||||
m_back = background;
|
||||
dataChanged(Qt::BackgroundRole);
|
||||
}
|
||||
inline void setForeground(QBrush foreground)
|
||||
{
|
||||
m_fore = foreground;
|
||||
dataChanged(Qt::ForegroundRole);
|
||||
}
|
||||
inline QString text(int column) const { return m_text[column].toString(); }
|
||||
inline void setText(int column, const QVariant &value)
|
||||
{
|
||||
if(column >= m_text.size())
|
||||
return;
|
||||
|
||||
m_text[column] = value;
|
||||
dataChanged(Qt::DisplayRole);
|
||||
}
|
||||
inline void setToolTip(const QString &value)
|
||||
{
|
||||
m_tooltip = value;
|
||||
dataChanged(Qt::ToolTipRole);
|
||||
}
|
||||
|
||||
private:
|
||||
friend class RDTreeWidget;
|
||||
friend class RDTreeWidgetModel;
|
||||
|
||||
void setWidget(RDTreeWidget *widget);
|
||||
RDTreeWidget *m_widget = NULL;
|
||||
|
||||
RDTreeWidgetItem *m_parent = NULL;
|
||||
QVector<RDTreeWidgetItem *> m_children;
|
||||
|
||||
struct RoleData
|
||||
{
|
||||
RoleData() : role(0), data() {}
|
||||
RoleData(int r, const QVariant &d) : role(r), data(d) {}
|
||||
int role;
|
||||
QVariant data;
|
||||
};
|
||||
|
||||
void dataChanged(int role);
|
||||
|
||||
// per-column properties
|
||||
QVector<QVariant> m_text;
|
||||
QVector<QIcon> m_icons;
|
||||
// each element, per-column, is a list of other data values
|
||||
// we allocate this lazily only if it's really needed
|
||||
QVector<QVector<RoleData>> *m_data = NULL;
|
||||
// bitmask of which special Qt roles have data set
|
||||
uint64_t m_customData = 0;
|
||||
|
||||
// per-item properties
|
||||
QString m_tooltip;
|
||||
bool m_bold = false;
|
||||
bool m_italic = false;
|
||||
QBrush m_back;
|
||||
QBrush m_fore;
|
||||
QVariant m_tag;
|
||||
};
|
||||
|
||||
class RDTreeWidget : public QTreeView
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit RDTreeWidget(QWidget *parent = 0);
|
||||
~RDTreeWidget();
|
||||
|
||||
void setDefaultHoverColor(QColor col) { m_defaultHoverColour = col; }
|
||||
void setHoverIconColumn(int column)
|
||||
void setHoverIconColumn(int column, QIcon normal, QIcon hover)
|
||||
{
|
||||
m_hoverColumn = column;
|
||||
m_normalHoverIcon = normal;
|
||||
m_activeHoverIcon = hover;
|
||||
m_hoverHandCursor = true;
|
||||
m_activateOnClick = true;
|
||||
}
|
||||
void setHoverHandCursor(bool hand) { m_hoverHandCursor = hand; }
|
||||
void setHoverClickActivate(bool click) { m_activateOnClick = click; }
|
||||
void setClearSelectionOnFocusLoss(bool clear) { m_clearSelectionOnFocusLoss = clear; }
|
||||
void setHoverIcons(QTreeWidgetItem *item, QIcon normal, QIcon hover);
|
||||
void setHoverColour(QTreeWidgetItem *item, QColor col);
|
||||
RDTreeWidgetItem *invisibleRootItem() { return m_root; }
|
||||
void addTopLevelItem(RDTreeWidgetItem *item) { m_root->addChild(item); }
|
||||
RDTreeWidgetItem *topLevelItem(int index) const { return m_root->child(index); }
|
||||
int indexOfTopLevelItem(RDTreeWidgetItem *item) const { return m_root->indexOfChild(item); }
|
||||
RDTreeWidgetItem *takeTopLevelItem(int index) { return m_root->takeChild(index); }
|
||||
int topLevelItemCount() const { return m_root->childCount(); }
|
||||
void beginUpdate();
|
||||
void endUpdate();
|
||||
|
||||
void setColumns(const QStringList &columns);
|
||||
QString headerText(int column) const { return m_headers[column]; }
|
||||
void setHeaderText(int column, const QString &text);
|
||||
RDTreeWidgetItem *selectedItem() const;
|
||||
RDTreeWidgetItem *currentItem() const;
|
||||
void setSelectedItem(RDTreeWidgetItem *node);
|
||||
void setCurrentItem(RDTreeWidgetItem *node);
|
||||
|
||||
RDTreeWidgetItem *itemAt(const QPoint &p) const;
|
||||
RDTreeWidgetItem *itemAt(int x, int y) const { return itemAt(QPoint(x, y)); }
|
||||
void expandItem(RDTreeWidgetItem *item);
|
||||
void scrollToItem(RDTreeWidgetItem *node);
|
||||
|
||||
void clear();
|
||||
|
||||
signals:
|
||||
void mouseMove(QMouseEvent *e);
|
||||
void leave(QEvent *e);
|
||||
void keyPress(QKeyEvent *e);
|
||||
void itemClicked(RDTreeWidgetItem *item, int column);
|
||||
void itemDoubleClicked(RDTreeWidgetItem *item, int column);
|
||||
void itemActivated(RDTreeWidgetItem *item, int column);
|
||||
void currentItemChanged(RDTreeWidgetItem *current, RDTreeWidgetItem *previous);
|
||||
void itemSelectionChanged();
|
||||
|
||||
public slots:
|
||||
|
||||
@@ -59,16 +198,35 @@ private:
|
||||
void focusOutEvent(QFocusEvent *event) override;
|
||||
void keyPressEvent(QKeyEvent *e) override;
|
||||
|
||||
void clearHovers(QTreeWidgetItem *root, QTreeWidgetItem *exception);
|
||||
void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) override;
|
||||
void currentChanged(const QModelIndex ¤t, const QModelIndex &previous) override;
|
||||
|
||||
QColor m_defaultHoverColour;
|
||||
int m_hoverColumn = 0;
|
||||
void setModel(QAbstractItemModel *model) override {}
|
||||
void dataChanged(RDTreeWidgetItem *item, int role);
|
||||
|
||||
friend class RDTreeWidgetModel;
|
||||
friend class RDTreeWidgetItem;
|
||||
|
||||
// invisible root item, used to simplify recursion by even top-level items having a parent
|
||||
RDTreeWidgetItem *m_root;
|
||||
|
||||
RDTreeWidgetModel *m_model;
|
||||
|
||||
QStringList m_headers;
|
||||
|
||||
bool m_queueUpdates;
|
||||
|
||||
RDTreeWidgetItem *m_queuedItem;
|
||||
QPair<int, int> m_lowestIndex;
|
||||
QPair<int, int> m_highestIndex;
|
||||
uint64_t m_queuedRoles;
|
||||
|
||||
RDTreeWidgetItem *m_currentHoverItem = NULL;
|
||||
|
||||
int m_hoverColumn = -1;
|
||||
QIcon m_normalHoverIcon;
|
||||
QIcon m_activeHoverIcon;
|
||||
bool m_hoverHandCursor = false;
|
||||
bool m_clearSelectionOnFocusLoss = true;
|
||||
bool m_clearSelectionOnFocusLoss = false;
|
||||
bool m_activateOnClick = false;
|
||||
|
||||
static const Qt::ItemDataRole hoverIconRole = Qt::ItemDataRole(Qt::UserRole + 10000);
|
||||
static const Qt::ItemDataRole backupNormalIconRole = Qt::ItemDataRole(Qt::UserRole + 10001);
|
||||
static const Qt::ItemDataRole hoverBackColourRole = Qt::ItemDataRole(Qt::UserRole + 10002);
|
||||
static const Qt::ItemDataRole backupBackColourRole = Qt::ItemDataRole(Qt::UserRole + 10002);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user