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 QModelIndex extraTopLeft = mParent->index( topLeft.row(), mParent->columnCount( parent ) - 1 - 3 , parent ); 00179 QModelIndex extraBottomRight = mParent->index( bottomRight.row(), mParent->columnCount( parent ) -1, parent ); 00180 mParent->disconnect( mParent, SIGNAL( dataChanged( const QModelIndex&, const QModelIndex& ) ), 00181 mParent, SLOT( proxyDataChanged( const QModelIndex&, const QModelIndex& ) ) ); 00182 emit mParent->dataChanged( extraTopLeft, extraBottomRight ); 00183 00184 // We get this signal when the statistics of a row changes. 00185 // However, we need to emit data changed for the statistics of all ancestor rows too 00186 // so that recursive totals can be updated. 00187 while ( parent.isValid() ) 00188 { 00189 emit mParent->dataChanged( parent.sibling( parent.row(), mParent->columnCount( parent ) - 1 - 3 ), 00190 parent.sibling( parent.row(), mParent->columnCount( parent ) - 1 ) ); 00191 parent = parent.parent(); 00192 } 00193 mParent->connect( mParent, SIGNAL( dataChanged( const QModelIndex&, const QModelIndex& ) ), 00194 SLOT( proxyDataChanged( const QModelIndex&, const QModelIndex& ) ) ); 00195 } 00196 } 00197 00198 void StatisticsProxyModel::Private::sourceLayoutAboutToBeChanged() 00199 { 00200 QModelIndexList persistent = mParent->persistentIndexList(); 00201 const int columnCount = mParent->sourceModel()->columnCount(); 00202 foreach( const QModelIndex &idx, persistent ) { 00203 if ( idx.column() >= columnCount ) { 00204 m_nonPersistent.push_back( idx ); 00205 m_persistent.push_back( idx ); 00206 const QModelIndex firstColumn = idx.sibling( 0, idx.column() ); 00207 m_nonPersistentFirstColumn.push_back( firstColumn ); 00208 m_persistentFirstColumn.push_back( firstColumn ); 00209 } 00210 } 00211 } 00212 00213 void StatisticsProxyModel::Private::sourceLayoutChanged() 00214 { 00215 QModelIndexList oldList; 00216 QModelIndexList newList; 00217 00218 const int columnCount = mParent->sourceModel()->columnCount(); 00219 00220 for( int i = 0; i < m_persistent.size(); ++i ) { 00221 const QModelIndex persistentIdx = m_persistent.at( i ); 00222 const QModelIndex nonPersistentIdx = m_nonPersistent.at( i ); 00223 if ( m_persistentFirstColumn.at( i ) != m_nonPersistentFirstColumn.at( i ) && persistentIdx.column() >= columnCount ) { 00224 oldList.append( nonPersistentIdx ); 00225 newList.append( persistentIdx ); 00226 } 00227 } 00228 mParent->changePersistentIndexList( oldList, newList ); 00229 } 00230 00231 void StatisticsProxyModel::setSourceModel(QAbstractItemModel* sourceModel) 00232 { 00233 // Order is important here. sourceLayoutChanged must be called *before* any downstreams react 00234 // to the layoutChanged so that it can have the QPersistentModelIndexes uptodate in time. 00235 disconnect(this, SIGNAL(layoutChanged()), this, SLOT(sourceLayoutChanged())); 00236 connect(this, SIGNAL(layoutChanged()), SLOT(sourceLayoutChanged())); 00237 QSortFilterProxyModel::setSourceModel(sourceModel); 00238 // This one should come *after* any downstream handlers of layoutAboutToBeChanged. 00239 // The connectNotify stuff below ensures that it remains the last one. 00240 disconnect(this, SIGNAL(layoutAboutToBeChanged()), this, SLOT(sourceLayoutAboutToBeChanged())); 00241 connect(this, SIGNAL(layoutAboutToBeChanged()), SLOT(sourceLayoutAboutToBeChanged())); 00242 } 00243 00244 void StatisticsProxyModel::connectNotify(const char *signal) 00245 { 00246 static bool ignore = false; 00247 if (ignore || QLatin1String(signal) == SIGNAL(layoutAboutToBeChanged())) 00248 return QSortFilterProxyModel::connectNotify(signal); 00249 ignore = true; 00250 disconnect(this, SIGNAL(layoutAboutToBeChanged()), this, SLOT(sourceLayoutAboutToBeChanged())); 00251 connect(this, SIGNAL(layoutAboutToBeChanged()), SLOT(sourceLayoutAboutToBeChanged())); 00252 ignore = false; 00253 QSortFilterProxyModel::connectNotify(signal); 00254 } 00255 00256 00257 StatisticsProxyModel::StatisticsProxyModel( QObject *parent ) 00258 : QSortFilterProxyModel( parent ), 00259 d( new Private( this ) ) 00260 { 00261 connect( this, SIGNAL( dataChanged( const QModelIndex&, const QModelIndex& ) ), 00262 SLOT( proxyDataChanged( const QModelIndex&, const QModelIndex& ) ) ); 00263 } 00264 00265 StatisticsProxyModel::~StatisticsProxyModel() 00266 { 00267 delete d; 00268 } 00269 00270 void StatisticsProxyModel::setToolTipEnabled( bool enable ) 00271 { 00272 d->mToolTipEnabled = enable; 00273 } 00274 00275 bool StatisticsProxyModel::isToolTipEnabled() const 00276 { 00277 return d->mToolTipEnabled; 00278 } 00279 00280 void StatisticsProxyModel::setExtraColumnsEnabled( bool enable ) 00281 { 00282 d->mExtraColumnsEnabled = enable; 00283 } 00284 00285 bool StatisticsProxyModel::isExtraColumnsEnabled() const 00286 { 00287 return d->mExtraColumnsEnabled; 00288 } 00289 00290 QModelIndex Akonadi::StatisticsProxyModel::index( int row, int column, const QModelIndex & parent ) const 00291 { 00292 if (!hasIndex(row, column, parent)) 00293 return QModelIndex(); 00294 00295 00296 int sourceColumn = column; 00297 00298 if ( column>=d->sourceColumnCount( parent ) ) { 00299 sourceColumn = 0; 00300 } 00301 00302 QModelIndex i = QSortFilterProxyModel::index( row, sourceColumn, parent ); 00303 return createIndex( i.row(), column, i.internalPointer() ); 00304 } 00305 00306 QVariant StatisticsProxyModel::data( const QModelIndex & index, int role) const 00307 { 00308 if (!sourceModel()) 00309 return QVariant(); 00310 if ( role == Qt::DisplayRole && index.column()>=d->sourceColumnCount( index.parent() ) ) { 00311 const QModelIndex sourceIndex = mapToSource( index.sibling( index.row(), 0 ) ); 00312 Collection collection = sourceModel()->data( sourceIndex, EntityTreeModel::CollectionRole ).value<Collection>(); 00313 00314 if ( collection.isValid() && collection.statistics().count()>=0 ) { 00315 if ( index.column() == d->sourceColumnCount( QModelIndex() )+2 ) { 00316 return KIO::convertSize( (KIO::filesize_t)( collection.statistics().size() ) ); 00317 } else if ( index.column() == d->sourceColumnCount( QModelIndex() )+1 ) { 00318 return collection.statistics().count(); 00319 } else if ( index.column() == d->sourceColumnCount( QModelIndex() ) ) { 00320 if ( collection.statistics().unreadCount() > 0 ) { 00321 return collection.statistics().unreadCount(); 00322 } else { 00323 return QString(); 00324 } 00325 } else { 00326 kWarning() << "We shouldn't get there for a column which is not total, unread or size."; 00327 return QVariant(); 00328 } 00329 } 00330 00331 } else if ( role == Qt::TextAlignmentRole && index.column()>=d->sourceColumnCount( index.parent() ) ) { 00332 return Qt::AlignRight; 00333 00334 } else if ( role == Qt::ToolTipRole && d->mToolTipEnabled ) { 00335 const QModelIndex sourceIndex = mapToSource( index.sibling( index.row(), 0 ) ); 00336 Collection collection 00337 = sourceModel()->data( sourceIndex, 00338 EntityTreeModel::CollectionRole ).value<Collection>(); 00339 00340 if ( collection.isValid() && collection.statistics().count()>0 ) { 00341 return d->toolTipForCollection( index, collection ); 00342 } 00343 00344 } else if ( role == Qt::DecorationRole && index.column() == 0 ) { 00345 const QModelIndex sourceIndex = mapToSource( index.sibling( index.row(), 0 ) ); 00346 Collection collection = sourceModel()->data( sourceIndex, EntityTreeModel::CollectionRole ).value<Collection>(); 00347 00348 if ( collection.isValid() ) 00349 return KIcon( CollectionUtils::displayIconName( collection ) ); 00350 else 00351 return QVariant(); 00352 } 00353 00354 return QAbstractProxyModel::data( index, role ); 00355 } 00356 00357 QVariant StatisticsProxyModel::headerData( int section, Qt::Orientation orientation, int role) const 00358 { 00359 if ( orientation == Qt::Horizontal && role == Qt::DisplayRole ) { 00360 if ( section == d->sourceColumnCount( QModelIndex() ) + 2 ) { 00361 return i18nc( "collection size", "Size" ); 00362 } else if ( section == d->sourceColumnCount( QModelIndex() ) + 1 ) { 00363 return i18nc( "number of entities in the collection", "Total" ); 00364 } else if ( section == d->sourceColumnCount( QModelIndex() ) ) { 00365 return i18nc( "number of unread entities in the collection", "Unread" ); 00366 } 00367 } 00368 00369 return QSortFilterProxyModel::headerData( section, orientation, role ); 00370 } 00371 00372 Qt::ItemFlags StatisticsProxyModel::flags( const QModelIndex & index ) const 00373 { 00374 if ( index.column()>=d->sourceColumnCount( index.parent() ) ) { 00375 return QSortFilterProxyModel::flags( index.sibling( index.row(), 0 ) ) 00376 & ( Qt::ItemIsSelectable | Qt::ItemIsDragEnabled // Allowed flags 00377 | Qt::ItemIsDropEnabled | Qt::ItemIsEnabled ); 00378 } 00379 00380 return QSortFilterProxyModel::flags( index ); 00381 } 00382 00383 int StatisticsProxyModel::columnCount( const QModelIndex & parent ) const 00384 { 00385 if ( sourceModel()==0 ) { 00386 return 0; 00387 } else { 00388 return d->sourceColumnCount( parent ) 00389 + ( d->mExtraColumnsEnabled ? 3 : 0 ); 00390 } 00391 } 00392 00393 QModelIndexList StatisticsProxyModel::match( const QModelIndex& start, int role, const QVariant& value, 00394 int hits, Qt::MatchFlags flags ) const 00395 { 00396 if ( role < Qt::UserRole ) 00397 return QSortFilterProxyModel::match( start, role, value, hits, flags ); 00398 00399 QModelIndexList list; 00400 QModelIndex proxyIndex; 00401 foreach ( const QModelIndex &idx, sourceModel()->match( mapToSource( start ), role, value, hits, flags ) ) { 00402 proxyIndex = mapFromSource( idx ); 00403 if ( proxyIndex.isValid() ) 00404 list << proxyIndex; 00405 } 00406 00407 return list; 00408 } 00409 00410 00411 #include "statisticsproxymodel.moc" 00412