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"