/****************************************************************************** * * This file is part of libkdepim. * * Copyright (C) 2008 Szymon Tomasz Stefanek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * *****************************************************************************/ #include "foldertreewidget.h" #include // for KIO::filesize_t and related functions #include #include #include #include #include #include #include #include #include #include #define FOLDERTREEWIDGETITEM_TYPE ( QTreeWidgetItem::UserType + 0xcafe ) namespace KPIM { #define ITEM_LABEL_RIGHT_MARGIN 2 #define ITEM_LABEL_TO_UNREADCOUNT_SPACING 2 class FolderTreeWidgetItemLabelColumnDelegate : public QStyledItemDelegate { protected: FolderTreeWidget *mFolderTreeWidget; public: FolderTreeWidgetItemLabelColumnDelegate( FolderTreeWidget *parent ) : QStyledItemDelegate( parent ), mFolderTreeWidget( parent ) {} ~FolderTreeWidgetItemLabelColumnDelegate() {} virtual QSize sizeHint( const QStyleOptionViewItem &option, const QModelIndex &index ) const { QStyleOptionViewItemV4 opt = option; initStyleOption( &opt, index ); opt.text = "X"; // fake a text (so the height is computed correctly) opt.features |= QStyleOptionViewItemV2::HasDisplay; return mFolderTreeWidget->style()->sizeFromContents( QStyle::CT_ItemViewItem, &opt, QSize(), mFolderTreeWidget ); } virtual void paint( QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index ) const { // // The equilibrism in this function attempts to rely on QStyle for most of the job. // QStyleOptionViewItemV4 opt = option; initStyleOption( &opt, index ); opt.text = ""; // draw no text for me, please.. I'll do it in a while QStyle * style = mFolderTreeWidget->style(); style->drawControl( QStyle::CE_ItemViewItem, &opt, painter, mFolderTreeWidget ); // FIXME: this is plain ugly :D // OTOH seaching the item by model index is extremely expensive when compared to a cast. // We're also using static_cast<> instead of dynamic_cast<> to avoid performance loss: // we actually trust the developer to add only real FolderTreeWidgetItems to this widget. FolderTreeWidgetItem * item = static_cast( index.internalPointer() ); if ( !item ) return; // ugh :/ // draw the labelText(). QRect textRect = style->subElementRect( QStyle::SE_ItemViewItemText, &opt, mFolderTreeWidget ); textRect.setWidth( textRect.width() - ITEM_LABEL_RIGHT_MARGIN ); // keeping indipendent state variables is faster than saving and restoring the whole painter state QPen oldPen = painter->pen(); QFont oldFont = painter->font(); if ( item->isCloseToQuota() && ( !( opt.state & QStyle::State_Selected ) ) ) { painter->setPen( QPen( mFolderTreeWidget->closeToQuotaWarningColor(), 0 ) ); } else { QPalette::ColorGroup cg = opt.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled; if (cg == QPalette::Normal && !(opt.state & QStyle::State_Active)) cg = QPalette::Inactive; QPalette::ColorRole cr = ( opt.state & QStyle::State_Selected ) ? QPalette::HighlightedText : QPalette::Text; painter->setPen( QPen( opt.palette.brush( cg, cr ), 0 ) ); } int unread = item->unreadCount(); int childUnread = item->childrenUnreadCount(); bool displayChildUnread = ( childUnread > 0 ) && ( !item->isExpanded() ); if ( ( unread > 0 ) || displayChildUnread ) { // font is bold for sure (unread stuff in folder) QFont f = opt.font; f.setBold( true ); painter->setFont( f ); QFontMetrics fm( painter->font() ); // use the bold font metrics if ( ( mFolderTreeWidget->unreadColumnIndex() == -1 ) || mFolderTreeWidget->isColumnHidden( mFolderTreeWidget->unreadColumnIndex() ) ) { // we need to paint the unread count too QString unreadText; if ( unread > 0 ) { if ( displayChildUnread ) unreadText = QString("(%1 + %2)").arg( unread ).arg( childUnread ); else unreadText = QString("(%1)").arg( unread ); } else unreadText = QString("(0 + %1)").arg( childUnread ); // compute the potential width so we know if elision has to be performed int unreadWidth = fm.width( unreadText ); int labelWidth = fm.width( item->labelText() ); int maxWidth = labelWidth + ITEM_LABEL_TO_UNREADCOUNT_SPACING + unreadWidth; QString label; if ( maxWidth > textRect.width() ) { // must elide label = item->elidedLabelText( fm, textRect.width() - ( ITEM_LABEL_TO_UNREADCOUNT_SPACING + unreadWidth ) ); labelWidth = fm.width( label ); // the condition inside this call is an optimization (it's faster than simply label != item->labelText()) item->setLabelTextElided( ( label.length() != item->labelText().length() ) || ( label != item->labelText() ) ); } else { label = item->labelText(); // no elision needed item->setLabelTextElided( false ); } painter->drawText( textRect, Qt::AlignLeft | Qt::TextSingleLine | Qt::AlignVCenter, label ); if ( QApplication::isLeftToRight() ) textRect.setLeft( textRect.left() + labelWidth + ITEM_LABEL_TO_UNREADCOUNT_SPACING ); else textRect.setRight( textRect.right() - labelWidth - ITEM_LABEL_TO_UNREADCOUNT_SPACING ); if ( !( opt.state & QStyle::State_Selected ) ) { painter->setPen( QPen( mFolderTreeWidget->unreadCountColor(), 0 ) ); } painter->drawText( textRect, Qt::AlignLeft | Qt::TextSingleLine | Qt::AlignVCenter, unreadText ); } else { // got unread messages: bold font but no special text tricks QString label = item->elidedLabelText( fm, textRect.width() ); // the condition inside this call is an optimization (it's faster than simply label != item->labelText()) item->setLabelTextElided( ( label.length() != item->labelText().length() ) || ( label != item->labelText() ) ); painter->drawText( textRect, Qt::AlignLeft | Qt::TextSingleLine | Qt::AlignVCenter, label ); } } else { // no unread messages: normal font, no text tricks QString label = item->elidedLabelText( opt.fontMetrics, textRect.width() ); // the condition inside this call is an optimization (it's faster than simply label != item->labelText()) item->setLabelTextElided( ( label.length() != item->labelText().length() ) || ( label != item->labelText() ) ); painter->setFont( opt.font ); painter->drawText( textRect, Qt::AlignLeft | Qt::TextSingleLine | Qt::AlignVCenter, label ); } painter->setPen( oldPen ); painter->setFont( oldFont ); } virtual void setModelData( QWidget *editor, QAbstractItemModel *model, const QModelIndex &index ) const { QStyledItemDelegate::setModelData( editor, model, index ); FolderTreeWidgetItem *item = static_cast( index.internalPointer() ); if ( item ) { // test if old text != new text if( item->labelText() != item->text( mFolderTreeWidget->labelColumnIndex() ) ) { item->setLabelText( item->text( mFolderTreeWidget->labelColumnIndex() ) ); mFolderTreeWidget->emitRenamed( item ); } } } }; FolderTreeWidget::FolderTreeWidget( QWidget *parent , const char *name ) : KPIM::TreeWidget( parent , name ), mUnreadCountColor( Qt::blue ), mCloseToQuotaWarningColor( Qt::red ) { mLabelColumnIndex = -1; mUnreadColumnIndex = -1; mTotalColumnIndex = -1; mDataSizeColumnIndex = -1; setAlternatingRowColors( true ); setAcceptDrops( true ); setAllColumnsShowFocus( true ); setRootIsDecorated( true ); setSortingEnabled( true ); setUniformRowHeights( true ); // optimize please //setAnimated( true ); // this slows down everything a lot header()->setSortIndicatorShown( true ); header()->setClickable( true ); //setSelectionMode( Extended ); connect( this, SIGNAL(itemExpanded(QTreeWidgetItem*)), SLOT(updateExpandedState(QTreeWidgetItem*)) ); connect( this, SIGNAL(itemCollapsed(QTreeWidgetItem*)), SLOT(updateExpandedState(QTreeWidgetItem*)) ); } int FolderTreeWidget::addLabelColumn( const QString &headerLabel ) { if ( mLabelColumnIndex == -1 ) { mLabelColumnIndex = addColumn( headerLabel ); setItemDelegateForColumn( mLabelColumnIndex, new FolderTreeWidgetItemLabelColumnDelegate( this ) ); } return mLabelColumnIndex; } bool FolderTreeWidget::labelColumnVisible() const { if ( mLabelColumnIndex == -1 ) return false; return !isColumnHidden( mLabelColumnIndex ); } int FolderTreeWidget::addTotalColumn( const QString &headerLabel ) { if ( mTotalColumnIndex == -1 ) mTotalColumnIndex = addColumn( headerLabel , Qt::AlignRight ); return mTotalColumnIndex; } bool FolderTreeWidget::totalColumnVisible() const { if ( mTotalColumnIndex == -1 ) return false; return !isColumnHidden( mTotalColumnIndex ); } int FolderTreeWidget::addUnreadColumn( const QString &headerLabel ) { if ( mUnreadColumnIndex == -1 ) mUnreadColumnIndex = addColumn( headerLabel , Qt::AlignRight ); return mUnreadColumnIndex; } bool FolderTreeWidget::unreadColumnVisible() const { if ( mUnreadColumnIndex == -1 ) return false; return !isColumnHidden( mUnreadColumnIndex ); } int FolderTreeWidget::addDataSizeColumn( const QString &headerLabel ) { if ( mDataSizeColumnIndex == -1 ) mDataSizeColumnIndex = addColumn( headerLabel , Qt::AlignRight ); return mDataSizeColumnIndex; } bool FolderTreeWidget::dataSizeColumnVisible() const { if ( mDataSizeColumnIndex == -1 ) return false; return !isColumnHidden( mDataSizeColumnIndex ); } void FolderTreeWidget::updateColumnForItem( FolderTreeWidgetItem * item, int columnIndex ) { update( indexFromItem( item, columnIndex ) ); } void FolderTreeWidget::updateExpandedState( QTreeWidgetItem *item ) { FolderTreeWidgetItem *folderItem = dynamic_cast( item ); Q_ASSERT( folderItem ); if ( folderItem ) folderItem->updateExpandedState(); } void FolderTreeWidget::setCloseToQuotaWarningColor( const QColor &clr ) { mCloseToQuotaWarningColor = clr; update(); } void FolderTreeWidget::setUnreadCountColor( const QColor &clr ) { mUnreadCountColor = clr; update(); } void FolderTreeWidget::emitRenamed( QTreeWidgetItem *item ) { emit renamed( item ); } FolderTreeWidgetItem::FolderTreeWidgetItem( FolderTreeWidget *parent, const QString &label, Protocol protocol, FolderType folderType ) : QTreeWidgetItem( parent, FOLDERTREEWIDGETITEM_TYPE ), mProtocol( protocol ), mFolderType( folderType ), mLabelText( label ), mTotalCount( 0 ), mUnreadCount( 0 ), mDataSize( -1 ), mIsCloseToQuota( 0 ), mLabelTextElided( 0 ), mChildrenTotalCount( 0 ), mChildrenUnreadCount( 0 ), mChildrenDataSize( -1 ), mAlwaysDisplayCounts( false ) { setText( parent->labelColumnIndex(), label ); } FolderTreeWidgetItem::FolderTreeWidgetItem( FolderTreeWidgetItem *parent, const QString &label, Protocol protocol, FolderType folderType ) : QTreeWidgetItem( parent, FOLDERTREEWIDGETITEM_TYPE ), mProtocol( protocol ), mFolderType( folderType ), mLabelText( label ), mTotalCount( 0 ), mUnreadCount( 0 ), mDataSize( -1 ), mIsCloseToQuota( 0 ), mLabelTextElided( 0 ), mChildrenTotalCount( 0 ), mChildrenUnreadCount( 0 ), mChildrenDataSize( -1 ), mAlwaysDisplayCounts( false ) { FolderTreeWidget * tree = dynamic_cast< FolderTreeWidget * >( treeWidget() ); if ( tree ) setText( tree->labelColumnIndex(), label ); } QString FolderTreeWidgetItem::protocolDescription() const { switch( mProtocol ) { case Local: return i18n( "Local" ); break; case Imap: return i18n( "IMAP" ); break; case CachedImap: return i18n( "Cached IMAP" ); break; case News: return i18n( "News" ); break; case Search: return i18n( "Search" ); break; case NONE: return i18n( "None" ); break; default: break; } return i18n( "Unknown" ); } bool FolderTreeWidgetItem::updateChildrenCounts() { int cc = childCount(); if ( cc < 1 ) return false; int idx = 0; int oldTotal = mChildrenTotalCount; int oldUnread = mChildrenUnreadCount; qint64 oldSize = mChildrenDataSize; mChildrenTotalCount = 0; mChildrenUnreadCount = 0; mChildrenDataSize = 0; bool gotValidDataSize = false; while ( idx < cc ) { FolderTreeWidgetItem *it = static_cast< FolderTreeWidgetItem * >( QTreeWidgetItem::child( idx ) ); mChildrenTotalCount += it->totalCount() + it->childrenTotalCount(); mChildrenUnreadCount += it->unreadCount() + it->childrenUnreadCount(); if ( it->dataSize() >= 0) { gotValidDataSize = true; mChildrenDataSize += it->dataSize(); } if ( it->childrenDataSize() >= 0 ) { gotValidDataSize = true; mChildrenDataSize += it->childrenDataSize(); } idx++; } if ( !gotValidDataSize ) mChildrenDataSize = -1; // keep it invald return ( oldTotal != mChildrenTotalCount ) || ( oldUnread != mChildrenUnreadCount ) || ( oldSize != mChildrenDataSize ); } void FolderTreeWidgetItem::setLabelText( const QString &label ) { mLabelText = label; // We set the text of the item so QStyle will compute the height correctly FolderTreeWidget * tree = dynamic_cast< FolderTreeWidget * >( treeWidget() ); Q_ASSERT ( tree ); int idx = tree->labelColumnIndex(); if ( idx >= 0 ) { setText( idx, label ); setTextAlignment( idx, Qt::AlignRight ); } } void FolderTreeWidgetItem::setUnreadCount( int unreadCount ) { mUnreadCount = unreadCount; int unreadCountToDisplay = unreadCount; if ( mChildrenUnreadCount > 0 && !isExpanded() ) unreadCountToDisplay += mChildrenUnreadCount; FolderTreeWidget * tree = dynamic_cast< FolderTreeWidget * >( treeWidget() ); if ( tree ) { int idx = tree->unreadColumnIndex(); if ( idx >= 0 ) { if ( ( parent() || mAlwaysDisplayCounts || !isExpanded() ) && unreadCountToDisplay > 0 ) setText( idx, QString::number( unreadCountToDisplay ) ); else setText( idx, QString() ); } setTextAlignment( idx, Qt::AlignRight ); } } void FolderTreeWidgetItem::setTotalCount( int totalCount ) { mTotalCount = totalCount; int totalCountToDisplay = totalCount; if ( mChildrenTotalCount > 0 && !isExpanded() ) totalCountToDisplay += mChildrenTotalCount; FolderTreeWidget * tree = dynamic_cast< FolderTreeWidget * >( treeWidget() ); if( tree ) { int idx = tree->totalColumnIndex(); if ( idx >= 0 ) { // FIXME: Why the "parent()" logic is hardwired here ? if ( parent() || mAlwaysDisplayCounts || !isExpanded() ) setText( idx, QString::number( totalCountToDisplay ) ); else setText( idx, QString() ); setTextAlignment( idx, Qt::AlignRight ); } } } void FolderTreeWidgetItem::setDataSize( qint64 dataSize ) { mDataSize = dataSize; QString txt; QString sizeText = KIO::convertSize( mDataSize >= 0 ? (KIO::filesize_t)mDataSize : (KIO::filesize_t)0 ); QString childSizeText = KIO::convertSize( (KIO::filesize_t)mChildrenDataSize ); // A top level item, they all have size 0 // FIXME: Why this logic is hardwired here ? if ( !parent() && !mAlwaysDisplayCounts ) { if ( mChildrenDataSize >= 0 && !isExpanded() ) txt = childSizeText; else txt = QString(); } // Not a top level item (or always displays counts) else if ( ( mDataSize >= 0 ) || ( mChildrenDataSize >= 0 ) ) { txt = sizeText; if ( !isExpanded() ) { if ( mChildrenDataSize >= 0 ) txt += " + " + childSizeText; } } else txt = "-"; FolderTreeWidget * tree = dynamic_cast< FolderTreeWidget * >( treeWidget() ); if( tree ) { int idx = tree->dataSizeColumnIndex(); if ( idx >= 0 ) { setText( idx, txt ); setTextAlignment( idx, Qt::AlignRight ); } } } void FolderTreeWidgetItem::updateExpandedState() { // Just trigger an update of the column text, it will all be handled thaere. setDataSize( mDataSize ); setTotalCount( mTotalCount ); setUnreadCount( mUnreadCount ); } void FolderTreeWidgetItem::updateColumn( int columnIndex ) { FolderTreeWidget * tree = dynamic_cast< FolderTreeWidget * >( treeWidget() ); if ( tree ) { tree->updateColumnForItem( this, columnIndex ); if ( columnIndex!=tree->labelColumnIndex() && !tree->unreadColumnVisible() && tree->labelColumnVisible() ) { updateColumn( tree->labelColumnIndex() ); } } } void FolderTreeWidgetItem::setIsCloseToQuota( bool closeToQuota ) { if ( ( mIsCloseToQuota == 1 ) == closeToQuota ) return; mIsCloseToQuota = closeToQuota ? 1 : 0; FolderTreeWidget * tree = dynamic_cast< FolderTreeWidget * >( treeWidget() ); if ( tree && tree->labelColumnVisible() ) updateColumn( tree->labelColumnIndex() ); } bool FolderTreeWidgetItem::operator < ( const QTreeWidgetItem &other ) const { // FIXME: Sort by children counts too ? int sortCol = treeWidget()->sortColumn(); if ( sortCol < 0 ) return true; // just "yes" :D FolderTreeWidget * w = dynamic_cast< FolderTreeWidget * >( treeWidget() ); if ( w ) { // sanity check if ( dynamic_cast( &other ) ) { const FolderTreeWidgetItem * oitem = dynamic_cast< const FolderTreeWidgetItem * >( &other ); if ( oitem ) { if ( sortCol == w->unreadColumnIndex() ) return mUnreadCount < oitem->unreadCount(); if ( sortCol == w->totalColumnIndex() ) return mTotalCount < oitem->totalCount(); if ( sortCol == w->dataSizeColumnIndex() ) return mDataSize < oitem->dataSize(); if ( sortCol == w->labelColumnIndex() ) { // Special sorting based on the item type int thisProto = ( int ) mProtocol; int thatProto = ( int ) oitem->protocol(); if ( thisProto < thatProto ) return true; if ( thisProto > thatProto ) return false; int thisType = ( int ) mFolderType; int thatType = ( int ) oitem->folderType(); if ( thisType < thatType ) return true; if ( thisType > thatType ) return false; // and finally compare by name return mLabelText.toLower() < oitem->labelText().toLower(); } } } } return text(sortCol) < other.text(sortCol); } QString FolderTreeWidgetItem::elidedLabelText( const QFontMetrics &metrics, unsigned int width ) const { return metrics.elidedText( labelText(), Qt::ElideRight, width ); } }