File indexing completed on 2024-04-21 11:34:11

0001 /*
0002     This file is part of the KDE project
0003     SPDX-FileCopyrightText: 2007-2008 Rafael Fernández López <ereslibre@kde.org>
0004     SPDX-FileCopyrightText: 2008 Kevin Ottens <ervin@kde.org>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-or-later
0007 */
0008 
0009 #include "kwidgetitemdelegate.h"
0010 #include "kwidgetitemdelegate_p.h"
0011 
0012 #include <QAbstractItemView>
0013 #include <QApplication>
0014 #include <QCursor>
0015 #include <QPainter>
0016 #include <QStyleOption>
0017 #include <QTimer>
0018 #include <QTreeView>
0019 
0020 #include "kwidgetitemdelegatepool_p.h"
0021 
0022 Q_DECLARE_METATYPE(QList<QEvent::Type>)
0023 
0024 /**
0025   KWidgetItemDelegatePrivate class that helps to provide binary compatibility between releases.
0026   @internal
0027 */
0028 //@cond PRIVATE
0029 KWidgetItemDelegatePrivate::KWidgetItemDelegatePrivate(KWidgetItemDelegate *q, QObject *parent)
0030     : QObject(parent)
0031     , widgetPool(new KWidgetItemDelegatePool(q))
0032     , q(q)
0033 {
0034 }
0035 
0036 KWidgetItemDelegatePrivate::~KWidgetItemDelegatePrivate()
0037 {
0038     if (!viewDestroyed) {
0039         widgetPool->fullClear();
0040     }
0041     delete widgetPool;
0042 }
0043 
0044 void KWidgetItemDelegatePrivate::_k_slotRowsInserted(const QModelIndex &parent, int start, int end)
0045 {
0046     Q_UNUSED(end);
0047     // We need to update the rows behind the inserted row as well because the widgets need to be
0048     // moved to their new position
0049     updateRowRange(parent, start, model->rowCount(parent), false);
0050 }
0051 
0052 void KWidgetItemDelegatePrivate::_k_slotRowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
0053 {
0054     updateRowRange(parent, start, end, true);
0055 }
0056 
0057 void KWidgetItemDelegatePrivate::_k_slotRowsRemoved(const QModelIndex &parent, int start, int end)
0058 {
0059     Q_UNUSED(end);
0060     // We need to update the rows that come behind the deleted rows because the widgets need to be
0061     // moved to the new position
0062     updateRowRange(parent, start, model->rowCount(parent), false);
0063 }
0064 
0065 void KWidgetItemDelegatePrivate::_k_slotDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
0066 {
0067     for (int i = topLeft.row(); i <= bottomRight.row(); ++i) {
0068         for (int j = topLeft.column(); j <= bottomRight.column(); ++j) {
0069             const QModelIndex index = model->index(i, j, topLeft.parent());
0070             widgetPool->findWidgets(index, optionView(index));
0071         }
0072     }
0073 }
0074 
0075 void KWidgetItemDelegatePrivate::_k_slotLayoutChanged()
0076 {
0077     const auto lst = widgetPool->invalidIndexesWidgets();
0078     for (QWidget *widget : lst) {
0079         widget->setVisible(false);
0080     }
0081     QTimer::singleShot(0, this, SLOT(initializeModel()));
0082 }
0083 
0084 void KWidgetItemDelegatePrivate::_k_slotModelReset()
0085 {
0086     widgetPool->fullClear();
0087     QTimer::singleShot(0, this, SLOT(initializeModel()));
0088 }
0089 
0090 void KWidgetItemDelegatePrivate::_k_slotSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
0091 {
0092     const auto lstSelected = selected.indexes();
0093     for (const QModelIndex &index : lstSelected) {
0094         widgetPool->findWidgets(index, optionView(index));
0095     }
0096     const auto lstDeselected = deselected.indexes();
0097     for (const QModelIndex &index : lstDeselected) {
0098         widgetPool->findWidgets(index, optionView(index));
0099     }
0100 }
0101 
0102 void KWidgetItemDelegatePrivate::updateRowRange(const QModelIndex &parent, int start, int end, bool isRemoving)
0103 {
0104     int i = start;
0105     while (i <= end) {
0106         for (int j = 0; j < model->columnCount(parent); ++j) {
0107             const QModelIndex index = model->index(i, j, parent);
0108             const QList<QWidget *> widgetList =
0109                 widgetPool->findWidgets(index,
0110                                         optionView(index),
0111                                         isRemoving ? KWidgetItemDelegatePool::NotUpdateWidgets : KWidgetItemDelegatePool::UpdateWidgets);
0112             if (isRemoving) {
0113                 widgetPool->d->allocatedWidgets.removeAll(widgetList);
0114                 for (QWidget *widget : widgetList) {
0115                     const QModelIndex idx = widgetPool->d->widgetInIndex[widget];
0116                     widgetPool->d->usedWidgets.remove(idx);
0117                     widgetPool->d->widgetInIndex.remove(widget);
0118                     delete widget;
0119                 }
0120             }
0121         }
0122         i++;
0123     }
0124 }
0125 
0126 inline QStyleOptionViewItem KWidgetItemDelegatePrivate::optionView(const QModelIndex &index)
0127 {
0128     QStyleOptionViewItem optionView;
0129     optionView.initFrom(itemView->viewport());
0130     optionView.rect = itemView->visualRect(index);
0131     optionView.decorationSize = itemView->iconSize();
0132     return optionView;
0133 }
0134 
0135 void KWidgetItemDelegatePrivate::initializeModel(const QModelIndex &parent)
0136 {
0137     if (!model) {
0138         return;
0139     }
0140 
0141     for (int i = 0; i < model->rowCount(parent); ++i) {
0142         for (int j = 0; j < model->columnCount(parent); ++j) {
0143             const QModelIndex index = model->index(i, j, parent);
0144             if (index.isValid()) {
0145                 widgetPool->findWidgets(index, optionView(index));
0146             }
0147         }
0148         // Check if we need to go recursively through the children of parent (if any) to initialize
0149         // all possible indexes that are shown.
0150         const QModelIndex index = model->index(i, 0, parent);
0151         if (index.isValid() && model->hasChildren(index)) {
0152             initializeModel(index);
0153         }
0154     }
0155 }
0156 //@endcond
0157 
0158 KWidgetItemDelegate::KWidgetItemDelegate(QAbstractItemView *itemView, QObject *parent)
0159     : QAbstractItemDelegate(parent)
0160     , d(new KWidgetItemDelegatePrivate(this))
0161 {
0162     Q_ASSERT(itemView);
0163 
0164     itemView->setMouseTracking(true);
0165     itemView->viewport()->setAttribute(Qt::WA_Hover);
0166 
0167     d->itemView = itemView;
0168 
0169     itemView->viewport()->installEventFilter(d.get()); // mouse events
0170     itemView->installEventFilter(d.get()); // keyboard events
0171 
0172     if (qobject_cast<QTreeView *>(itemView)) {
0173         connect(itemView, SIGNAL(collapsed(QModelIndex)), d.get(), SLOT(initializeModel()));
0174         connect(itemView, SIGNAL(expanded(QModelIndex)), d.get(), SLOT(initializeModel()));
0175     }
0176 }
0177 
0178 KWidgetItemDelegate::~KWidgetItemDelegate() = default;
0179 
0180 QAbstractItemView *KWidgetItemDelegate::itemView() const
0181 {
0182     return d->itemView;
0183 }
0184 
0185 QPersistentModelIndex KWidgetItemDelegate::focusedIndex() const
0186 {
0187     const QPersistentModelIndex idx = d->widgetPool->d->widgetInIndex.value(QApplication::focusWidget());
0188     if (idx.isValid()) {
0189         return idx;
0190     }
0191     // Use the mouse position, if the widget refused to take keyboard focus.
0192     const QPoint pos = d->itemView->viewport()->mapFromGlobal(QCursor::pos());
0193     return d->itemView->indexAt(pos);
0194 }
0195 
0196 #if KITEMVIEWS_BUILD_DEPRECATED_SINCE(4, 2)
0197 void KWidgetItemDelegate::paintWidgets(QPainter *painter, const QStyleOptionViewItem &option, const QPersistentModelIndex &index) const
0198 {
0199     Q_UNUSED(painter);
0200     Q_UNUSED(option);
0201     Q_UNUSED(index);
0202 }
0203 #endif
0204 
0205 //@cond PRIVATE
0206 bool KWidgetItemDelegatePrivate::eventFilter(QObject *watched, QEvent *event)
0207 {
0208     if (event->type() == QEvent::Destroy) {
0209         // we care for the view since it deletes the widgets (parentage).
0210         // if the view hasn't been deleted, it might be that just the
0211         // delegate is removed from it, in which case we need to remove the widgets
0212         // manually, otherwise they still get drawn.
0213         if (watched == itemView) {
0214             viewDestroyed = true;
0215         }
0216         return false;
0217     }
0218 
0219     Q_ASSERT(itemView);
0220 
0221     // clang-format off
0222     if (model != itemView->model()) {
0223         if (model) {
0224             disconnect(model, SIGNAL(rowsInserted(QModelIndex,int,int)), q, SLOT(_k_slotRowsInserted(QModelIndex,int,int)));
0225             disconnect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), q, SLOT(_k_slotRowsAboutToBeRemoved(QModelIndex,int,int)));
0226             disconnect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)), q, SLOT(_k_slotRowsRemoved(QModelIndex,int,int)));
0227             disconnect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), q, SLOT(_k_slotDataChanged(QModelIndex,QModelIndex)));
0228             disconnect(model, SIGNAL(layoutChanged()), q, SLOT(_k_slotLayoutChanged()));
0229             disconnect(model, SIGNAL(modelReset()), q, SLOT(_k_slotModelReset()));
0230         }
0231         model = itemView->model();
0232         connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)), q, SLOT(_k_slotRowsInserted(QModelIndex,int,int)));
0233         connect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), q, SLOT(_k_slotRowsAboutToBeRemoved(QModelIndex,int,int)));
0234         connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)), q, SLOT(_k_slotRowsRemoved(QModelIndex,int,int)));
0235         connect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), q, SLOT(_k_slotDataChanged(QModelIndex,QModelIndex)));
0236         connect(model, SIGNAL(layoutChanged()), q, SLOT(_k_slotLayoutChanged()));
0237         connect(model, SIGNAL(modelReset()), q, SLOT(_k_slotModelReset()));
0238         QTimer::singleShot(0, this, SLOT(initializeModel()));
0239     }
0240 
0241     if (selectionModel != itemView->selectionModel()) {
0242         if (selectionModel) {
0243             disconnect(selectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)), q, SLOT(_k_slotSelectionChanged(QItemSelection,QItemSelection)));
0244         }
0245         selectionModel = itemView->selectionModel();
0246         connect(selectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)), q, SLOT(_k_slotSelectionChanged(QItemSelection,QItemSelection)));
0247         QTimer::singleShot(0, this, SLOT(initializeModel()));
0248     }
0249     // clang-format on
0250 
0251     switch (event->type()) {
0252     case QEvent::Polish:
0253     case QEvent::Resize:
0254         if (!qobject_cast<QAbstractItemView *>(watched)) {
0255             QTimer::singleShot(0, this, SLOT(initializeModel()));
0256         }
0257         break;
0258     case QEvent::FocusIn:
0259     case QEvent::FocusOut:
0260         if (qobject_cast<QAbstractItemView *>(watched)) {
0261             const auto lst = selectionModel->selectedIndexes();
0262             for (const QModelIndex &index : lst) {
0263                 if (index.isValid()) {
0264                     widgetPool->findWidgets(index, optionView(index));
0265                 }
0266             }
0267         }
0268         break;
0269     default:
0270         break;
0271     }
0272 
0273     return QObject::eventFilter(watched, event);
0274 }
0275 //@endcond
0276 
0277 void KWidgetItemDelegate::setBlockedEventTypes(QWidget *widget, QList<QEvent::Type> types) const
0278 {
0279     widget->setProperty("goya:blockedEventTypes", QVariant::fromValue(types));
0280 }
0281 
0282 QList<QEvent::Type> KWidgetItemDelegate::blockedEventTypes(QWidget *widget) const
0283 {
0284     return widget->property("goya:blockedEventTypes").value<QList<QEvent::Type>>();
0285 }
0286 
0287 void KWidgetItemDelegate::resetModel()
0288 {
0289     d->_k_slotModelReset();
0290 }
0291 
0292 #include "moc_kwidgetitemdelegate.cpp"
0293 #include "moc_kwidgetitemdelegate_p.cpp"