Advance Wayland and KDE package bring-up
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -0,0 +1,411 @@
|
||||
/*
|
||||
This file is part of the KDE libraries
|
||||
SPDX-FileCopyrightText: 2006, 2007 Andreas Hartmetz <ahartmetz@gmail.com>
|
||||
SPDX-FileCopyrightText: 2008 Urs Wolfer <uwolfer@kde.org>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "kextendableitemdelegate.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QModelIndex>
|
||||
#include <QPainter>
|
||||
#include <QScrollBar>
|
||||
#include <QTreeView>
|
||||
|
||||
class KExtendableItemDelegatePrivate
|
||||
{
|
||||
public:
|
||||
KExtendableItemDelegatePrivate(KExtendableItemDelegate *parent)
|
||||
: q(parent)
|
||||
, stateTick(0)
|
||||
, cachedStateTick(-1)
|
||||
, cachedRow(-20)
|
||||
, // Qt uses -1 for invalid indices
|
||||
extenderHeight(0)
|
||||
|
||||
{
|
||||
}
|
||||
|
||||
void _k_extenderDestructionHandler(QObject *destroyed);
|
||||
void _k_verticalScroll();
|
||||
|
||||
QSize maybeExtendedSize(const QStyleOptionViewItem &option, const QModelIndex &index) const;
|
||||
QModelIndex indexOfExtendedColumnInSameRow(const QModelIndex &index) const;
|
||||
void scheduleUpdateViewLayout();
|
||||
|
||||
KExtendableItemDelegate *const q;
|
||||
|
||||
/**
|
||||
* Delete all active extenders
|
||||
*/
|
||||
void deleteExtenders();
|
||||
|
||||
// this will trigger a lot of auto-casting QModelIndex <-> QPersistentModelIndex
|
||||
QHash<QPersistentModelIndex, QWidget *> extenders;
|
||||
QHash<QWidget *, QPersistentModelIndex> extenderIndices;
|
||||
QMultiHash<QWidget *, QPersistentModelIndex> deletionQueue;
|
||||
QPixmap extendPixmap;
|
||||
QPixmap contractPixmap;
|
||||
int stateTick;
|
||||
int cachedStateTick;
|
||||
int cachedRow;
|
||||
QModelIndex cachedParentIndex;
|
||||
QWidget *extender = nullptr;
|
||||
int extenderHeight;
|
||||
};
|
||||
|
||||
KExtendableItemDelegate::KExtendableItemDelegate(QAbstractItemView *parent)
|
||||
: QStyledItemDelegate(parent)
|
||||
, d(new KExtendableItemDelegatePrivate(this))
|
||||
{
|
||||
connect(parent->verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(_k_verticalScroll()));
|
||||
}
|
||||
|
||||
KExtendableItemDelegate::~KExtendableItemDelegate() = default;
|
||||
|
||||
void KExtendableItemDelegate::extendItem(QWidget *ext, const QModelIndex &index)
|
||||
{
|
||||
// qDebug() << "Creating extender at " << ext << " for item " << index.model()->data(index,Qt::DisplayRole).toString();
|
||||
|
||||
if (!ext || !index.isValid()) {
|
||||
return;
|
||||
}
|
||||
// maintain the invariant "zero or one extender per row"
|
||||
d->stateTick++;
|
||||
contractItem(d->indexOfExtendedColumnInSameRow(index));
|
||||
d->stateTick++;
|
||||
// reparent, as promised in the docs
|
||||
QAbstractItemView *aiv = qobject_cast<QAbstractItemView *>(parent());
|
||||
if (!aiv) {
|
||||
return;
|
||||
}
|
||||
ext->setParent(aiv->viewport());
|
||||
d->extenders.insert(index, ext);
|
||||
d->extenderIndices.insert(ext, index);
|
||||
connect(ext, SIGNAL(destroyed(QObject *)), this, SLOT(_k_extenderDestructionHandler(QObject *)));
|
||||
Q_EMIT extenderCreated(ext, index);
|
||||
d->scheduleUpdateViewLayout();
|
||||
}
|
||||
|
||||
void KExtendableItemDelegate::contractItem(const QModelIndex &index)
|
||||
{
|
||||
QWidget *extender = d->extenders.value(index);
|
||||
if (!extender) {
|
||||
return;
|
||||
}
|
||||
// qDebug() << "Collapse extender at " << extender << " for item " << index.model()->data(index,Qt::DisplayRole).toString();
|
||||
extender->hide();
|
||||
extender->deleteLater();
|
||||
|
||||
QPersistentModelIndex persistentIndex = d->extenderIndices.take(extender);
|
||||
d->extenders.remove(persistentIndex);
|
||||
|
||||
d->deletionQueue.insert(extender, persistentIndex);
|
||||
|
||||
d->scheduleUpdateViewLayout();
|
||||
}
|
||||
|
||||
void KExtendableItemDelegate::contractAll()
|
||||
{
|
||||
d->deleteExtenders();
|
||||
}
|
||||
|
||||
// slot
|
||||
void KExtendableItemDelegatePrivate::_k_extenderDestructionHandler(QObject *destroyed)
|
||||
{
|
||||
// qDebug() << "Removing extender at " << destroyed;
|
||||
|
||||
QWidget *extender = static_cast<QWidget *>(destroyed);
|
||||
stateTick++;
|
||||
|
||||
QPersistentModelIndex persistentIndex = deletionQueue.take(extender);
|
||||
if (persistentIndex.isValid() && q->receivers(SIGNAL(extenderDestroyed(QWidget *, QModelIndex))) != 0) {
|
||||
QModelIndex index = persistentIndex;
|
||||
Q_EMIT q->extenderDestroyed(extender, index);
|
||||
}
|
||||
|
||||
scheduleUpdateViewLayout();
|
||||
}
|
||||
|
||||
// slot
|
||||
void KExtendableItemDelegatePrivate::_k_verticalScroll()
|
||||
{
|
||||
for (QWidget *extender : std::as_const(extenders)) {
|
||||
// Fast scrolling can lead to artifacts where extenders stay in the viewport
|
||||
// of the parent's scroll area even though their items are scrolled out.
|
||||
// Therefore we hide all extenders when scrolling.
|
||||
// In paintEvent() show() will be called on actually visible extenders and
|
||||
// Qt's double buffering takes care of eliminating flicker.
|
||||
// ### This scales badly to many extenders. There are probably better ways to
|
||||
// avoid the artifacts.
|
||||
extender->hide();
|
||||
}
|
||||
}
|
||||
|
||||
bool KExtendableItemDelegate::isExtended(const QModelIndex &index) const
|
||||
{
|
||||
return d->extenders.value(index);
|
||||
}
|
||||
|
||||
QSize KExtendableItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
|
||||
{
|
||||
QSize ret;
|
||||
|
||||
if (!d->extenders.isEmpty()) {
|
||||
ret = d->maybeExtendedSize(option, index);
|
||||
} else {
|
||||
ret = QStyledItemDelegate::sizeHint(option, index);
|
||||
}
|
||||
|
||||
bool showExtensionIndicator = index.model() ? index.model()->data(index, ShowExtensionIndicatorRole).toBool() : false;
|
||||
if (showExtensionIndicator) {
|
||||
ret.rwidth() += d->extendPixmap.width() / d->extendPixmap.devicePixelRatio();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void KExtendableItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
|
||||
{
|
||||
int indicatorX = 0;
|
||||
int indicatorY = 0;
|
||||
|
||||
QStyleOptionViewItem indicatorOption(option);
|
||||
initStyleOption(&indicatorOption, index);
|
||||
if (index.column() == 0) {
|
||||
indicatorOption.viewItemPosition = QStyleOptionViewItem::Beginning;
|
||||
} else if (index.column() == index.model()->columnCount() - 1) {
|
||||
indicatorOption.viewItemPosition = QStyleOptionViewItem::End;
|
||||
} else {
|
||||
indicatorOption.viewItemPosition = QStyleOptionViewItem::Middle;
|
||||
}
|
||||
|
||||
QStyleOptionViewItem itemOption(option);
|
||||
initStyleOption(&itemOption, index);
|
||||
if (index.column() == 0) {
|
||||
itemOption.viewItemPosition = QStyleOptionViewItem::Beginning;
|
||||
} else if (index.column() == index.model()->columnCount() - 1) {
|
||||
itemOption.viewItemPosition = QStyleOptionViewItem::End;
|
||||
} else {
|
||||
itemOption.viewItemPosition = QStyleOptionViewItem::Middle;
|
||||
}
|
||||
|
||||
const bool showExtensionIndicator = index.model()->data(index, ShowExtensionIndicatorRole).toBool();
|
||||
|
||||
if (showExtensionIndicator) {
|
||||
const QSize extendPixmapSize = d->extendPixmap.size() / d->extendPixmap.devicePixelRatio();
|
||||
if (QApplication::isRightToLeft()) {
|
||||
indicatorX = option.rect.right() - extendPixmapSize.width();
|
||||
itemOption.rect.setRight(indicatorX);
|
||||
indicatorOption.rect.setLeft(indicatorX);
|
||||
} else {
|
||||
indicatorX = option.rect.left();
|
||||
indicatorOption.rect.setRight(indicatorX + extendPixmapSize.width());
|
||||
itemOption.rect.setLeft(indicatorX + extendPixmapSize.width());
|
||||
}
|
||||
indicatorY = option.rect.top() + ((option.rect.height() - extendPixmapSize.height()) >> 1);
|
||||
}
|
||||
|
||||
// fast path
|
||||
if (d->extenders.isEmpty()) {
|
||||
QStyledItemDelegate::paint(painter, itemOption, index);
|
||||
if (showExtensionIndicator) {
|
||||
painter->save();
|
||||
QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &indicatorOption, painter);
|
||||
painter->restore();
|
||||
painter->drawPixmap(indicatorX, indicatorY, d->extendPixmap);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
int row = index.row();
|
||||
QModelIndex parentIndex = index.parent();
|
||||
|
||||
// indexOfExtendedColumnInSameRow() is very expensive, try to avoid calling it.
|
||||
if (row != d->cachedRow //
|
||||
|| d->cachedStateTick != d->stateTick //
|
||||
|| d->cachedParentIndex != parentIndex) {
|
||||
d->extender = d->extenders.value(d->indexOfExtendedColumnInSameRow(index));
|
||||
d->cachedStateTick = d->stateTick;
|
||||
d->cachedRow = row;
|
||||
d->cachedParentIndex = parentIndex;
|
||||
if (d->extender) {
|
||||
d->extenderHeight = d->extender->sizeHint().height();
|
||||
}
|
||||
}
|
||||
|
||||
if (!d->extender) {
|
||||
QStyledItemDelegate::paint(painter, itemOption, index);
|
||||
if (showExtensionIndicator) {
|
||||
painter->save();
|
||||
QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &indicatorOption, painter);
|
||||
painter->restore();
|
||||
painter->drawPixmap(indicatorX, indicatorY, d->extendPixmap);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// an extender is present - make two rectangles: one to paint the original item, one for the extender
|
||||
if (isExtended(index)) {
|
||||
QStyleOptionViewItem extOption(option);
|
||||
initStyleOption(&extOption, index);
|
||||
extOption.rect = extenderRect(d->extender, option, index);
|
||||
updateExtenderGeometry(d->extender, extOption, index);
|
||||
// if we show it before, it will briefly flash in the wrong location.
|
||||
// the downside is, of course, that an api user effectively can't hide it.
|
||||
d->extender->show();
|
||||
}
|
||||
|
||||
indicatorOption.rect.setHeight(option.rect.height() - d->extenderHeight);
|
||||
itemOption.rect.setHeight(option.rect.height() - d->extenderHeight);
|
||||
// tricky:make sure that the modified options' rect really has the
|
||||
// same height as the unchanged option.rect if no extender is present
|
||||
//(seems to work OK)
|
||||
QStyledItemDelegate::paint(painter, itemOption, index);
|
||||
|
||||
if (showExtensionIndicator) {
|
||||
const int extendPixmapHeight = d->extendPixmap.height() / d->extendPixmap.devicePixelRatio();
|
||||
// indicatorOption's height changed, change this too
|
||||
indicatorY = indicatorOption.rect.top() + ((indicatorOption.rect.height() - extendPixmapHeight) >> 1);
|
||||
painter->save();
|
||||
QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &indicatorOption, painter);
|
||||
painter->restore();
|
||||
|
||||
if (d->extenders.contains(index)) {
|
||||
painter->drawPixmap(indicatorX, indicatorY, d->contractPixmap);
|
||||
} else {
|
||||
painter->drawPixmap(indicatorX, indicatorY, d->extendPixmap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QRect KExtendableItemDelegate::extenderRect(QWidget *extender, const QStyleOptionViewItem &option, const QModelIndex &index) const
|
||||
{
|
||||
Q_ASSERT(extender);
|
||||
QRect rect(option.rect);
|
||||
rect.setTop(rect.bottom() + 1 - extender->sizeHint().height());
|
||||
|
||||
int indentation = 0;
|
||||
if (QTreeView *tv = qobject_cast<QTreeView *>(parent())) {
|
||||
int indentSteps = 0;
|
||||
for (QModelIndex idx(index.parent()); idx.isValid(); idx = idx.parent()) {
|
||||
indentSteps++;
|
||||
}
|
||||
if (tv->rootIsDecorated()) {
|
||||
indentSteps++;
|
||||
}
|
||||
indentation = indentSteps * tv->indentation();
|
||||
}
|
||||
|
||||
QAbstractScrollArea *container = qobject_cast<QAbstractScrollArea *>(parent());
|
||||
Q_ASSERT(container);
|
||||
if (qApp->isLeftToRight()) {
|
||||
rect.setLeft(indentation);
|
||||
rect.setRight(container->viewport()->width() - 1);
|
||||
} else {
|
||||
rect.setRight(container->viewport()->width() - 1 - indentation);
|
||||
rect.setLeft(0);
|
||||
}
|
||||
return rect;
|
||||
}
|
||||
|
||||
QSize KExtendableItemDelegatePrivate::maybeExtendedSize(const QStyleOptionViewItem &option, const QModelIndex &index) const
|
||||
{
|
||||
QWidget *extender = extenders.value(index);
|
||||
QSize size(q->QStyledItemDelegate::sizeHint(option, index));
|
||||
if (!extender) {
|
||||
return size;
|
||||
}
|
||||
// add extender height to maximum height of any column in our row
|
||||
int itemHeight = size.height();
|
||||
|
||||
int row = index.row();
|
||||
int thisColumn = index.column();
|
||||
|
||||
// this is quite slow, but Qt is smart about when to call sizeHint().
|
||||
for (int column = 0; index.model()->columnCount() < column; column++) {
|
||||
if (column == thisColumn) {
|
||||
continue;
|
||||
}
|
||||
QModelIndex neighborIndex(index.sibling(row, column));
|
||||
if (!neighborIndex.isValid()) {
|
||||
break;
|
||||
}
|
||||
itemHeight = qMax(itemHeight, q->QStyledItemDelegate::sizeHint(option, neighborIndex).height());
|
||||
}
|
||||
|
||||
// we only want to reserve vertical space, the horizontal extender layout is our private business.
|
||||
size.rheight() = itemHeight + extender->sizeHint().height();
|
||||
return size;
|
||||
}
|
||||
|
||||
QModelIndex KExtendableItemDelegatePrivate::indexOfExtendedColumnInSameRow(const QModelIndex &index) const
|
||||
{
|
||||
const QAbstractItemModel *const model = index.model();
|
||||
const QModelIndex parentIndex(index.parent());
|
||||
const int row = index.row();
|
||||
const int columnCount = model->columnCount();
|
||||
|
||||
// slow, slow, slow
|
||||
for (int column = 0; column < columnCount; column++) {
|
||||
QModelIndex indexOfExt(model->index(row, column, parentIndex));
|
||||
if (extenders.value(indexOfExt)) {
|
||||
return indexOfExt;
|
||||
}
|
||||
}
|
||||
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
void KExtendableItemDelegate::updateExtenderGeometry(QWidget *extender, const QStyleOptionViewItem &option, const QModelIndex &index) const
|
||||
{
|
||||
Q_UNUSED(index);
|
||||
extender->setGeometry(option.rect);
|
||||
}
|
||||
|
||||
void KExtendableItemDelegatePrivate::deleteExtenders()
|
||||
{
|
||||
for (QWidget *ext : std::as_const(extenders)) {
|
||||
ext->hide();
|
||||
ext->deleteLater();
|
||||
}
|
||||
deletionQueue.unite(extenderIndices);
|
||||
extenders.clear();
|
||||
extenderIndices.clear();
|
||||
}
|
||||
|
||||
// make the view re-ask for sizeHint() and redisplay items with their new size
|
||||
//### starting from Qt 4.4 we could emit sizeHintChanged() instead
|
||||
void KExtendableItemDelegatePrivate::scheduleUpdateViewLayout()
|
||||
{
|
||||
QAbstractItemView *aiv = qobject_cast<QAbstractItemView *>(q->parent());
|
||||
// prevent crashes during destruction of the view
|
||||
if (aiv) {
|
||||
// dirty hack to call aiv's protected scheduleDelayedItemsLayout()
|
||||
aiv->setRootIndex(aiv->rootIndex());
|
||||
}
|
||||
}
|
||||
|
||||
void KExtendableItemDelegate::setExtendPixmap(const QPixmap &pixmap)
|
||||
{
|
||||
d->extendPixmap = pixmap;
|
||||
}
|
||||
|
||||
void KExtendableItemDelegate::setContractPixmap(const QPixmap &pixmap)
|
||||
{
|
||||
d->contractPixmap = pixmap;
|
||||
}
|
||||
|
||||
QPixmap KExtendableItemDelegate::extendPixmap()
|
||||
{
|
||||
return d->extendPixmap;
|
||||
}
|
||||
|
||||
QPixmap KExtendableItemDelegate::contractPixmap()
|
||||
{
|
||||
return d->contractPixmap;
|
||||
}
|
||||
|
||||
#include "moc_kextendableitemdelegate.cpp"
|
||||
Reference in New Issue
Block a user