Akonadi

collectionstatisticsdelegate.cpp
1 /*
2  Copyright (c) 2008 Thomas McGuire <[email protected]>
3  Copyright (C) 2012-2020 Laurent Montel <[email protected]>
4 
5  This library is free software; you can redistribute it and/or modify it
6  under the terms of the GNU Library General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or (at your
8  option) any later version.
9 
10  This library is distributed in the hope that it will be useful, but WITHOUT
11  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
13  License for more details.
14 
15  You should have received a copy of the GNU Library General Public License
16  along with this library; see the file COPYING.LIB. If not, write to the
17  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18  02110-1301, USA.
19 */
20 
21 #include "collectionstatisticsdelegate.h"
22 
23 #include <KColorScheme>
24 #include <KFormat>
25 #include "akonadiwidgets_debug.h"
26 
27 #include <QPainter>
28 #include <QStyle>
29 #include <QStyleOption>
30 #include <QStyleOptionViewItem>
31 #include <QAbstractItemView>
32 #include <QTreeView>
33 
34 #include "entitytreemodel.h"
35 #include "collectionstatistics.h"
36 #include "collection.h"
37 #include "progressspinnerdelegate_p.h"
38 
39 using namespace Akonadi;
40 
41 namespace Akonadi
42 {
43 
44 enum CountType {
45  UnreadCount,
46  TotalCount
47 };
48 
49 class CollectionStatisticsDelegatePrivate
50 {
51 public:
52  QAbstractItemView *parent = nullptr;
53  bool drawUnreadAfterFolder = false;
54  DelegateAnimator *animator = nullptr;
55  QColor mSelectedUnreadColor;
56  QColor mDeselectedUnreadColor;
57 
58  CollectionStatisticsDelegatePrivate(QAbstractItemView *treeView)
59  : parent(treeView)
60  {
61  updateColor();
62  }
63 
64  void getCountRecursive(const QModelIndex &index, qint64 &totalCount, qint64 &unreadCount, qint64 &totalSize) const
65  {
66  Collection collection = qvariant_cast<Collection>(index.data(EntityTreeModel::CollectionRole));
67  // Do not assert on invalid collections, since a collection may be deleted
68  // in the meantime and deleted collections are invalid.
69  if (collection.isValid()) {
71  totalCount += qMax(0LL, statistics.count());
72  unreadCount += qMax(0LL, statistics.unreadCount());
73  totalSize += qMax(0LL, statistics.size());
74  if (index.model()->hasChildren(index)) {
75  const int rowCount = index.model()->rowCount(index);
76  for (int row = 0; row < rowCount; row++) {
77  static const int column = 0;
78  getCountRecursive(index.model()->index(row, column, index), totalCount, unreadCount, totalSize);
79  }
80  }
81  }
82  }
83 
84  void updateColor()
85  {
86  mSelectedUnreadColor = KColorScheme(QPalette::Active, KColorScheme::Selection)
88  mDeselectedUnreadColor = KColorScheme(QPalette::Active, KColorScheme::View)
90  }
91 };
92 
93 }
94 
96  : QStyledItemDelegate(parent)
97  , d_ptr(new CollectionStatisticsDelegatePrivate(parent))
98 {
99 
100 }
101 
103  : QStyledItemDelegate(parent)
104  , d_ptr(new CollectionStatisticsDelegatePrivate(parent))
105 {
106 
107 }
108 
110 {
111  delete d_ptr;
112 }
113 
115 {
117  d->drawUnreadAfterFolder = enable;
118 }
119 
121 {
122  Q_D(const CollectionStatisticsDelegate);
123  return d->drawUnreadAfterFolder;
124 }
125 
127 {
129  if (enable == (d->animator != nullptr)) {
130  return;
131  }
132  if (enable) {
133  Q_ASSERT(!d->animator);
134  Akonadi::DelegateAnimator *animator = new Akonadi::DelegateAnimator(d->parent);
135  d->animator = animator;
136  } else {
137  delete d->animator;
138  d->animator = nullptr;
139  }
140 }
141 
142 bool CollectionStatisticsDelegate::progressAnimationEnabled() const
143 {
144  Q_D(const CollectionStatisticsDelegate);
145  return (d->animator != nullptr);
146 }
147 
149  const QModelIndex &index) const
150 {
151  Q_D(const CollectionStatisticsDelegate);
152 
153  QStyleOptionViewItem *noTextOption =
154  qstyleoption_cast<QStyleOptionViewItem *>(option);
155  QStyledItemDelegate::initStyleOption(noTextOption, index);
156  if (option->decorationPosition != QStyleOptionViewItem::Top) {
157  if (noTextOption) {
158  noTextOption->text.clear();
159  }
160  }
161 
162  if (d->animator) {
163 
164  const QVariant fetchState = index.data(Akonadi::EntityTreeModel::FetchStateRole);
165  if (!fetchState.isValid() || fetchState.toInt() != Akonadi::EntityTreeModel::FetchingState) {
166  d->animator->pop(index);
167  return;
168  }
169 
170  d->animator->push(index);
171 
172  if (QStyleOptionViewItem *v4 = qstyleoption_cast<QStyleOptionViewItem *>(option)) {
173  v4->icon = d->animator->sequenceFrame(index);
174  }
175  }
176 }
177 
178 class PainterStateSaver
179 {
180 public:
181  PainterStateSaver(QPainter *painter)
182  {
183  mPainter = painter;
184  mPainter->save();
185  }
186 
187  ~PainterStateSaver()
188  {
189  mPainter->restore();
190  }
191 
192 private:
193  Q_DISABLE_COPY(PainterStateSaver)
194  QPainter *mPainter = nullptr;
195 };
196 
198  const QStyleOptionViewItem &option,
199  const QModelIndex &index) const
200 {
201  Q_D(const CollectionStatisticsDelegate);
202  PainterStateSaver stateSaver(painter);
203 
204  const QColor textColor = index.data(Qt::ForegroundRole).value<QColor>();
205  // First, paint the basic, but without the text. We remove the text
206  // in initStyleOption(), which gets called by QStyledItemDelegate::paint().
207  QStyledItemDelegate::paint(painter, option, index);
208 
209  // Now, we retrieve the correct style option by calling initStyleOption from
210  // the superclass.
211  QStyleOptionViewItem option4 = option;
212  QStyledItemDelegate::initStyleOption(&option4, index);
213  QString text = option4.text;
214 
215  // Now calculate the rectangle for the text
216  QStyle *s = d->parent->style();
217  const QWidget *widget = option4.widget;
218  const QRect textRect = s->subElementRect(QStyle::SE_ItemViewItemText, &option4, widget);
219 
220  // When checking if the item is expanded, we need to check that for the first
221  // column, as Qt only recognizes the index as expanded for the first column
222  const QModelIndex firstColumn = index.sibling(index.row(), 0);
223  QTreeView *treeView = qobject_cast<QTreeView *>(d->parent);
224  bool expanded = treeView && treeView->isExpanded(firstColumn);
225 
227  painter->setPen(option.palette.color(QPalette::Disabled, QPalette::Text));
228  } else if (option.state & QStyle::State_Selected) {
229  painter->setPen(textColor.isValid() ? textColor : option.palette.highlightedText().color());
230  } else {
231  painter->setPen(textColor.isValid() ? textColor : option.palette.text().color());
232  }
233 
234  Collection collection = firstColumn.data(EntityTreeModel::CollectionRole).value<Collection>();
235 
236  if (!collection.isValid()) {
237  qCCritical(AKONADIWIDGETS_LOG) << "Invalid collection at index" << firstColumn << firstColumn.data().toString() << "sibling of" << index << "rowCount=" << index.model()->rowCount(index.parent()) << "parent=" << index.parent().data().toString();
238  return;
239  }
240 
241  CollectionStatistics statistics = collection.statistics();
242 
243  qint64 unreadCount = qMax(0LL, statistics.unreadCount());
244  qint64 totalRecursiveCount = 0;
245  qint64 unreadRecursiveCount = 0;
246  qint64 totalSize = 0;
247  bool needRecursiveCounts = false;
248  bool needTotalSize = false;
249  if (d->drawUnreadAfterFolder && index.column() == 0) {
250  needRecursiveCounts = true;
251  } else if ((index.column() == 1 || index.column() == 2)) {
252  needRecursiveCounts = true;
253  } else if (index.column() == 3 && !expanded) {
254  needTotalSize = true;
255  }
256 
257  if (needRecursiveCounts || needTotalSize) {
258  d->getCountRecursive(firstColumn, totalRecursiveCount, unreadRecursiveCount, totalSize);
259  }
260 
261  // Draw the unread count after the folder name (in parenthesis)
262  if (d->drawUnreadAfterFolder && index.column() == 0) {
263  // Construct the string which will appear after the foldername (with the
264  // unread count)
265  QString unread;
266 // qCDebug(AKONADIWIDGETS_LOG) << expanded << unreadCount << unreadRecursiveCount;
267  if (expanded && unreadCount > 0) {
268  unread = QStringLiteral(" (%1)").arg(unreadCount);
269  } else if (!expanded) {
270  if (unreadCount != unreadRecursiveCount) {
271  unread = QStringLiteral(" (%1 + %2)").arg(unreadCount).arg(unreadRecursiveCount - unreadCount);
272  } else if (unreadCount > 0) {
273  unread = QStringLiteral(" (%1)").arg(unreadCount);
274  }
275  }
276 
277  PainterStateSaver stateSaver(painter);
278 
279  if (!unread.isEmpty()) {
280  QFont font = painter->font();
281  font.setBold(true);
282  painter->setFont(font);
283  }
284 
285  const QColor unreadColor = (option.state & QStyle::State_Selected) ? d->mSelectedUnreadColor : d->mDeselectedUnreadColor;
286  const QRect iconRect = s->subElementRect(QStyle::SE_ItemViewItemDecoration, &option4, widget);
287 
288  if (option.decorationPosition == QStyleOptionViewItem::Left ||
289  option.decorationPosition == QStyleOptionViewItem::Right) {
290  // Squeeze the folder text if it is to big and calculate the rectangles
291  // where the folder text and the unread count will be drawn to
292  QString folderName = text;
293  QFontMetrics fm(painter->fontMetrics());
294  const int unreadWidth = fm.horizontalAdvance(unread);
295  int folderWidth(fm.horizontalAdvance(folderName));
296  const bool enoughPlaceForText = (option.rect.width() > (folderWidth + unreadWidth + iconRect.width()));
297 
298  if (!enoughPlaceForText && (folderWidth + unreadWidth > textRect.width())) {
299  folderName = fm.elidedText(folderName, Qt::ElideRight,
300  option.rect.width() - unreadWidth - iconRect.width());
301  folderWidth = fm.horizontalAdvance(folderName);
302  }
303  QRect folderRect = textRect;
304  QRect unreadRect = textRect;
305  folderRect.setRight(textRect.left() + folderWidth);
306  unreadRect = QRect(folderRect.right(), folderRect.top(), unreadWidth, unreadRect.height());
307 
308  // Draw folder name and unread count
309  painter->drawText(folderRect, Qt::AlignLeft | Qt::AlignVCenter, folderName);
310  painter->setPen(unreadColor);
311  painter->drawText(unreadRect, Qt::AlignLeft | Qt::AlignVCenter, unread);
312  } else if (option.decorationPosition == QStyleOptionViewItem::Top) {
313  if (unreadCount > 0) {
314  // draw over the icon
315  // the iconRect is enlarged to the whole width of the item, in case the text is wider than the underlying icon
316  painter->setPen(unreadColor);
317  painter->drawText(QRect(option.rect.x(), iconRect.y(), option.rect.width(), iconRect.height()), Qt::AlignCenter, QString::number(unreadCount));
318  }
319  }
320  return;
321  }
322 
323  // For the unread/total column, paint the summed up count if the item
324  // is collapsed
325  if ((index.column() == 1 || index.column() == 2)) {
326 
327  QFont savedFont = painter->font();
328  QString sumText;
329  if (index.column() == 1 && ((!expanded && unreadRecursiveCount > 0) || (expanded && unreadCount > 0))) {
330  QFont font = painter->font();
331  font.setBold(true);
332  painter->setFont(font);
333  sumText = QString::number(expanded ? unreadCount : unreadRecursiveCount);
334  } else {
335 
336  qint64 totalCount = statistics.count();
337  if (index.column() == 2 && ((!expanded && totalRecursiveCount > 0) || (expanded && totalCount > 0))) {
338  sumText = QString::number(expanded ? totalCount : totalRecursiveCount);
339  }
340  }
341 
342  painter->drawText(textRect, Qt::AlignRight | Qt::AlignVCenter, sumText);
343  painter->setFont(savedFont);
344  return;
345  }
346 
347  //total size
348  if (index.column() == 3 && !expanded) {
349  painter->drawText(textRect, option4.displayAlignment | Qt::AlignVCenter,
350  KFormat().formatByteSize(totalSize));
351  return;
352  }
353 
354  painter->drawText(textRect, option4.displayAlignment | Qt::AlignVCenter, text);
355 }
356 
358 {
360  d->updateColor();
361 }
qint64 count() const
Returns the number of items in this collection or -1 if this information is not available.
virtual int rowCount(const QModelIndex &parent) const const =0
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const =0
bool isValid() const
Returns whether the collection is valid.
Definition: collection.cpp:137
int right() const const
Provides statistics information of a Collection.
Returns the FetchState of a particular item.
virtual void initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const const
const QFont & font() const const
Represents a collection of PIM items.
Definition: collection.h:76
Left
void save()
T value() const const
int height() const const
const QColor & color() const const
void setBold(bool enable)
void setFont(const QFont &font)
QString number(int n, int base)
QBrush foreground(ForegroundRole=NormalText) const
int toInt(bool *ok) const const
int top() const const
KDEGAMES_EXPORT QAction * statistics(const QObject *recvr, const char *slot, QObject *parent)
void setPen(const QColor &color)
virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const const override
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
int left() const const
bool isEmpty() const const
int row() const const
void drawText(const QPointF &position, const QString &text)
QModelIndex parent() const const
bool isExpanded(const QModelIndex &index) const const
bool unreadCountShown() const
Returns whether the unread count is drawn next to the folder name.
CollectionStatisticsDelegate(QAbstractItemView *parent)
Creates a new collection statistics delegate.
Right
int horizontalAdvance(const QString &text, int len) const const
void setRight(int x)
Q_DISABLE_COPY(Class)
Top
int width() const const
const QAbstractItemModel * model() const const
void setUnreadCountShown(bool enable)
Sets whether the unread count is drawn next to the folder name.
~CollectionStatisticsDelegate() override
Destroys the collection statistics delegate.
QVariant data(int role) const const
virtual bool hasChildren(const QModelIndex &parent) const const
QModelIndex sibling(int row, int column) const const
QFontMetrics fontMetrics() const const
Helper integration between Akonadi and Qt.
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
Used to indicate items which are to be cut.
There is a fetch of items in this collection in progress.
int column() const const
bool toBool() const const
qint64 unreadCount() const
Returns the number of unread items in this collection or -1 if this information is not available...
bool isValid() const const
virtual QRect subElementRect(QStyle::SubElement element, const QStyleOption *option, const QWidget *widget) const const =0
T qobject_cast(QObject *object)
QObject * parent() const const
A delegate that draws unread and total count for StatisticsProxyModel.
QString toString() const const
CollectionStatistics statistics() const
Returns the collection statistics of the collection.
Definition: collection.cpp:343
bool isValid() const const
qint64 size() const
Returns the total size of the items in this collection or -1 if this information is not available...
void initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const override
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Fri Jun 5 2020 23:08:54 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.