1 /******************************************************************************
3 * This file is part of libkdepim.
5 * Copyright (C) 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Library General Public License for more details.
17 * You should have received a copy of the GNU Library General Public License
18 * along with this library; see the file COPYING.LIB. If not, write to
19 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 * Boston, MA 02110-1301, USA.
22 *****************************************************************************/
24 #include "foldertreewidget.h"
26 #include <kio/global.h> // for KIO::filesize_t and related functions
27 #include <klineedit.h>
30 #include <QHeaderView>
33 #include <QStyledItemDelegate>
36 #include <QFontMetrics>
37 #include <QApplication>
39 #define FOLDERTREEWIDGETITEM_TYPE ( QTreeWidgetItem::UserType + 0xcafe )
44 #define ITEM_LABEL_RIGHT_MARGIN 2
45 #define ITEM_LABEL_TO_UNREADCOUNT_SPACING 2
47 class FolderTreeWidgetItemLabelColumnDelegate
: public QStyledItemDelegate
50 FolderTreeWidget
*mFolderTreeWidget
;
53 FolderTreeWidgetItemLabelColumnDelegate( FolderTreeWidget
*parent
)
54 : QStyledItemDelegate( parent
), mFolderTreeWidget( parent
) {}
56 ~FolderTreeWidgetItemLabelColumnDelegate()
59 virtual QSize
sizeHint( const QStyleOptionViewItem
&option
, const QModelIndex
&index
) const
61 QStyleOptionViewItemV4 opt
= option
;
62 initStyleOption( &opt
, index
);
64 opt
.text
= "X"; // fake a text (so the height is computed correctly)
65 opt
.features
|= QStyleOptionViewItemV2::HasDisplay
;
67 return mFolderTreeWidget
->style()->sizeFromContents( QStyle::CT_ItemViewItem
, &opt
, QSize(), mFolderTreeWidget
);
70 virtual void paint( QPainter
*painter
, const QStyleOptionViewItem
&option
, const QModelIndex
&index
) const
73 // The equilibrism in this function attempts to rely on QStyle for most of the job.
75 QStyleOptionViewItemV4 opt
= option
;
76 initStyleOption( &opt
, index
);
78 opt
.text
= ""; // draw no text for me, please.. I'll do it in a while
80 QStyle
* style
= mFolderTreeWidget
->style();
82 style
->drawControl( QStyle::CE_ItemViewItem
, &opt
, painter
, mFolderTreeWidget
);
84 // FIXME: this is plain ugly :D
85 // OTOH seaching the item by model index is extremely expensive when compared to a cast.
86 // We're also using static_cast<> instead of dynamic_cast<> to avoid performance loss:
87 // we actually trust the developer to add only real FolderTreeWidgetItems to this widget.
88 FolderTreeWidgetItem
* item
= static_cast<FolderTreeWidgetItem
*>( index
.internalPointer() );
92 // draw the labelText().
93 QRect textRect
= style
->subElementRect( QStyle::SE_ItemViewItemText
, &opt
, mFolderTreeWidget
);
94 textRect
.setWidth( textRect
.width() - ITEM_LABEL_RIGHT_MARGIN
);
96 // keeping indipendent state variables is faster than saving and restoring the whole painter state
97 QPen oldPen
= painter
->pen();
98 QFont oldFont
= painter
->font();
100 if ( item
->isCloseToQuota() && ( !( opt
.state
& QStyle::State_Selected
) ) )
102 painter
->setPen( QPen( mFolderTreeWidget
->closeToQuotaWarningColor(), 0 ) );
104 QPalette::ColorGroup cg
= opt
.state
& QStyle::State_Enabled
? QPalette::Normal
: QPalette::Disabled
;
106 if (cg
== QPalette::Normal
&& !(opt
.state
& QStyle::State_Active
))
107 cg
= QPalette::Inactive
;
109 QPalette::ColorRole cr
= ( opt
.state
& QStyle::State_Selected
) ? QPalette::HighlightedText
: QPalette::Text
;
110 painter
->setPen( QPen( opt
.palette
.brush( cg
, cr
), 0 ) );
113 int unread
= item
->unreadCount();
114 int childUnread
= item
->childrenUnreadCount();
115 bool displayChildUnread
= ( childUnread
> 0 ) && ( !item
->isExpanded() );
117 if ( ( unread
> 0 ) || displayChildUnread
)
119 // font is bold for sure (unread stuff in folder)
122 painter
->setFont( f
);
124 QFontMetrics
fm( painter
->font() ); // use the bold font metrics
127 ( mFolderTreeWidget
->unreadColumnIndex() == -1 ) ||
128 mFolderTreeWidget
->isColumnHidden( mFolderTreeWidget
->unreadColumnIndex() )
131 // we need to paint the unread count too
136 if ( displayChildUnread
)
137 unreadText
= QString("(%1 + %2)").arg( unread
).arg( childUnread
);
139 unreadText
= QString("(%1)").arg( unread
);
141 unreadText
= QString("(0 + %1)").arg( childUnread
);
143 // compute the potential width so we know if elision has to be performed
145 int unreadWidth
= fm
.width( unreadText
);
146 int labelWidth
= fm
.width( item
->labelText() );
147 int maxWidth
= labelWidth
+ ITEM_LABEL_TO_UNREADCOUNT_SPACING
+ unreadWidth
;
150 if ( maxWidth
> textRect
.width() )
153 label
= item
->elidedLabelText( fm
, textRect
.width() - ( ITEM_LABEL_TO_UNREADCOUNT_SPACING
+ unreadWidth
) );
154 labelWidth
= fm
.width( label
);
156 // the condition inside this call is an optimization (it's faster than simply label != item->labelText())
157 item
->setLabelTextElided( ( label
.length() != item
->labelText().length() ) || ( label
!= item
->labelText() ) );
159 label
= item
->labelText();
161 item
->setLabelTextElided( false );
164 painter
->drawText( textRect
, Qt::AlignLeft
| Qt::TextSingleLine
| Qt::AlignVCenter
, label
);
166 if ( QApplication::isLeftToRight() )
167 textRect
.setLeft( textRect
.left() + labelWidth
+ ITEM_LABEL_TO_UNREADCOUNT_SPACING
);
169 textRect
.setRight( textRect
.right() - labelWidth
- ITEM_LABEL_TO_UNREADCOUNT_SPACING
);
171 if ( !( opt
.state
& QStyle::State_Selected
) ) {
172 painter
->setPen( QPen( mFolderTreeWidget
->unreadCountColor(), 0 ) );
174 painter
->drawText( textRect
, Qt::AlignLeft
| Qt::TextSingleLine
| Qt::AlignVCenter
, unreadText
);
176 // got unread messages: bold font but no special text tricks
177 QString label
= item
->elidedLabelText( fm
, textRect
.width() );
179 // the condition inside this call is an optimization (it's faster than simply label != item->labelText())
180 item
->setLabelTextElided( ( label
.length() != item
->labelText().length() ) || ( label
!= item
->labelText() ) );
182 painter
->drawText( textRect
, Qt::AlignLeft
| Qt::TextSingleLine
| Qt::AlignVCenter
, label
);
185 // no unread messages: normal font, no text tricks
186 QString label
= item
->elidedLabelText( opt
.fontMetrics
, textRect
.width() );
188 // the condition inside this call is an optimization (it's faster than simply label != item->labelText())
189 item
->setLabelTextElided( ( label
.length() != item
->labelText().length() ) || ( label
!= item
->labelText() ) );
191 painter
->setFont( opt
.font
);
192 painter
->drawText( textRect
, Qt::AlignLeft
| Qt::TextSingleLine
| Qt::AlignVCenter
, label
);
195 painter
->setPen( oldPen
);
196 painter
->setFont( oldFont
);
199 virtual void setModelData( QWidget
*editor
, QAbstractItemModel
*model
, const QModelIndex
&index
) const
201 QStyledItemDelegate::setModelData( editor
, model
, index
);
202 FolderTreeWidgetItem
*item
= static_cast<FolderTreeWidgetItem
*>( index
.internalPointer() );
204 // test if old text != new text
205 if( item
->labelText() != item
->text( mFolderTreeWidget
->labelColumnIndex() ) ) {
206 item
->setLabelText( item
->text( mFolderTreeWidget
->labelColumnIndex() ) );
207 mFolderTreeWidget
->emitRenamed( item
);
216 FolderTreeWidget::FolderTreeWidget( QWidget
*parent
, const char *name
)
217 : KPIM::TreeWidget( parent
, name
),
218 mUnreadCountColor( Qt::blue
), mCloseToQuotaWarningColor( Qt::red
)
220 mLabelColumnIndex
= -1;
221 mUnreadColumnIndex
= -1;
222 mTotalColumnIndex
= -1;
223 mDataSizeColumnIndex
= -1;
225 setAlternatingRowColors( true );
226 setAcceptDrops( true );
227 setAllColumnsShowFocus( true );
228 setRootIsDecorated( true );
229 setSortingEnabled( true );
230 setUniformRowHeights( true ); // optimize please
231 //setAnimated( true ); // this slows down everything a lot
233 header()->setSortIndicatorShown( true );
234 header()->setClickable( true );
235 //setSelectionMode( Extended );
237 connect( this, SIGNAL(itemExpanded(QTreeWidgetItem
*)),
238 SLOT(updateExpandedState(QTreeWidgetItem
*)) );
239 connect( this, SIGNAL(itemCollapsed(QTreeWidgetItem
*)),
240 SLOT(updateExpandedState(QTreeWidgetItem
*)) );
244 int FolderTreeWidget::addLabelColumn( const QString
&headerLabel
)
246 if ( mLabelColumnIndex
== -1 )
248 mLabelColumnIndex
= addColumn( headerLabel
);
249 setItemDelegateForColumn( mLabelColumnIndex
, new FolderTreeWidgetItemLabelColumnDelegate( this ) );
251 return mLabelColumnIndex
;
254 bool FolderTreeWidget::labelColumnVisible() const
256 if ( mLabelColumnIndex
== -1 )
258 return !isColumnHidden( mLabelColumnIndex
);
261 int FolderTreeWidget::addTotalColumn( const QString
&headerLabel
)
263 if ( mTotalColumnIndex
== -1 )
264 mTotalColumnIndex
= addColumn( headerLabel
, Qt::AlignRight
);
265 return mTotalColumnIndex
;
268 bool FolderTreeWidget::totalColumnVisible() const
270 if ( mTotalColumnIndex
== -1 )
272 return !isColumnHidden( mTotalColumnIndex
);
275 int FolderTreeWidget::addUnreadColumn( const QString
&headerLabel
)
277 if ( mUnreadColumnIndex
== -1 )
278 mUnreadColumnIndex
= addColumn( headerLabel
, Qt::AlignRight
);
279 return mUnreadColumnIndex
;
282 bool FolderTreeWidget::unreadColumnVisible() const
284 if ( mUnreadColumnIndex
== -1 )
286 return !isColumnHidden( mUnreadColumnIndex
);
289 int FolderTreeWidget::addDataSizeColumn( const QString
&headerLabel
)
291 if ( mDataSizeColumnIndex
== -1 )
292 mDataSizeColumnIndex
= addColumn( headerLabel
, Qt::AlignRight
);
293 return mDataSizeColumnIndex
;
296 bool FolderTreeWidget::dataSizeColumnVisible() const
298 if ( mDataSizeColumnIndex
== -1 )
300 return !isColumnHidden( mDataSizeColumnIndex
);
303 void FolderTreeWidget::updateColumnForItem( FolderTreeWidgetItem
* item
, int columnIndex
)
305 update( indexFromItem( item
, columnIndex
) );
308 void FolderTreeWidget::updateExpandedState( QTreeWidgetItem
*item
)
310 FolderTreeWidgetItem
*folderItem
= dynamic_cast<FolderTreeWidgetItem
*>( item
);
311 Q_ASSERT( folderItem
);
313 folderItem
->updateExpandedState();
316 void FolderTreeWidget::setCloseToQuotaWarningColor( const QColor
&clr
)
318 mCloseToQuotaWarningColor
= clr
;
322 void FolderTreeWidget::setUnreadCountColor( const QColor
&clr
)
324 mUnreadCountColor
= clr
;
328 void FolderTreeWidget::emitRenamed( QTreeWidgetItem
*item
)
330 emit
renamed( item
);
334 FolderTreeWidgetItem::FolderTreeWidgetItem(
335 FolderTreeWidget
*parent
,
336 const QString
&label
,
338 FolderType folderType
339 ) : QTreeWidgetItem( parent
, FOLDERTREEWIDGETITEM_TYPE
),
340 mProtocol( protocol
), mFolderType( folderType
), mLabelText( label
),
341 mTotalCount( 0 ), mUnreadCount( 0 ), mDataSize( -1 ), mIsCloseToQuota( 0 ),
342 mLabelTextElided( 0 ), mChildrenTotalCount( 0 ), mChildrenUnreadCount( 0 ),
343 mChildrenDataSize( -1 ), mAlwaysDisplayCounts( false )
345 setText( parent
->labelColumnIndex(), label
);
348 FolderTreeWidgetItem::FolderTreeWidgetItem(
349 FolderTreeWidgetItem
*parent
,
350 const QString
&label
,
352 FolderType folderType
353 ) : QTreeWidgetItem( parent
, FOLDERTREEWIDGETITEM_TYPE
),
354 mProtocol( protocol
), mFolderType( folderType
), mLabelText( label
),
355 mTotalCount( 0 ), mUnreadCount( 0 ), mDataSize( -1 ), mIsCloseToQuota( 0 ),
356 mLabelTextElided( 0 ), mChildrenTotalCount( 0 ), mChildrenUnreadCount( 0 ),
357 mChildrenDataSize( -1 ), mAlwaysDisplayCounts( false )
359 FolderTreeWidget
* tree
= dynamic_cast< FolderTreeWidget
* >( treeWidget() );
361 setText( tree
->labelColumnIndex(), label
);
364 QString
FolderTreeWidgetItem::protocolDescription() const
369 return i18n( "Local" );
372 return i18n( "IMAP" );
375 return i18n( "Cached IMAP" );
378 return i18n( "News" );
381 return i18n( "Search" );
384 return i18n( "None" );
390 return i18n( "Unknown" );
393 bool FolderTreeWidgetItem::updateChildrenCounts()
395 int cc
= childCount();
403 int oldTotal
= mChildrenTotalCount
;
404 int oldUnread
= mChildrenUnreadCount
;
405 qint64 oldSize
= mChildrenDataSize
;
407 mChildrenTotalCount
= 0;
408 mChildrenUnreadCount
= 0;
409 mChildrenDataSize
= 0;
411 bool gotValidDataSize
= false;
415 FolderTreeWidgetItem
*it
= static_cast< FolderTreeWidgetItem
* >( QTreeWidgetItem::child( idx
) );
416 mChildrenTotalCount
+= it
->totalCount() + it
->childrenTotalCount();
417 mChildrenUnreadCount
+= it
->unreadCount() + it
->childrenUnreadCount();
418 if ( it
->dataSize() >= 0)
420 gotValidDataSize
= true;
421 mChildrenDataSize
+= it
->dataSize();
423 if ( it
->childrenDataSize() >= 0 )
425 gotValidDataSize
= true;
426 mChildrenDataSize
+= it
->childrenDataSize();
431 if ( !gotValidDataSize
)
432 mChildrenDataSize
= -1; // keep it invald
434 return ( oldTotal
!= mChildrenTotalCount
) ||
435 ( oldUnread
!= mChildrenUnreadCount
) ||
436 ( oldSize
!= mChildrenDataSize
);
439 void FolderTreeWidgetItem::setLabelText( const QString
&label
)
442 // We set the text of the item so QStyle will compute the height correctly
443 FolderTreeWidget
* tree
= dynamic_cast< FolderTreeWidget
* >( treeWidget() );
445 int idx
= tree
->labelColumnIndex();
448 setText( idx
, label
);
449 setTextAlignment( idx
, Qt::AlignRight
);
453 void FolderTreeWidgetItem::setUnreadCount( int unreadCount
)
455 mUnreadCount
= unreadCount
;
456 int unreadCountToDisplay
= unreadCount
;
458 if ( mChildrenUnreadCount
> 0 && !isExpanded() )
459 unreadCountToDisplay
+= mChildrenUnreadCount
;
461 FolderTreeWidget
* tree
= dynamic_cast< FolderTreeWidget
* >( treeWidget() );
464 int idx
= tree
->unreadColumnIndex();
467 if ( ( parent() || mAlwaysDisplayCounts
|| !isExpanded() ) && unreadCountToDisplay
> 0 )
468 setText( idx
, QString::number( unreadCountToDisplay
) );
470 setText( idx
, QString() );
472 setTextAlignment( idx
, Qt::AlignRight
);
476 void FolderTreeWidgetItem::setTotalCount( int totalCount
)
478 mTotalCount
= totalCount
;
479 int totalCountToDisplay
= totalCount
;
481 if ( mChildrenTotalCount
> 0 && !isExpanded() )
482 totalCountToDisplay
+= mChildrenTotalCount
;
484 FolderTreeWidget
* tree
= dynamic_cast< FolderTreeWidget
* >( treeWidget() );
486 int idx
= tree
->totalColumnIndex();
489 // FIXME: Why the "parent()" logic is hardwired here ?
490 if ( parent() || mAlwaysDisplayCounts
|| !isExpanded() )
491 setText( idx
, QString::number( totalCountToDisplay
) );
493 setText( idx
, QString() );
494 setTextAlignment( idx
, Qt::AlignRight
);
499 void FolderTreeWidgetItem::setDataSize( qint64 dataSize
)
501 mDataSize
= dataSize
;
504 QString sizeText
= KIO::convertSize( mDataSize
>= 0 ? (KIO::filesize_t
)mDataSize
: (KIO::filesize_t
)0 );
505 QString childSizeText
= KIO::convertSize( (KIO::filesize_t
)mChildrenDataSize
);
507 // A top level item, they all have size 0
508 // FIXME: Why this logic is hardwired here ?
509 if ( !parent() && !mAlwaysDisplayCounts
) {
510 if ( mChildrenDataSize
>= 0 && !isExpanded() )
516 // Not a top level item (or always displays counts)
517 else if ( ( mDataSize
>= 0 ) || ( mChildrenDataSize
>= 0 ) ) {
519 if ( !isExpanded() ) {
520 if ( mChildrenDataSize
>= 0 )
521 txt
+= " + " + childSizeText
;
527 FolderTreeWidget
* tree
= dynamic_cast< FolderTreeWidget
* >( treeWidget() );
529 int idx
= tree
->dataSizeColumnIndex();
533 setTextAlignment( idx
, Qt::AlignRight
);
538 void FolderTreeWidgetItem::updateExpandedState()
540 // Just trigger an update of the column text, it will all be handled thaere.
541 setDataSize( mDataSize
);
542 setTotalCount( mTotalCount
);
543 setUnreadCount( mUnreadCount
);
546 void FolderTreeWidgetItem::updateColumn( int columnIndex
)
548 FolderTreeWidget
* tree
= dynamic_cast< FolderTreeWidget
* >( treeWidget() );
550 tree
->updateColumnForItem( this, columnIndex
);
551 if ( columnIndex
!=tree
->labelColumnIndex() && !tree
->unreadColumnVisible() && tree
->labelColumnVisible() ) {
552 updateColumn( tree
->labelColumnIndex() );
557 void FolderTreeWidgetItem::setIsCloseToQuota( bool closeToQuota
)
559 if ( ( mIsCloseToQuota
== 1 ) == closeToQuota
)
561 mIsCloseToQuota
= closeToQuota
? 1 : 0;
563 FolderTreeWidget
* tree
= dynamic_cast< FolderTreeWidget
* >( treeWidget() );
564 if ( tree
&& tree
->labelColumnVisible() )
565 updateColumn( tree
->labelColumnIndex() );
569 bool FolderTreeWidgetItem::operator < ( const QTreeWidgetItem
&other
) const
571 // FIXME: Sort by children counts too ?
573 int sortCol
= treeWidget()->sortColumn();
575 return true; // just "yes" :D
577 FolderTreeWidget
* w
= dynamic_cast< FolderTreeWidget
* >( treeWidget() );
581 if ( dynamic_cast<const FolderTreeWidgetItem
*>( &other
) )
583 const FolderTreeWidgetItem
* oitem
= dynamic_cast< const FolderTreeWidgetItem
* >( &other
);
586 if ( sortCol
== w
->unreadColumnIndex() )
587 return mUnreadCount
< oitem
->unreadCount();
588 if ( sortCol
== w
->totalColumnIndex() )
589 return mTotalCount
< oitem
->totalCount();
590 if ( sortCol
== w
->dataSizeColumnIndex() )
591 return mDataSize
< oitem
->dataSize();
592 if ( sortCol
== w
->labelColumnIndex() )
594 // Special sorting based on the item type
595 int thisProto
= ( int ) mProtocol
;
596 int thatProto
= ( int ) oitem
->protocol();
597 if ( thisProto
< thatProto
)
599 if ( thisProto
> thatProto
)
602 int thisType
= ( int ) mFolderType
;
603 int thatType
= ( int ) oitem
->folderType();
604 if ( thisType
< thatType
)
606 if ( thisType
> thatType
)
609 // and finally compare by name
610 return mLabelText
.toLower() < oitem
->labelText().toLower();
616 return text(sortCol
) < other
.text(sortCol
);
619 QString
FolderTreeWidgetItem::elidedLabelText( const QFontMetrics
&metrics
, unsigned int width
) const
621 return metrics
.elidedText( labelText(), Qt::ElideRight
, width
);