File indexing completed on 2024-04-14 03:54:02

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 //@cond PRIVATE
0197 bool KWidgetItemDelegatePrivate::eventFilter(QObject *watched, QEvent *event)
0198 {
0199     if (event->type() == QEvent::Destroy) {
0200         // we care for the view since it deletes the widgets (parentage).
0201         // if the view hasn't been deleted, it might be that just the
0202         // delegate is removed from it, in which case we need to remove the widgets
0203         // manually, otherwise they still get drawn.
0204         if (watched == itemView) {
0205             viewDestroyed = true;
0206         }
0207         return false;
0208     }
0209 
0210     Q_ASSERT(itemView);
0211 
0212     // clang-format off
0213     if (model != itemView->model()) {
0214         if (model) {
0215             disconnect(model, SIGNAL(rowsInserted(QModelIndex,int,int)), q, SLOT(_k_slotRowsInserted(QModelIndex,int,int)));
0216             disconnect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), q, SLOT(_k_slotRowsAboutToBeRemoved(QModelIndex,int,int)));
0217             disconnect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)), q, SLOT(_k_slotRowsRemoved(QModelIndex,int,int)));
0218             disconnect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), q, SLOT(_k_slotDataChanged(QModelIndex,QModelIndex)));
0219             disconnect(model, SIGNAL(layoutChanged()), q, SLOT(_k_slotLayoutChanged()));
0220             disconnect(model, SIGNAL(modelReset()), q, SLOT(_k_slotModelReset()));
0221         }
0222         model = itemView->model();
0223         connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)), q, SLOT(_k_slotRowsInserted(QModelIndex,int,int)));
0224         connect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), q, SLOT(_k_slotRowsAboutToBeRemoved(QModelIndex,int,int)));
0225         connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)), q, SLOT(_k_slotRowsRemoved(QModelIndex,int,int)));
0226         connect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), q, SLOT(_k_slotDataChanged(QModelIndex,QModelIndex)));
0227         connect(model, SIGNAL(layoutChanged()), q, SLOT(_k_slotLayoutChanged()));
0228         connect(model, SIGNAL(modelReset()), q, SLOT(_k_slotModelReset()));
0229         QTimer::singleShot(0, this, SLOT(initializeModel()));
0230     }
0231 
0232     if (selectionModel != itemView->selectionModel()) {
0233         if (selectionModel) {
0234             disconnect(selectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)), q, SLOT(_k_slotSelectionChanged(QItemSelection,QItemSelection)));
0235         }
0236         selectionModel = itemView->selectionModel();
0237         connect(selectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)), q, SLOT(_k_slotSelectionChanged(QItemSelection,QItemSelection)));
0238         QTimer::singleShot(0, this, SLOT(initializeModel()));
0239     }
0240     // clang-format on
0241 
0242     switch (event->type()) {
0243     case QEvent::Polish:
0244     case QEvent::Resize:
0245         if (!qobject_cast<QAbstractItemView *>(watched)) {
0246             QTimer::singleShot(0, this, SLOT(initializeModel()));
0247         }
0248         break;
0249     case QEvent::FocusIn:
0250     case QEvent::FocusOut:
0251         if (qobject_cast<QAbstractItemView *>(watched)) {
0252             const auto lst = selectionModel->selectedIndexes();
0253             for (const QModelIndex &index : lst) {
0254                 if (index.isValid()) {
0255                     widgetPool->findWidgets(index, optionView(index));
0256                 }
0257             }
0258         }
0259         break;
0260     default:
0261         break;
0262     }
0263 
0264     return QObject::eventFilter(watched, event);
0265 }
0266 //@endcond
0267 
0268 void KWidgetItemDelegate::setBlockedEventTypes(QWidget *widget, const QList<QEvent::Type> &types) const
0269 {
0270     widget->setProperty("goya:blockedEventTypes", QVariant::fromValue(types));
0271 }
0272 
0273 QList<QEvent::Type> KWidgetItemDelegate::blockedEventTypes(QWidget *widget) const
0274 {
0275     return widget->property("goya:blockedEventTypes").value<QList<QEvent::Type>>();
0276 }
0277 
0278 void KWidgetItemDelegate::resetModel()
0279 {
0280     d->_k_slotModelReset();
0281 }
0282 
0283 #include "moc_kwidgetitemdelegate.cpp"
0284 #include "moc_kwidgetitemdelegate_p.cpp"