kde-playground/kdepim/knode/foldertreewidget.cpp
2015-04-14 21:49:29 +00:00

625 lines
20 KiB
C++

/******************************************************************************
*
* This file is part of libkdepim.
*
* Copyright (C) 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
*
* 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 <kio/global.h> // for KIO::filesize_t and related functions
#include <klineedit.h>
#include <klocale.h>
#include <QHeaderView>
#include <QDropEvent>
#include <QMimeData>
#include <QStyledItemDelegate>
#include <QStyle>
#include <QPainter>
#include <QFontMetrics>
#include <QApplication>
#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<FolderTreeWidgetItem *>( 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<FolderTreeWidgetItem *>( 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<FolderTreeWidgetItem*>( 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<const FolderTreeWidgetItem*>( &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 );
}
}