• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdepimlibs-4.10.5 API Reference
  • KDE Home
  • Contact Us
 

akonadi

  • akonadi
statisticsproxymodel.cpp
1 /*
2  Copyright (c) 2009 Kevin Ottens <ervin@kde.org>
3 
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 "statisticsproxymodel.h"
22 
23 #include "entitytreemodel.h"
24 #include "collectionutils_p.h"
25 
26 #include <akonadi/collectionquotaattribute.h>
27 #include <akonadi/collectionstatistics.h>
28 #include <akonadi/entitydisplayattribute.h>
29 
30 #include <kdebug.h>
31 #include <kiconloader.h>
32 #include <klocale.h>
33 #include <kio/global.h>
34 
35 #include <QApplication>
36 #include <QPalette>
37 #include <KIcon>
38 using namespace Akonadi;
39 
43 class StatisticsProxyModel::Private
44 {
45  public:
46  Private( StatisticsProxyModel *parent )
47  : mParent( parent ), mToolTipEnabled( false ), mExtraColumnsEnabled( true )
48  {
49  }
50 
51  int sourceColumnCount( const QModelIndex &parent )
52  {
53  return mParent->sourceModel()->columnCount( mParent->mapToSource( parent ) );
54  }
55 
56  void getCountRecursive( const QModelIndex &index, qint64 &totalSize ) const
57  {
58  Collection collection = qvariant_cast<Collection>( index.data( EntityTreeModel::CollectionRole ) );
59  // Do not assert on invalid collections, since a collection may be deleted
60  // in the meantime and deleted collections are invalid.
61  if ( collection.isValid() ) {
62  CollectionStatistics statistics = collection.statistics();
63  totalSize += qMax( 0LL, statistics.size() );
64  if ( index.model()->hasChildren( index ) ) {
65  const int rowCount = index.model()->rowCount( index );
66  for ( int row = 0; row < rowCount; row++ ) {
67  static const int column = 0;
68  getCountRecursive( index.model()->index( row, column, index ), totalSize );
69  }
70  }
71  }
72  }
73 
74 
75  QString toolTipForCollection( const QModelIndex &index, const Collection &collection )
76  {
77  QString bckColor = QApplication::palette().color( QPalette::ToolTipBase ).name();
78  QString txtColor = QApplication::palette().color( QPalette::ToolTipText ).name();
79 
80  QString tip = QString::fromLatin1(
81  "<table width=\"100%\" border=\"0\" cellpadding=\"2\" cellspacing=\"0\">\n"
82  );
83  const QString textDirection = ( QApplication::layoutDirection() == Qt::LeftToRight ) ? QLatin1String( "left" ) : QLatin1String( "right" );
84  tip += QString::fromLatin1(
85  " <tr>\n"
86  " <td bgcolor=\"%1\" colspan=\"2\" align=\"%4\" valign=\"middle\">\n"
87  " <div style=\"color: %2; font-weight: bold;\">\n"
88  " %3\n"
89  " </div>\n"
90  " </td>\n"
91  " </tr>\n"
92  ).arg( txtColor ).arg( bckColor ).arg( index.data( Qt::DisplayRole ).toString() ).arg( textDirection );
93 
94 
95  tip += QString::fromLatin1(
96  " <tr>\n"
97  " <td align=\"%1\" valign=\"top\">\n"
98  ).arg( textDirection );
99 
100  QString tipInfo;
101  tipInfo += QString::fromLatin1(
102  " <strong>%1</strong>: %2<br>\n"
103  " <strong>%3</strong>: %4<br><br>\n"
104  ).arg( i18n( "Total Messages" ) ).arg( collection.statistics().count() )
105  .arg( i18n( "Unread Messages" ) ).arg( collection.statistics().unreadCount() );
106 
107  if ( collection.hasAttribute<CollectionQuotaAttribute>() ) {
108  CollectionQuotaAttribute *quota = collection.attribute<CollectionQuotaAttribute>();
109  if ( quota->currentValue() > -1 && quota->maximumValue() > 0 ) {
110  qreal percentage = ( 100.0 * quota->currentValue() ) / quota->maximumValue();
111 
112  if ( qAbs( percentage ) >= 0.01 ) {
113  QString percentStr = QString::number( percentage, 'f', 2 );
114  tipInfo += QString::fromLatin1(
115  " <strong>%1</strong>: %2%<br>\n"
116  ).arg( i18n( "Quota" ) ).arg( percentStr );
117  }
118  }
119  }
120 
121  qint64 currentFolderSize( collection.statistics().size() );
122  tipInfo += QString::fromLatin1(
123  " <strong>%1</strong>: %2<br>\n"
124  ).arg( i18n( "Storage Size" ) ).arg( KIO::convertSize( (KIO::filesize_t)( currentFolderSize ) ) );
125 
126 
127  qint64 totalSize = 0;
128  getCountRecursive( index, totalSize );
129  totalSize -= currentFolderSize;
130  if (totalSize > 0 ) {
131  tipInfo += QString::fromLatin1(
132  "<strong>%1</strong>: %2<br>"
133  ).arg( i18n("Subfolder Storage Size") ).arg( KIO::convertSize( (KIO::filesize_t)( totalSize ) ) );
134  }
135 
136 
137  QString iconName = CollectionUtils::defaultIconName( collection );
138  if ( collection.hasAttribute<EntityDisplayAttribute>() &&
139  !collection.attribute<EntityDisplayAttribute>()->iconName().isEmpty() ) {
140  if ( !collection.attribute<EntityDisplayAttribute>()->activeIconName().isEmpty() && collection.statistics().unreadCount()> 0) {
141  iconName = collection.attribute<EntityDisplayAttribute>()->activeIconName();
142  }
143  else
144  iconName = collection.attribute<EntityDisplayAttribute>()->iconName();
145  }
146 
147 
148  int iconSizes[] = { 32, 22 };
149  int icon_size_found = 32;
150 
151  QString iconPath;
152 
153  for ( int i = 0; i < 2; i++ ) {
154  iconPath = KIconLoader::global()->iconPath( iconName, -iconSizes[ i ], true );
155  if ( !iconPath.isEmpty() ) {
156  icon_size_found = iconSizes[ i ];
157  break;
158  }
159  }
160 
161  if ( iconPath.isEmpty() ) {
162  iconPath = KIconLoader::global()->iconPath( QLatin1String( "folder" ), -32, false );
163  }
164 
165  QString tipIcon = QString::fromLatin1(
166  " <table border=\"0\"><tr><td width=\"32\" height=\"32\" align=\"center\" valign=\"middle\">\n"
167  " <img src=\"%1\" width=\"%2\" height=\"32\">\n"
168  " </td></tr></table>\n"
169  " </td>\n"
170  ).arg( iconPath ).arg( icon_size_found ) ;
171 
172  if ( QApplication::layoutDirection() == Qt::LeftToRight )
173  {
174  tip += tipInfo + QString::fromLatin1( "</td><td align=\"%3\" valign=\"top\">" ).arg( textDirection ) + tipIcon;
175  }
176  else
177  {
178  tip += tipIcon + QString::fromLatin1( "</td><td align=\"%3\" valign=\"top\">" ).arg( textDirection ) + tipInfo;
179  }
180 
181 
182  tip += QString::fromLatin1(
183  " </tr>" \
184  "</table>"
185  );
186 
187  return tip;
188  }
189 
190  void proxyDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight);
191 
192  void sourceLayoutAboutToBeChanged();
193  void sourceLayoutChanged();
194 
195  QVector<QModelIndex> m_nonPersistent;
196  QVector<QModelIndex> m_nonPersistentFirstColumn;
197  QVector<QPersistentModelIndex> m_persistent;
198  QVector<QPersistentModelIndex> m_persistentFirstColumn;
199 
200  StatisticsProxyModel *mParent;
201 
202  bool mToolTipEnabled;
203  bool mExtraColumnsEnabled;
204 };
205 
206 void StatisticsProxyModel::Private::proxyDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight)
207 {
208  if ( mExtraColumnsEnabled )
209  {
210  // Ugly hack.
211  // The proper solution is a KExtraColumnsProxyModel, but this will do for now.
212  QModelIndex parent = topLeft.parent();
213  int parentColumnCount = mParent->columnCount( parent );
214  QModelIndex extraTopLeft = mParent->index( topLeft.row(), parentColumnCount - 1 - 3 , parent );
215  QModelIndex extraBottomRight = mParent->index( bottomRight.row(), parentColumnCount -1, parent );
216  mParent->disconnect( mParent, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
217  mParent, SLOT(proxyDataChanged(QModelIndex,QModelIndex)) );
218  emit mParent->dataChanged( extraTopLeft, extraBottomRight );
219 
220  // We get this signal when the statistics of a row changes.
221  // However, we need to emit data changed for the statistics of all ancestor rows too
222  // so that recursive totals can be updated.
223  while ( parent.isValid() )
224  {
225  emit mParent->dataChanged( parent.sibling( parent.row(), parentColumnCount - 1 - 3 ),
226  parent.sibling( parent.row(), parentColumnCount - 1 ) );
227  parent = parent.parent();
228  parentColumnCount = mParent->columnCount( parent );
229  }
230  mParent->connect( mParent, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
231  SLOT(proxyDataChanged(QModelIndex,QModelIndex)) );
232  }
233 }
234 
235 void StatisticsProxyModel::Private::sourceLayoutAboutToBeChanged()
236 {
237  QModelIndexList persistent = mParent->persistentIndexList();
238  const int columnCount = mParent->sourceModel()->columnCount();
239  foreach( const QModelIndex &idx, persistent ) {
240  if ( idx.column() >= columnCount ) {
241  m_nonPersistent.push_back( idx );
242  m_persistent.push_back( idx );
243  const QModelIndex firstColumn = idx.sibling( 0, idx.column() );
244  m_nonPersistentFirstColumn.push_back( firstColumn );
245  m_persistentFirstColumn.push_back( firstColumn );
246  }
247  }
248 }
249 
250 void StatisticsProxyModel::Private::sourceLayoutChanged()
251 {
252  QModelIndexList oldList;
253  QModelIndexList newList;
254 
255  const int columnCount = mParent->sourceModel()->columnCount();
256 
257  for( int i = 0; i < m_persistent.size(); ++i ) {
258  const QModelIndex persistentIdx = m_persistent.at( i );
259  const QModelIndex nonPersistentIdx = m_nonPersistent.at( i );
260  if ( m_persistentFirstColumn.at( i ) != m_nonPersistentFirstColumn.at( i ) && persistentIdx.column() >= columnCount ) {
261  oldList.append( nonPersistentIdx );
262  newList.append( persistentIdx );
263  }
264  }
265  mParent->changePersistentIndexList( oldList, newList );
266 }
267 
268 void StatisticsProxyModel::setSourceModel(QAbstractItemModel* sourceModel)
269 {
270  // Order is important here. sourceLayoutChanged must be called *before* any downstreams react
271  // to the layoutChanged so that it can have the QPersistentModelIndexes uptodate in time.
272  disconnect(this, SIGNAL(layoutChanged()), this, SLOT(sourceLayoutChanged()));
273  connect(this, SIGNAL(layoutChanged()), SLOT(sourceLayoutChanged()));
274  QSortFilterProxyModel::setSourceModel(sourceModel);
275  // This one should come *after* any downstream handlers of layoutAboutToBeChanged.
276  // The connectNotify stuff below ensures that it remains the last one.
277  disconnect(this, SIGNAL(layoutAboutToBeChanged()), this, SLOT(sourceLayoutAboutToBeChanged()));
278  connect(this, SIGNAL(layoutAboutToBeChanged()), SLOT(sourceLayoutAboutToBeChanged()));
279 }
280 
281 void StatisticsProxyModel::connectNotify(const char *signal)
282 {
283  static bool ignore = false;
284  if (ignore || QLatin1String(signal) == SIGNAL(layoutAboutToBeChanged()))
285  return QSortFilterProxyModel::connectNotify(signal);
286  ignore = true;
287  disconnect(this, SIGNAL(layoutAboutToBeChanged()), this, SLOT(sourceLayoutAboutToBeChanged()));
288  connect(this, SIGNAL(layoutAboutToBeChanged()), SLOT(sourceLayoutAboutToBeChanged()));
289  ignore = false;
290  QSortFilterProxyModel::connectNotify(signal);
291 }
292 
293 
294 StatisticsProxyModel::StatisticsProxyModel( QObject *parent )
295  : QSortFilterProxyModel( parent ),
296  d( new Private( this ) )
297 {
298  connect( this, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
299  SLOT(proxyDataChanged(QModelIndex,QModelIndex)) );
300 }
301 
302 StatisticsProxyModel::~StatisticsProxyModel()
303 {
304  delete d;
305 }
306 
307 void StatisticsProxyModel::setToolTipEnabled( bool enable )
308 {
309  d->mToolTipEnabled = enable;
310 }
311 
312 bool StatisticsProxyModel::isToolTipEnabled() const
313 {
314  return d->mToolTipEnabled;
315 }
316 
317 void StatisticsProxyModel::setExtraColumnsEnabled( bool enable )
318 {
319  d->mExtraColumnsEnabled = enable;
320 }
321 
322 bool StatisticsProxyModel::isExtraColumnsEnabled() const
323 {
324  return d->mExtraColumnsEnabled;
325 }
326 
327 QModelIndex Akonadi::StatisticsProxyModel::index( int row, int column, const QModelIndex & parent ) const
328 {
329  if (!hasIndex(row, column, parent))
330  return QModelIndex();
331 
332 
333  int sourceColumn = column;
334 
335  if ( column>=d->sourceColumnCount( parent ) ) {
336  sourceColumn = 0;
337  }
338 
339  QModelIndex i = QSortFilterProxyModel::index( row, sourceColumn, parent );
340  return createIndex( i.row(), column, i.internalPointer() );
341 }
342 
343 QVariant StatisticsProxyModel::data( const QModelIndex & index, int role) const
344 {
345  if (!sourceModel())
346  return QVariant();
347  if ( role == Qt::DisplayRole && index.column()>=d->sourceColumnCount( index.parent() ) ) {
348  const QModelIndex sourceIndex = mapToSource( index.sibling( index.row(), 0 ) );
349  Collection collection = sourceModel()->data( sourceIndex, EntityTreeModel::CollectionRole ).value<Collection>();
350 
351  if ( collection.isValid() && collection.statistics().count()>=0 ) {
352  if ( index.column() == d->sourceColumnCount( QModelIndex() )+2 ) {
353  return KIO::convertSize( (KIO::filesize_t)( collection.statistics().size() ) );
354  } else if ( index.column() == d->sourceColumnCount( QModelIndex() )+1 ) {
355  return collection.statistics().count();
356  } else if ( index.column() == d->sourceColumnCount( QModelIndex() ) ) {
357  if ( collection.statistics().unreadCount() > 0 ) {
358  return collection.statistics().unreadCount();
359  } else {
360  return QString();
361  }
362  } else {
363  kWarning() << "We shouldn't get there for a column which is not total, unread or size.";
364  return QVariant();
365  }
366  }
367 
368  } else if ( role == Qt::TextAlignmentRole && index.column()>=d->sourceColumnCount( index.parent() ) ) {
369  return Qt::AlignRight;
370 
371  } else if ( role == Qt::ToolTipRole && d->mToolTipEnabled ) {
372  const QModelIndex sourceIndex = mapToSource( index.sibling( index.row(), 0 ) );
373  Collection collection
374  = sourceModel()->data( sourceIndex,
375  EntityTreeModel::CollectionRole ).value<Collection>();
376 
377  if ( collection.isValid() && collection.statistics().count()>0 ) {
378  return d->toolTipForCollection( index, collection );
379  }
380 
381  } else if ( role == Qt::DecorationRole && index.column() == 0 ) {
382  const QModelIndex sourceIndex = mapToSource( index.sibling( index.row(), 0 ) );
383  Collection collection = sourceModel()->data( sourceIndex, EntityTreeModel::CollectionRole ).value<Collection>();
384 
385  if ( collection.isValid() )
386  return KIcon( CollectionUtils::displayIconName( collection ) );
387  else
388  return QVariant();
389  }
390 
391  return QAbstractProxyModel::data( index, role );
392 }
393 
394 QVariant StatisticsProxyModel::headerData( int section, Qt::Orientation orientation, int role) const
395 {
396  if ( orientation == Qt::Horizontal && role == Qt::DisplayRole ) {
397  if ( section == d->sourceColumnCount( QModelIndex() ) + 2 ) {
398  return i18nc( "collection size", "Size" );
399  } else if ( section == d->sourceColumnCount( QModelIndex() ) + 1 ) {
400  return i18nc( "number of entities in the collection", "Total" );
401  } else if ( section == d->sourceColumnCount( QModelIndex() ) ) {
402  return i18nc( "number of unread entities in the collection", "Unread" );
403  }
404  }
405 
406  return QSortFilterProxyModel::headerData( section, orientation, role );
407 }
408 
409 Qt::ItemFlags StatisticsProxyModel::flags( const QModelIndex & index ) const
410 {
411  if ( index.column()>=d->sourceColumnCount( index.parent() ) ) {
412  return QSortFilterProxyModel::flags( index.sibling( index.row(), 0 ) )
413  & ( Qt::ItemIsSelectable | Qt::ItemIsDragEnabled // Allowed flags
414  | Qt::ItemIsDropEnabled | Qt::ItemIsEnabled );
415  }
416 
417  return QSortFilterProxyModel::flags( index );
418 }
419 
420 int StatisticsProxyModel::columnCount( const QModelIndex & parent ) const
421 {
422  if ( sourceModel()==0 ) {
423  return 0;
424  } else {
425  return d->sourceColumnCount( parent )
426  + ( d->mExtraColumnsEnabled ? 3 : 0 );
427  }
428 }
429 
430 QModelIndexList StatisticsProxyModel::match( const QModelIndex& start, int role, const QVariant& value,
431  int hits, Qt::MatchFlags flags ) const
432 {
433  if ( role < Qt::UserRole )
434  return QSortFilterProxyModel::match( start, role, value, hits, flags );
435 
436  QModelIndexList list;
437  QModelIndex proxyIndex;
438  foreach ( const QModelIndex &idx, sourceModel()->match( mapToSource( start ), role, value, hits, flags ) ) {
439  proxyIndex = mapFromSource( idx );
440  if ( proxyIndex.isValid() )
441  list << proxyIndex;
442  }
443 
444  return list;
445 }
446 
447 
448 #include "moc_statisticsproxymodel.cpp"
449 
This file is part of the KDE documentation.
Documentation copyright © 1996-2013 The KDE developers.
Generated on Sat Jul 13 2013 01:27:43 by doxygen 1.8.3.1 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

akonadi

Skip menu "akonadi"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • Modules
  • Related Pages

kdepimlibs-4.10.5 API Reference

Skip menu "kdepimlibs-4.10.5 API Reference"
  • akonadi
  •   contact
  •   kmime
  •   socialutils
  • kabc
  • kalarmcal
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmbox
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • microblog
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal