akonadi
statisticsproxymodel.cpp
00001 /* 00002 Copyright (c) 2009 Kevin Ottens <ervin@kde.org> 00003 00004 00005 This library is free software; you can redistribute it and/or modify it 00006 under the terms of the GNU Library General Public License as published by 00007 the Free Software Foundation; either version 2 of the License, or (at your 00008 option) any later version. 00009 00010 This library is distributed in the hope that it will be useful, but WITHOUT 00011 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 00012 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public 00013 License for more details. 00014 00015 You should have received a copy of the GNU Library General Public License 00016 along with this library; see the file COPYING.LIB. If not, write to the 00017 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 00018 02110-1301, USA. 00019 */ 00020 00021 #include "statisticsproxymodel.h" 00022 00023 #include "entitytreemodel.h" 00024 #include "collectionutils_p.h" 00025 00026 #include <akonadi/collectionquotaattribute.h> 00027 #include <akonadi/collectionstatistics.h> 00028 #include <akonadi/entitydisplayattribute.h> 00029 00030 #include <kdebug.h> 00031 #include <kiconloader.h> 00032 #include <klocale.h> 00033 #include <kio/global.h> 00034 00035 #include <QtGui/QApplication> 00036 #include <QtGui/QPalette> 00037 #include <KIcon> 00038 using namespace Akonadi; 00039 00043 class StatisticsProxyModel::Private 00044 { 00045 public: 00046 Private( StatisticsProxyModel *parent ) 00047 : mParent( parent ), mToolTipEnabled( false ), mExtraColumnsEnabled( true ) 00048 { 00049 } 00050 00051 int sourceColumnCount( const QModelIndex &parent ) 00052 { 00053 return mParent->sourceModel()->columnCount( mParent->mapToSource( parent ) ); 00054 } 00055 00056 QString toolTipForCollection( const QModelIndex &index, const Collection &collection ) 00057 { 00058 QString bckColor = QApplication::palette().color( QPalette::ToolTipBase ).name(); 00059 QString txtColor = QApplication::palette().color( QPalette::ToolTipText ).name(); 00060 00061 QString tip = QString::fromLatin1( 00062 "<table width=\"100%\" border=\"0\" cellpadding=\"2\" cellspacing=\"0\">\n" 00063 ); 00064 const QString textDirection = ( QApplication::layoutDirection() == Qt::LeftToRight ) ? QLatin1String( "left" ) : QLatin1String( "right" ); 00065 tip += QString::fromLatin1( 00066 " <tr>\n" 00067 " <td bgcolor=\"%1\" colspan=\"2\" align=\"%4\" valign=\"middle\">\n" 00068 " <div style=\"color: %2; font-weight: bold;\">\n" 00069 " %3\n" 00070 " </div>\n" 00071 " </td>\n" 00072 " </tr>\n" 00073 ).arg( txtColor ).arg( bckColor ).arg( index.data( Qt::DisplayRole ).toString() ).arg( textDirection ); 00074 00075 00076 tip += QString::fromLatin1( 00077 " <tr>\n" 00078 " <td align=\"%1\" valign=\"top\">\n" 00079 ).arg( textDirection ); 00080 00081 QString tipInfo; 00082 tipInfo += QString::fromLatin1( 00083 " <strong>%1</strong>: %2<br>\n" 00084 " <strong>%3</strong>: %4<br><br>\n" 00085 ).arg( i18n( "Total Messages" ) ).arg( collection.statistics().count() ) 00086 .arg( i18n( "Unread Messages" ) ).arg( collection.statistics().unreadCount() ); 00087 00088 if ( collection.hasAttribute<CollectionQuotaAttribute>() ) { 00089 CollectionQuotaAttribute *quota = collection.attribute<CollectionQuotaAttribute>(); 00090 if ( quota->currentValue() > -1 && quota->maximumValue() > 0 ) { 00091 qreal percentage = ( 100.0 * quota->currentValue() ) / quota->maximumValue(); 00092 00093 if ( qAbs( percentage ) >= 0.01 ) { 00094 QString percentStr = QString::number( percentage, 'f', 2 ); 00095 tipInfo += QString::fromLatin1( 00096 " <strong>%1</strong>: %2%<br>\n" 00097 ).arg( i18n( "Quota" ) ).arg( percentStr ); 00098 } 00099 } 00100 } 00101 00102 tipInfo += QString::fromLatin1( 00103 " <strong>%1</strong>: %2<br>\n" 00104 ).arg( i18n( "Storage Size" ) ).arg( KIO::convertSize( (KIO::filesize_t)( collection.statistics().size() ) ) ); 00105 00106 00107 QString iconName = CollectionUtils::defaultIconName( collection ); 00108 if ( collection.hasAttribute<EntityDisplayAttribute>() && 00109 !collection.attribute<EntityDisplayAttribute>()->iconName().isEmpty() ) { 00110 iconName = collection.attribute<EntityDisplayAttribute>()->iconName(); 00111 } 00112 00113 int iconSizes[] = { 32, 22 }; 00114 int icon_size_found = 32; 00115 00116 QString iconPath; 00117 00118 for ( int i = 0; i < 2; i++ ) { 00119 iconPath = KIconLoader::global()->iconPath( iconName, -iconSizes[ i ], true ); 00120 if ( !iconPath.isEmpty() ) { 00121 icon_size_found = iconSizes[ i ]; 00122 break; 00123 } 00124 } 00125 00126 if ( iconPath.isEmpty() ) { 00127 iconPath = KIconLoader::global()->iconPath( QLatin1String( "folder" ), -32, false ); 00128 } 00129 00130 QString tipIcon = QString::fromLatin1( 00131 " <table border=\"0\"><tr><td width=\"32\" height=\"32\" align=\"center\" valign=\"middle\">\n" 00132 " <img src=\"%1\" width=\"%2\" height=\"32\">\n" 00133 " </td></tr></table>\n" 00134 " </td>\n" 00135 ).arg( iconPath ).arg( icon_size_found ) ; 00136 00137 if ( QApplication::layoutDirection() == Qt::LeftToRight ) 00138 { 00139 tip += tipInfo + QString::fromLatin1( "</td><td align=\"%3\" valign=\"top\">" ).arg( textDirection ) + tipIcon; 00140 } 00141 else 00142 { 00143 tip += tipIcon + QString::fromLatin1( "</td><td align=\"%3\" valign=\"top\">" ).arg( textDirection ) + tipInfo; 00144 } 00145 00146 00147 tip += QString::fromLatin1( 00148 " </tr>" \ 00149 "</table>" 00150 ); 00151 00152 return tip; 00153 } 00154 00155 void proxyDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); 00156 00157 void sourceLayoutAboutToBeChanged(); 00158 void sourceLayoutChanged(); 00159 00160 QVector<QModelIndex> m_nonPersistent; 00161 QVector<QModelIndex> m_nonPersistentFirstColumn; 00162 QVector<QPersistentModelIndex> m_persistent; 00163 QVector<QPersistentModelIndex> m_persistentFirstColumn; 00164 00165 StatisticsProxyModel *mParent; 00166 00167 bool mToolTipEnabled; 00168 bool mExtraColumnsEnabled; 00169 }; 00170 00171 void StatisticsProxyModel::Private::proxyDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) 00172 { 00173 if ( mExtraColumnsEnabled ) 00174 { 00175 // Ugly hack. 00176 // The proper solution is a KExtraColumnsProxyModel, but this will do for now. 00177 QModelIndex parent = topLeft.parent(); 00178 int parentColumnCount = mParent->columnCount( parent ); 00179 QModelIndex extraTopLeft = mParent->index( topLeft.row(), parentColumnCount - 1 - 3 , parent ); 00180 QModelIndex extraBottomRight = mParent->index( bottomRight.row(), parentColumnCount -1, parent ); 00181 mParent->disconnect( mParent, SIGNAL( dataChanged( const QModelIndex&, const QModelIndex& ) ), 00182 mParent, SLOT( proxyDataChanged( const QModelIndex&, const QModelIndex& ) ) ); 00183 emit mParent->dataChanged( extraTopLeft, extraBottomRight ); 00184 00185 // We get this signal when the statistics of a row changes. 00186 // However, we need to emit data changed for the statistics of all ancestor rows too 00187 // so that recursive totals can be updated. 00188 while ( parent.isValid() ) 00189 { 00190 emit mParent->dataChanged( parent.sibling( parent.row(), parentColumnCount - 1 - 3 ), 00191 parent.sibling( parent.row(), parentColumnCount - 1 ) ); 00192 parent = parent.parent(); 00193 parentColumnCount = mParent->columnCount( parent ); 00194 } 00195 mParent->connect( mParent, SIGNAL( dataChanged( const QModelIndex&, const QModelIndex& ) ), 00196 SLOT( proxyDataChanged( const QModelIndex&, const QModelIndex& ) ) ); 00197 } 00198 } 00199 00200 void StatisticsProxyModel::Private::sourceLayoutAboutToBeChanged() 00201 { 00202 QModelIndexList persistent = mParent->persistentIndexList(); 00203 const int columnCount = mParent->sourceModel()->columnCount(); 00204 foreach( const QModelIndex &idx, persistent ) { 00205 if ( idx.column() >= columnCount ) { 00206 m_nonPersistent.push_back( idx ); 00207 m_persistent.push_back( idx ); 00208 const QModelIndex firstColumn = idx.sibling( 0, idx.column() ); 00209 m_nonPersistentFirstColumn.push_back( firstColumn ); 00210 m_persistentFirstColumn.push_back( firstColumn ); 00211 } 00212 } 00213 } 00214 00215 void StatisticsProxyModel::Private::sourceLayoutChanged() 00216 { 00217 QModelIndexList oldList; 00218 QModelIndexList newList; 00219 00220 const int columnCount = mParent->sourceModel()->columnCount(); 00221 00222 for( int i = 0; i < m_persistent.size(); ++i ) { 00223 const QModelIndex persistentIdx = m_persistent.at( i ); 00224 const QModelIndex nonPersistentIdx = m_nonPersistent.at( i ); 00225 if ( m_persistentFirstColumn.at( i ) != m_nonPersistentFirstColumn.at( i ) && persistentIdx.column() >= columnCount ) { 00226 oldList.append( nonPersistentIdx ); 00227 newList.append( persistentIdx ); 00228 } 00229 } 00230 mParent->changePersistentIndexList( oldList, newList ); 00231 } 00232 00233 void StatisticsProxyModel::setSourceModel(QAbstractItemModel* sourceModel) 00234 { 00235 // Order is important here. sourceLayoutChanged must be called *before* any downstreams react 00236 // to the layoutChanged so that it can have the QPersistentModelIndexes uptodate in time. 00237 disconnect(this, SIGNAL(layoutChanged()), this, SLOT(sourceLayoutChanged())); 00238 connect(this, SIGNAL(layoutChanged()), SLOT(sourceLayoutChanged())); 00239 QSortFilterProxyModel::setSourceModel(sourceModel); 00240 // This one should come *after* any downstream handlers of layoutAboutToBeChanged. 00241 // The connectNotify stuff below ensures that it remains the last one. 00242 disconnect(this, SIGNAL(layoutAboutToBeChanged()), this, SLOT(sourceLayoutAboutToBeChanged())); 00243 connect(this, SIGNAL(layoutAboutToBeChanged()), SLOT(sourceLayoutAboutToBeChanged())); 00244 } 00245 00246 void StatisticsProxyModel::connectNotify(const char *signal) 00247 { 00248 static bool ignore = false; 00249 if (ignore || QLatin1String(signal) == SIGNAL(layoutAboutToBeChanged())) 00250 return QSortFilterProxyModel::connectNotify(signal); 00251 ignore = true; 00252 disconnect(this, SIGNAL(layoutAboutToBeChanged()), this, SLOT(sourceLayoutAboutToBeChanged())); 00253 connect(this, SIGNAL(layoutAboutToBeChanged()), SLOT(sourceLayoutAboutToBeChanged())); 00254 ignore = false; 00255 QSortFilterProxyModel::connectNotify(signal); 00256 } 00257 00258 00259 StatisticsProxyModel::StatisticsProxyModel( QObject *parent ) 00260 : QSortFilterProxyModel( parent ), 00261 d( new Private( this ) ) 00262 { 00263 connect( this, SIGNAL( dataChanged( const QModelIndex&, const QModelIndex& ) ), 00264 SLOT( proxyDataChanged( const QModelIndex&, const QModelIndex& ) ) ); 00265 } 00266 00267 StatisticsProxyModel::~StatisticsProxyModel() 00268 { 00269 delete d; 00270 } 00271 00272 void StatisticsProxyModel::setToolTipEnabled( bool enable ) 00273 { 00274 d->mToolTipEnabled = enable; 00275 } 00276 00277 bool StatisticsProxyModel::isToolTipEnabled() const 00278 { 00279 return d->mToolTipEnabled; 00280 } 00281 00282 void StatisticsProxyModel::setExtraColumnsEnabled( bool enable ) 00283 { 00284 d->mExtraColumnsEnabled = enable; 00285 } 00286 00287 bool StatisticsProxyModel::isExtraColumnsEnabled() const 00288 { 00289 return d->mExtraColumnsEnabled; 00290 } 00291 00292 QModelIndex Akonadi::StatisticsProxyModel::index( int row, int column, const QModelIndex & parent ) const 00293 { 00294 if (!hasIndex(row, column, parent)) 00295 return QModelIndex(); 00296 00297 00298 int sourceColumn = column; 00299 00300 if ( column>=d->sourceColumnCount( parent ) ) { 00301 sourceColumn = 0; 00302 } 00303 00304 QModelIndex i = QSortFilterProxyModel::index( row, sourceColumn, parent ); 00305 return createIndex( i.row(), column, i.internalPointer() ); 00306 } 00307 00308 QVariant StatisticsProxyModel::data( const QModelIndex & index, int role) const 00309 { 00310 if (!sourceModel()) 00311 return QVariant(); 00312 if ( role == Qt::DisplayRole && index.column()>=d->sourceColumnCount( index.parent() ) ) { 00313 const QModelIndex sourceIndex = mapToSource( index.sibling( index.row(), 0 ) ); 00314 Collection collection = sourceModel()->data( sourceIndex, EntityTreeModel::CollectionRole ).value<Collection>(); 00315 00316 if ( collection.isValid() && collection.statistics().count()>=0 ) { 00317 if ( index.column() == d->sourceColumnCount( QModelIndex() )+2 ) { 00318 return KIO::convertSize( (KIO::filesize_t)( collection.statistics().size() ) ); 00319 } else if ( index.column() == d->sourceColumnCount( QModelIndex() )+1 ) { 00320 return collection.statistics().count(); 00321 } else if ( index.column() == d->sourceColumnCount( QModelIndex() ) ) { 00322 if ( collection.statistics().unreadCount() > 0 ) { 00323 return collection.statistics().unreadCount(); 00324 } else { 00325 return QString(); 00326 } 00327 } else { 00328 kWarning() << "We shouldn't get there for a column which is not total, unread or size."; 00329 return QVariant(); 00330 } 00331 } 00332 00333 } else if ( role == Qt::TextAlignmentRole && index.column()>=d->sourceColumnCount( index.parent() ) ) { 00334 return Qt::AlignRight; 00335 00336 } else if ( role == Qt::ToolTipRole && d->mToolTipEnabled ) { 00337 const QModelIndex sourceIndex = mapToSource( index.sibling( index.row(), 0 ) ); 00338 Collection collection 00339 = sourceModel()->data( sourceIndex, 00340 EntityTreeModel::CollectionRole ).value<Collection>(); 00341 00342 if ( collection.isValid() && collection.statistics().count()>0 ) { 00343 return d->toolTipForCollection( index, collection ); 00344 } 00345 00346 } else if ( role == Qt::DecorationRole && index.column() == 0 ) { 00347 const QModelIndex sourceIndex = mapToSource( index.sibling( index.row(), 0 ) ); 00348 Collection collection = sourceModel()->data( sourceIndex, EntityTreeModel::CollectionRole ).value<Collection>(); 00349 00350 if ( collection.isValid() ) 00351 return KIcon( CollectionUtils::displayIconName( collection ) ); 00352 else 00353 return QVariant(); 00354 } 00355 00356 return QAbstractProxyModel::data( index, role ); 00357 } 00358 00359 QVariant StatisticsProxyModel::headerData( int section, Qt::Orientation orientation, int role) const 00360 { 00361 if ( orientation == Qt::Horizontal && role == Qt::DisplayRole ) { 00362 if ( section == d->sourceColumnCount( QModelIndex() ) + 2 ) { 00363 return i18nc( "collection size", "Size" ); 00364 } else if ( section == d->sourceColumnCount( QModelIndex() ) + 1 ) { 00365 return i18nc( "number of entities in the collection", "Total" ); 00366 } else if ( section == d->sourceColumnCount( QModelIndex() ) ) { 00367 return i18nc( "number of unread entities in the collection", "Unread" ); 00368 } 00369 } 00370 00371 return QSortFilterProxyModel::headerData( section, orientation, role ); 00372 } 00373 00374 Qt::ItemFlags StatisticsProxyModel::flags( const QModelIndex & index ) const 00375 { 00376 if ( index.column()>=d->sourceColumnCount( index.parent() ) ) { 00377 return QSortFilterProxyModel::flags( index.sibling( index.row(), 0 ) ) 00378 & ( Qt::ItemIsSelectable | Qt::ItemIsDragEnabled // Allowed flags 00379 | Qt::ItemIsDropEnabled | Qt::ItemIsEnabled ); 00380 } 00381 00382 return QSortFilterProxyModel::flags( index ); 00383 } 00384 00385 int StatisticsProxyModel::columnCount( const QModelIndex & parent ) const 00386 { 00387 if ( sourceModel()==0 ) { 00388 return 0; 00389 } else { 00390 return d->sourceColumnCount( parent ) 00391 + ( d->mExtraColumnsEnabled ? 3 : 0 ); 00392 } 00393 } 00394 00395 QModelIndexList StatisticsProxyModel::match( const QModelIndex& start, int role, const QVariant& value, 00396 int hits, Qt::MatchFlags flags ) const 00397 { 00398 if ( role < Qt::UserRole ) 00399 return QSortFilterProxyModel::match( start, role, value, hits, flags ); 00400 00401 QModelIndexList list; 00402 QModelIndex proxyIndex; 00403 foreach ( const QModelIndex &idx, sourceModel()->match( mapToSource( start ), role, value, hits, flags ) ) { 00404 proxyIndex = mapFromSource( idx ); 00405 if ( proxyIndex.isValid() ) 00406 list << proxyIndex; 00407 } 00408 00409 return list; 00410 } 00411 00412 00413 #include "statisticsproxymodel.moc" 00414