File indexing completed on 2025-01-05 04:47:09

0001 /*
0002     SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB,
0003         a KDAB Group company, info@kdab.net
0004     SPDX-FileContributor: Stephen Kelly <stephen@kdab.com>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-or-later
0007 */
0008 
0009 #include "progressspinnerdelegate_p.h"
0010 
0011 #include "entitytreemodel.h"
0012 
0013 #include <KPixmapSequenceLoader>
0014 
0015 #include <QAbstractItemView>
0016 #include <QTimerEvent>
0017 
0018 using namespace Akonadi;
0019 
0020 DelegateAnimator::DelegateAnimator(QAbstractItemView *view)
0021     : QObject(view)
0022     , m_view(view)
0023     , m_pixmapSequence(KPixmapSequenceLoader::load(QStringLiteral("process-working"), 22))
0024 {
0025 }
0026 
0027 void DelegateAnimator::push(const QModelIndex &index)
0028 {
0029     if (m_animations.isEmpty()) {
0030         m_timerId = startTimer(200);
0031     }
0032     m_animations.insert(Animation(index));
0033 }
0034 
0035 void DelegateAnimator::pop(const QModelIndex &index)
0036 {
0037     if (m_animations.remove(Animation(index))) {
0038         if (m_animations.isEmpty() && m_timerId != -1) {
0039             killTimer(m_timerId);
0040             m_timerId = -1;
0041         }
0042     }
0043 }
0044 
0045 void DelegateAnimator::timerEvent(QTimerEvent *event)
0046 {
0047     if (!(event->timerId() == m_timerId && m_view)) {
0048         QObject::timerEvent(event);
0049         return;
0050     }
0051 
0052     QRegion region;
0053     // working copy as m_animations could be modified in the loop by pop()
0054     const QSet<Animation> currentAnimations(m_animations);
0055     for (const Animation &animation : currentAnimations) {
0056         // Check if loading is finished (we might not be notified, if the index is scrolled out of view)
0057         const QVariant fetchState = animation.index.data(Akonadi::EntityTreeModel::FetchStateRole);
0058         if (fetchState.toInt() != Akonadi::EntityTreeModel::FetchingState) {
0059             pop(animation.index);
0060             continue;
0061         }
0062 
0063         // This repaints the entire delegate (icon and text).
0064         // TODO: See if there's a way to repaint only part of it (the icon).
0065         animation.nextFrame();
0066         const QRect rect = m_view->visualRect(animation.index);
0067         region += rect;
0068     }
0069 
0070     if (!region.isEmpty()) {
0071         m_view->viewport()->update(region);
0072     }
0073 }
0074 
0075 QPixmap DelegateAnimator::sequenceFrame(const QModelIndex &index)
0076 {
0077     for (const Animation &animation : std::as_const(m_animations)) {
0078         if (animation.index == index) {
0079             return m_pixmapSequence.frameAt(animation.frame);
0080         }
0081     }
0082     return QPixmap();
0083 }
0084 
0085 ProgressSpinnerDelegate::ProgressSpinnerDelegate(DelegateAnimator *animator, QObject *parent)
0086     : QStyledItemDelegate(parent)
0087     , m_animator(animator)
0088 {
0089 }
0090 
0091 void ProgressSpinnerDelegate::initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const
0092 {
0093     QStyledItemDelegate::initStyleOption(option, index);
0094 
0095     const QVariant fetchState = index.data(Akonadi::EntityTreeModel::FetchStateRole);
0096     if (!fetchState.isValid() || fetchState.toInt() != Akonadi::EntityTreeModel::FetchingState) {
0097         m_animator->pop(index);
0098         return;
0099     }
0100 
0101     m_animator->push(index);
0102 
0103     if (auto v = qstyleoption_cast<QStyleOptionViewItem *>(option)) {
0104         v->icon = m_animator->sequenceFrame(index);
0105     }
0106 }
0107 
0108 size_t Akonadi::qHash(const Akonadi::DelegateAnimator::Animation &anim, size_t seed) noexcept
0109 {
0110     return qHash(anim.index, seed);
0111 }
0112 
0113 #include "moc_progressspinnerdelegate_p.cpp"