Akonadi

collectionstatisticsdelegate.cpp
1/*
2 SPDX-FileCopyrightText: 2008 Thomas McGuire <thomas.mcguire@gmx.net>
3 SPDX-FileCopyrightText: 2012-2024 Laurent Montel <montel@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "collectionstatisticsdelegate.h"
9
10#include "akonadiwidgets_debug.h"
11#include <KColorScheme>
12#include <KFormat>
13
14#include <QAbstractItemView>
15#include <QPainter>
16#include <QStyle>
17#include <QStyleOption>
18#include <QStyleOptionViewItem>
19#include <QTreeView>
20
21#include "collection.h"
22#include "collectionstatistics.h"
23#include "entitytreemodel.h"
24#include "progressspinnerdelegate_p.h"
25
26using namespace Akonadi;
27
28namespace Akonadi
29{
30enum CountType {
31 UnreadCount,
32 TotalCount,
33};
34
35class CollectionStatisticsDelegatePrivate
36{
37public:
38 QAbstractItemView *const parent;
39 bool drawUnreadAfterFolder = false;
40 DelegateAnimator *animator = nullptr;
41 QColor mSelectedUnreadColor;
42 QColor mDeselectedUnreadColor;
43
44 explicit CollectionStatisticsDelegatePrivate(QAbstractItemView *treeView)
45 : parent(treeView)
46 {
47 updateColor();
48 }
49
50 void getCountRecursive(const QModelIndex &index, qint64 &totalCount, qint64 &unreadCount, qint64 &totalSize) const
51 {
52 auto collection = qvariant_cast<Collection>(index.data(EntityTreeModel::CollectionRole));
53 // Do not assert on invalid collections, since a collection may be deleted
54 // in the meantime and deleted collections are invalid.
55 if (collection.isValid()) {
56 CollectionStatistics statistics = collection.statistics();
57 totalCount += qMax(0LL, statistics.count());
58 unreadCount += qMax(0LL, statistics.unreadCount());
59 totalSize += qMax(0LL, statistics.size());
60 if (index.model()->hasChildren(index)) {
61 const int rowCount = index.model()->rowCount(index);
62 for (int row = 0; row < rowCount; row++) {
63 static const int column = 0;
64 getCountRecursive(index.model()->index(row, column, index), totalCount, unreadCount, totalSize);
65 }
66 }
67 }
68 }
69
70 void updateColor()
71 {
74 }
75};
76
77} // namespace Akonadi
78
80 : QStyledItemDelegate(parent)
81 , d_ptr(new CollectionStatisticsDelegatePrivate(parent))
82{
83}
84
86 : QStyledItemDelegate(parent)
87 , d_ptr(new CollectionStatisticsDelegatePrivate(parent))
88{
89}
90
92
94{
96 d->drawUnreadAfterFolder = enable;
97}
98
100{
102 return d->drawUnreadAfterFolder;
103}
104
106{
108 if (enable == (d->animator != nullptr)) {
109 return;
110 }
111 if (enable) {
112 Q_ASSERT(!d->animator);
113 auto animator = new Akonadi::DelegateAnimator(d->parent);
114 d->animator = animator;
115 } else {
116 delete d->animator;
117 d->animator = nullptr;
118 }
119}
120
121bool CollectionStatisticsDelegate::progressAnimationEnabled() const
122{
124 return (d->animator != nullptr);
125}
126
128{
130
131 auto noTextOption = qstyleoption_cast<QStyleOptionViewItem *>(option);
132 QStyledItemDelegate::initStyleOption(noTextOption, index);
133 if (option->decorationPosition != QStyleOptionViewItem::Top) {
134 if (noTextOption) {
135 noTextOption->text.clear();
136 }
137 }
138
139 if (d->animator) {
141 if (!fetchState.isValid() || fetchState.toInt() != Akonadi::EntityTreeModel::FetchingState) {
142 d->animator->pop(index);
143 return;
144 }
145
146 d->animator->push(index);
147
148 if (auto v4 = qstyleoption_cast<QStyleOptionViewItem *>(option)) {
149 v4->icon = d->animator->sequenceFrame(index);
150 }
151 }
152}
153
154class PainterStateSaver
155{
156public:
157 explicit PainterStateSaver(QPainter *painter)
158 {
159 mPainter = painter;
160 mPainter->save();
161 }
162
163 ~PainterStateSaver()
164 {
165 mPainter->restore();
166 }
167
168private:
169 Q_DISABLE_COPY(PainterStateSaver)
170 QPainter *mPainter = nullptr;
171};
172
173void CollectionStatisticsDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
174{
176 PainterStateSaver stateSaver(painter);
177
178 const auto textColor = index.data(Qt::ForegroundRole).value<QColor>();
179 // First, paint the basic, but without the text. We remove the text
180 // in initStyleOption(), which gets called by QStyledItemDelegate::paint().
181 QStyledItemDelegate::paint(painter, option, index);
182
183 // Now, we retrieve the correct style option by calling initStyleOption from
184 // the superclass.
185 QStyleOptionViewItem option4 = option;
187 QString text = option4.text;
188
189 // Now calculate the rectangle for the text
190 QStyle *s = d->parent->style();
191 const QWidget *widget = option4.widget;
192 const QRect textRect = s->subElementRect(QStyle::SE_ItemViewItemText, &option4, widget);
193
194 // When checking if the item is expanded, we need to check that for the first
195 // column, as Qt only recognizes the index as expanded for the first column
196 const QModelIndex firstColumn = index.sibling(index.row(), 0);
197 auto treeView = qobject_cast<QTreeView *>(d->parent);
198 bool expanded = treeView && treeView->isExpanded(firstColumn);
199
201 painter->setPen(option.palette.color(QPalette::Disabled, QPalette::Text));
202 } else if (option.state & QStyle::State_Selected) {
203 painter->setPen(textColor.isValid() ? textColor : option.palette.highlightedText().color());
204 } else {
205 painter->setPen(textColor.isValid() ? textColor : option.palette.text().color());
206 }
207
208 auto collection = firstColumn.data(EntityTreeModel::CollectionRole).value<Collection>();
209
210 if (!collection.isValid()) {
211 qCCritical(AKONADIWIDGETS_LOG) << "Invalid collection at index" << firstColumn << firstColumn.data().toString() << "sibling of" << index
212 << "rowCount=" << index.model()->rowCount(index.parent()) << "parent=" << index.parent().data().toString();
213 return;
214 }
215
216 CollectionStatistics statistics = collection.statistics();
217
218 qint64 unreadCount = qMax(0LL, statistics.unreadCount());
219 qint64 totalRecursiveCount = 0;
220 qint64 unreadRecursiveCount = 0;
221 qint64 totalSize = 0;
222 bool needRecursiveCounts = false;
223 bool needTotalSize = false;
224 if ((d->drawUnreadAfterFolder && index.column() == 0) || (index.column() == 1 || index.column() == 2)) {
225 needRecursiveCounts = true;
226 } else if (index.column() == 3 && !expanded) {
227 needTotalSize = true;
228 }
229
230 if (needRecursiveCounts || needTotalSize) {
231 d->getCountRecursive(firstColumn, totalRecursiveCount, unreadRecursiveCount, totalSize);
232 }
233
234 // Draw the unread count after the folder name (in parenthesis)
235 if (d->drawUnreadAfterFolder && index.column() == 0) {
236 // Construct the string which will appear after the foldername (with the
237 // unread count)
238 QString unread;
239 // qCDebug(AKONADIWIDGETS_LOG) << expanded << unreadCount << unreadRecursiveCount;
240 if (expanded && unreadCount > 0) {
241 unread = QStringLiteral(" (%1)").arg(unreadCount);
242 } else if (!expanded) {
243 if (unreadCount != unreadRecursiveCount) {
244 unread = QStringLiteral(" (%1 + %2)").arg(unreadCount).arg(unreadRecursiveCount - unreadCount);
245 } else if (unreadCount > 0) {
246 unread = QStringLiteral(" (%1)").arg(unreadCount);
247 }
248 }
249
250 PainterStateSaver stateSaver(painter);
251
252 if (!unread.isEmpty()) {
253 QFont font = painter->font();
254 font.setBold(true);
255 painter->setFont(font);
256 }
257
258 const QColor unreadColor = (option.state & QStyle::State_Selected) ? d->mSelectedUnreadColor : d->mDeselectedUnreadColor;
259 const QRect iconRect = s->subElementRect(QStyle::SE_ItemViewItemDecoration, &option4, widget);
260
261 if (option.decorationPosition == QStyleOptionViewItem::Left || option.decorationPosition == QStyleOptionViewItem::Right) {
262 // Squeeze the folder text if it is to big and calculate the rectangles
263 // where the folder text and the unread count will be drawn to
264 QString folderName = text;
265 QFontMetrics fm(painter->fontMetrics());
266 const int unreadWidth = fm.horizontalAdvance(unread);
267 int folderWidth(fm.horizontalAdvance(folderName));
268 const bool enoughPlaceForText = (option.rect.width() > (folderWidth + unreadWidth + iconRect.width()));
269
270 if (!enoughPlaceForText && (folderWidth + unreadWidth > textRect.width())) {
271 folderName = fm.elidedText(folderName, Qt::ElideRight, option.rect.width() - unreadWidth - iconRect.width());
272 folderWidth = fm.horizontalAdvance(folderName);
273 }
274 QRect folderRect = textRect;
275 QRect unreadRect = textRect;
276 folderRect.setRight(textRect.left() + folderWidth);
277 unreadRect = QRect(folderRect.right(), folderRect.top(), unreadWidth, unreadRect.height());
278
279 // Draw folder name and unread count
280 painter->drawText(folderRect, Qt::AlignLeft | Qt::AlignVCenter, folderName);
281 painter->setPen(unreadColor);
282 painter->drawText(unreadRect, Qt::AlignLeft | Qt::AlignVCenter, unread);
283 } else if (option.decorationPosition == QStyleOptionViewItem::Top) {
284 if (unreadCount > 0) {
285 // draw over the icon
286 // the iconRect is enlarged to the whole width of the item, in case the text is wider than the underlying icon
287 painter->setPen(unreadColor);
288 painter->drawText(QRect(option.rect.x(), iconRect.y(), option.rect.width(), iconRect.height()), Qt::AlignCenter, QString::number(unreadCount));
289 }
290 }
291 return;
292 }
293
294 // For the unread/total column, paint the summed up count if the item
295 // is collapsed
296 if ((index.column() == 1 || index.column() == 2)) {
297 QFont savedFont = painter->font();
298 QString sumText;
299 if (index.column() == 1 && ((!expanded && unreadRecursiveCount > 0) || (expanded && unreadCount > 0))) {
300 QFont font = painter->font();
301 font.setBold(true);
302 painter->setFont(font);
303 sumText = QString::number(expanded ? unreadCount : unreadRecursiveCount);
304 } else {
305 qint64 totalCount = statistics.count();
306 if (index.column() == 2 && ((!expanded && totalRecursiveCount > 0) || (expanded && totalCount > 0))) {
307 sumText = QString::number(expanded ? totalCount : totalRecursiveCount);
308 }
309 }
310
311 painter->drawText(textRect, Qt::AlignRight | Qt::AlignVCenter, sumText);
312 painter->setFont(savedFont);
313 return;
314 }
315
316 // total size
317 if (index.column() == 3 && !expanded) {
318 KFormat format;
319 painter->drawText(textRect, option4.displayAlignment | Qt::AlignVCenter, format.formatByteSize(totalSize));
320 return;
321 }
322
323 painter->drawText(textRect, option4.displayAlignment | Qt::AlignVCenter, text);
324}
325
331
332#include "moc_collectionstatisticsdelegate.cpp"
A delegate that draws unread and total count for StatisticsProxyModel.
void initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const override
bool unreadCountShown() const
Returns whether the unread count is drawn next to the folder name.
void setUnreadCountShown(bool enable)
Sets whether the unread count is drawn next to the folder name.
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
CollectionStatisticsDelegate(QAbstractItemView *parent)
Creates a new collection statistics delegate.
~CollectionStatisticsDelegate() override
Destroys the collection statistics delegate.
Provides statistics information of a Collection.
Represents a collection of PIM items.
Definition collection.h:62
@ FetchingState
There is a fetch of items in this collection in progress.
@ CollectionRole
The collection.
@ PendingCutRole
Used to indicate items which are to be cut.
@ FetchStateRole
Returns the FetchState of a particular item.
QBrush foreground(ForegroundRole=NormalText) const
QString formatByteSize(double size, int precision=1, KFormat::BinaryUnitDialect dialect=KFormat::DefaultBinaryDialect, KFormat::BinarySizeUnits units=KFormat::DefaultBinaryUnits) const
Helper integration between Akonadi and Qt.
QAction * statistics(const QObject *recvr, const char *slot, QObject *parent)
virtual bool hasChildren(const QModelIndex &parent) const const
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const=0
virtual int rowCount(const QModelIndex &parent) const const=0
const QColor & color() const const
void setBold(bool enable)
QString elidedText(const QString &text, Qt::TextElideMode mode, int width, int flags) const const
int horizontalAdvance(QChar ch) const const
int column() const const
QVariant data(int role) const const
const QAbstractItemModel * model() const const
QModelIndex parent() const const
int row() const const
QModelIndex sibling(int row, int column) const const
QObject * parent() const const
T qobject_cast(QObject *object)
void drawText(const QPoint &position, const QString &text)
const QFont & font() const const
QFontMetrics fontMetrics() const const
void save()
void setFont(const QFont &font)
void setPen(Qt::PenStyle style)
int height() const const
int left() const const
int right() const const
void setRight(int x)
int top() const const
int width() const const
int y() const const
QString arg(Args &&... args) const const
bool isEmpty() const const
QString number(double n, char format, int precision)
SE_ItemViewItemText
virtual QRect subElementRect(SubElement element, const QStyleOption *option, const QWidget *widget) const const=0
virtual void initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const const
virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const const override
AlignLeft
ForegroundRole
ElideRight
bool isValid() const const
bool toBool() const const
int toInt(bool *ok) const const
QString toString() const const
T value() const const
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:08:30 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.