1 /* -*- mode: C++; c-file-style: "gnu" -*-
2 This file is part of KMail, the KDE mail client.
3 Copyright (c) 2009 Montel Laurent <montel@kde.org>
5 KMail is free software; you can redistribute it and/or modify it
6 under the terms of the GNU General Public License, version 2, as
7 published by the Free Software Foundation.
9 KMail is distributed in the hope that it will be useful, but
10 WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 General Public License for more details.
14 You should have received a copy of the GNU General Public License along
15 with this program; if not, write to the Free Software Foundation, Inc.,
16 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 #include "foldertreeview.h"
20 #include "mailkernel.h"
24 #include <akonadi/entitytreemodel.h>
25 #include <akonadi/collectionstatistics.h>
26 #include <akonadi/collectionstatisticsdelegate.h>
27 #include <KMessageBox>
30 #include <KConfigGroup>
32 #include <QHeaderView>
33 #include <QMouseEvent>
35 namespace MailCommon
{
37 FolderTreeView::FolderTreeView( QWidget
* parent
, bool showUnreadCount
)
38 : Akonadi::EntityTreeView( parent
),
39 mbDisableContextMenuAndExtraColumn( false )
41 init(showUnreadCount
);
45 FolderTreeView::FolderTreeView( KXMLGUIClient
* xmlGuiClient
, QWidget
* parent
, bool showUnreadCount
)
46 :Akonadi::EntityTreeView( xmlGuiClient
, parent
), mbDisableContextMenuAndExtraColumn( false )
48 init(showUnreadCount
);
52 FolderTreeView::~FolderTreeView()
57 void FolderTreeView::setTooltipsPolicy( FolderTreeWidget::ToolTipDisplayPolicy policy
)
59 mToolTipDisplayPolicy
= policy
;
63 void FolderTreeView::disableContextMenuAndExtraColumn()
65 mbDisableContextMenuAndExtraColumn
= true;
66 const int nbColumn
= header()->count();
67 for ( int i
= 1; i
<nbColumn
; ++i
)
69 setColumnHidden( i
, true );
73 void FolderTreeView::init( bool showUnreadCount
)
75 setIconSize( QSize( 22, 22 ) );
76 mSortingPolicy
= FolderTreeWidget::SortByCurrentColumn
;
77 mToolTipDisplayPolicy
= FolderTreeWidget::DisplayAlways
;
79 header()->setContextMenuPolicy( Qt::CustomContextMenu
);
80 connect( header(), SIGNAL(customContextMenuRequested(QPoint
)),
81 SLOT(slotHeaderContextMenuRequested(QPoint
)) );
84 mCollectionStatisticsDelegate
= new Akonadi::CollectionStatisticsDelegate( this );
85 mCollectionStatisticsDelegate
->setProgressAnimationEnabled( true );
86 setItemDelegate(mCollectionStatisticsDelegate
);
87 mCollectionStatisticsDelegate
->setUnreadCountShown( showUnreadCount
&& !header()->isSectionHidden( 1 ) );
91 void FolderTreeView::showStatisticAnimation( bool anim
)
93 mCollectionStatisticsDelegate
->setProgressAnimationEnabled( anim
);
96 void FolderTreeView::writeConfig()
98 KConfigGroup
myGroup( KernelIf
->config(), "MainFolderView");
99 myGroup
.writeEntry( "IconSize", iconSize().width() );
100 myGroup
.writeEntry( "ToolTipDisplayPolicy", ( int ) mToolTipDisplayPolicy
);
101 myGroup
.writeEntry( "SortingPolicy", ( int ) mSortingPolicy
);
104 void FolderTreeView::readConfig()
106 KConfigGroup
myGroup( KernelIf
->config(), "MainFolderView" );
107 int iIconSize
= myGroup
.readEntry( "IconSize", iconSize().width() );
108 if ( iIconSize
< 16 || iIconSize
> 32 )
110 setIconSize( QSize( iIconSize
, iIconSize
) );
111 setTooltipsPolicy( static_cast<FolderTreeWidget::ToolTipDisplayPolicy
>( myGroup
.readEntry( "ToolTipDisplayPolicy", static_cast<int>( FolderTreeWidget::DisplayAlways
) ) ) );
112 setSortingPolicy( ( FolderTreeWidget::SortingPolicy
)myGroup
.readEntry( "SortingPolicy", ( int ) mSortingPolicy
) );
115 void FolderTreeView::slotHeaderContextMenuRequested( const QPoint
&pnt
)
117 if ( mbDisableContextMenuAndExtraColumn
) {
122 // the menu for the columns
125 menu
.addTitle( i18n("View Columns") );
126 const int nbColumn
= header()->count();
127 for ( int i
= 1; i
<nbColumn
; ++i
)
129 act
= menu
.addAction( model()->headerData( i
, Qt::Horizontal
).toString() );
130 act
->setCheckable( true );
131 act
->setChecked( !header()->isSectionHidden( i
) );
132 act
->setData( QVariant( i
) );
134 act
->setEnabled( false );
135 connect( act
, SIGNAL(triggered(bool)),
136 SLOT(slotHeaderContextMenuChangeHeader(bool)) );
140 menu
.addTitle( i18n( "Icon Size" ) );
142 static int icon_sizes
[] = { 16, 22, 32 /*, 48, 64, 128 */ };
144 QActionGroup
*grp
= new QActionGroup( &menu
);
146 for ( int i
= 0; i
< (int)( sizeof( icon_sizes
) / sizeof( int ) ); i
++ )
148 act
= menu
.addAction( QString("%1x%2").arg( icon_sizes
[ i
] ).arg( icon_sizes
[ i
] ) );
149 act
->setCheckable( true );
150 grp
->addAction( act
);
151 if ( iconSize().width() == icon_sizes
[ i
] )
152 act
->setChecked( true );
153 act
->setData( QVariant( icon_sizes
[ i
] ) );
155 connect( act
, SIGNAL(triggered(bool)),
156 SLOT(slotHeaderContextMenuChangeIconSize(bool)) );
158 menu
.addTitle( i18n( "Display Tooltips" ) );
160 grp
= new QActionGroup( &menu
);
162 act
= menu
.addAction( i18nc("@action:inmenu Always display tooltips", "Always") );
163 act
->setCheckable( true );
164 grp
->addAction( act
);
165 act
->setChecked( mToolTipDisplayPolicy
== FolderTreeWidget::DisplayAlways
);
166 act
->setData( QVariant( (int)FolderTreeWidget::DisplayAlways
) );
167 connect( act
, SIGNAL(triggered(bool)),
168 SLOT(slotHeaderContextMenuChangeToolTipDisplayPolicy(bool)) );
169 act
= menu
.addAction( i18nc("@action:inmenu", "When Text Obscured") );
170 act
->setCheckable( true );
173 act
->setEnabled( false );
174 grp
->addAction( act
);
175 act
->setChecked( mToolTipDisplayPolicy
== FolderTreeWidget::DisplayWhenTextElided
);
176 act
->setData( QVariant( (int)FolderTreeWidget::DisplayWhenTextElided
) );
177 connect( act
, SIGNAL(triggered(bool)),
178 SLOT(slotHeaderContextMenuChangeToolTipDisplayPolicy(bool)) );
180 act
= menu
.addAction( i18nc("@action:inmenu Never display tooltips.", "Never") );
181 act
->setCheckable( true );
182 grp
->addAction( act
);
183 act
->setChecked( mToolTipDisplayPolicy
== FolderTreeWidget::DisplayNever
);
184 act
->setData( QVariant( (int)FolderTreeWidget::DisplayNever
) );
185 connect( act
, SIGNAL(triggered(bool)),
186 SLOT(slotHeaderContextMenuChangeToolTipDisplayPolicy(bool)) );
188 menu
.addTitle( i18nc("@action:inmenu", "Sort Items" ) );
190 grp
= new QActionGroup( &menu
);
192 act
= menu
.addAction( i18nc("@action:inmenu", "Automatically, by Current Column") );
193 act
->setCheckable( true );
194 grp
->addAction( act
);
195 act
->setChecked( mSortingPolicy
== FolderTreeWidget::SortByCurrentColumn
);
196 act
->setData( QVariant( (int)FolderTreeWidget::SortByCurrentColumn
) );
197 connect( act
, SIGNAL(triggered(bool)),
198 SLOT(slotHeaderContextMenuChangeSortingPolicy(bool)) );
200 act
= menu
.addAction( i18nc("@action:inmenu", "Manually, by Drag And Drop") );
201 act
->setCheckable( true );
202 grp
->addAction( act
);
203 act
->setChecked( mSortingPolicy
== FolderTreeWidget::SortByDragAndDropKey
);
204 act
->setData( QVariant( (int)FolderTreeWidget::SortByDragAndDropKey
) );
205 connect( act
, SIGNAL(triggered(bool)),
206 SLOT(slotHeaderContextMenuChangeSortingPolicy(bool)) );
208 menu
.exec( header()->mapToGlobal( pnt
) );
211 void FolderTreeView::slotHeaderContextMenuChangeSortingPolicy( bool )
213 QAction
*act
= dynamic_cast< QAction
* >( sender() );
217 QVariant data
= act
->data();
220 int policy
= data
.toInt( &ok
);
224 setSortingPolicy( ( FolderTreeWidget::SortingPolicy
)policy
);
228 void FolderTreeView::setSortingPolicy( FolderTreeWidget::SortingPolicy policy
)
230 mSortingPolicy
= policy
;
231 switch ( mSortingPolicy
)
233 case FolderTreeWidget::SortByCurrentColumn
:
234 header()->setClickable( true );
235 header()->setSortIndicatorShown( true );
236 setSortingEnabled( true );
237 emit
manualSortingChanged( false );
239 case FolderTreeWidget::SortByDragAndDropKey
:
240 header()->setClickable( false );
241 header()->setSortIndicatorShown( false );
244 // Qt 4.5 introduced a nasty bug here:
245 // Sorting must be enabled in order to sortByColumn() to work.
246 // If sorting is disabled it disconnects some internal signal/slot pairs
247 // and calling sortByColumn() silently has no effect.
248 // This is a bug as we actually DON'T want automatic sorting to be
249 // performed by the view whenever it wants. We want to control sorting.
251 setSortingEnabled( true ); // hack for qutie bug: the param here should be false
252 sortByColumn( 0, Qt::AscendingOrder
);
254 setSortingEnabled( false ); // hack for qutie bug: this call shouldn't be here at all
255 emit
manualSortingChanged( true );
259 // should never happen
265 void FolderTreeView::slotHeaderContextMenuChangeToolTipDisplayPolicy( bool )
267 QAction
*act
= dynamic_cast< QAction
* >( sender() );
271 QVariant data
= act
->data();
274 const int id
= data
.toInt( &ok
);
277 emit
changeTooltipsPolicy( ( FolderTreeWidget::ToolTipDisplayPolicy
)id
);
280 void FolderTreeView::slotHeaderContextMenuChangeHeader( bool )
282 QAction
*act
= dynamic_cast< QAction
* >( sender() );
286 QVariant data
= act
->data();
289 const int id
= data
.toInt( &ok
);
293 if ( id
> header()->count() )
297 mCollectionStatisticsDelegate
->setUnreadCountShown(!act
->isChecked());
299 setColumnHidden( id
, !act
->isChecked() );
302 void FolderTreeView::slotHeaderContextMenuChangeIconSize( bool )
304 QAction
*act
= dynamic_cast< QAction
* >( sender() );
308 QVariant data
= act
->data();
311 const int size
= data
.toInt( &ok
);
315 setIconSize( QSize( size
, size
) );
319 void FolderTreeView::setCurrentModelIndex( const QModelIndex
& index
)
321 if ( index
.isValid() ) {
324 selectionModel()->setCurrentIndex( index
, QItemSelectionModel::Rows
);
328 void FolderTreeView::selectModelIndex( const QModelIndex
& index
)
330 if ( index
.isValid() ) {
332 selectionModel()->select( index
, QItemSelectionModel::Rows
| QItemSelectionModel::Select
|
333 QItemSelectionModel::Current
| QItemSelectionModel::Clear
);
337 void FolderTreeView::slotSelectFocusFolder()
339 const QModelIndex index
= currentIndex();
340 if( index
.isValid() )
341 setCurrentIndex( index
);
344 void FolderTreeView::slotFocusNextFolder()
346 const QModelIndex nextFolder
= selectNextFolder( currentIndex() );
348 if ( nextFolder
.isValid() ) {
349 expand( nextFolder
);
350 setCurrentModelIndex( nextFolder
);
354 QModelIndex
FolderTreeView::selectNextFolder( const QModelIndex
& current
)
357 if ( current
.isValid() ) {
358 model()->fetchMore( current
);
359 if ( model()->hasChildren( current
) ) {
361 below
= indexBelow( current
);
362 } else if ( current
.row() < model()->rowCount( model()->parent( current
) ) -1 ) {
363 below
= model()->index( current
.row()+1, current
.column(), model()->parent( current
) );
365 below
= indexBelow( current
);
371 void FolderTreeView::slotFocusPrevFolder()
373 const QModelIndex current
= currentIndex();
374 if ( current
.isValid() ) {
375 QModelIndex above
= indexAbove( current
);
376 setCurrentModelIndex( above
);
380 void FolderTreeView::selectNextUnreadFolder( bool confirm
)
382 // find next unread collection starting from current position
383 if ( !trySelectNextUnreadFolder( currentIndex(), MailCommon::Util::ForwardSearch
, confirm
) ) {
384 // if there is none, jump to the last collection and try again
385 trySelectNextUnreadFolder( model()->index( 0, 0 ), MailCommon::Util::ForwardSearch
, confirm
);
389 // helper method to find last item in the model tree
390 static QModelIndex
lastChildOf( QAbstractItemModel
*model
, const QModelIndex
¤t
)
392 if ( model
->rowCount( current
) == 0 )
395 return lastChildOf( model
, model
->index( model
->rowCount( current
) - 1, 0, current
) );
398 void FolderTreeView::selectPrevUnreadFolder( bool confirm
)
400 // find next unread collection starting from current position
401 if ( !trySelectNextUnreadFolder( currentIndex(), MailCommon::Util::BackwardSearch
, confirm
) ) {
402 // if there is none, jump to top and try again
403 const QModelIndex index
= lastChildOf( model(), QModelIndex() );
404 trySelectNextUnreadFolder( index
, MailCommon::Util::BackwardSearch
, confirm
);
408 bool FolderTreeView::trySelectNextUnreadFolder( const QModelIndex
¤t
, MailCommon::Util::SearchDirection direction
, bool confirm
)
410 QModelIndex index
= current
;
412 index
= MailCommon::Util::nextUnreadCollection( model(), index
, direction
);
414 if ( !index
.isValid() )
417 const Akonadi::Collection collection
= index
.data( Akonadi::EntityTreeModel::CollectionRole
).value
<Akonadi::Collection
>();
418 if ( ignoreUnreadFolder( collection
, confirm
) )
421 if ( allowedToEnterFolder( collection
, confirm
) ) {
423 setCurrentIndex( index
);
424 selectModelIndex( index
);
434 bool FolderTreeView::ignoreUnreadFolder( const Akonadi::Collection
&collection
, bool confirm
) const
439 // Skip drafts, sent mail and templates as well, when reading mail with the
440 // space bar - but not when changing into the next folder with unread mail
441 // via ctrl+ or ctrl- so we do this only if (confirm == true), which means
442 // we are doing readOn.
444 return ( collection
== Kernel::self()->draftsCollectionFolder() ||
445 collection
== Kernel::self()->templatesCollectionFolder() ||
446 collection
== Kernel::self()->sentCollectionFolder() );
449 bool FolderTreeView::allowedToEnterFolder( const Akonadi::Collection
&collection
, bool confirm
) const
454 // warn user that going to next folder - but keep track of
455 // whether he wishes to be notified again in "AskNextFolder"
456 // parameter (kept in the config file for kmail)
457 const int result
= KMessageBox::questionYesNo( const_cast<FolderTreeView
*>( this ),
458 i18n( "<qt>Go to the next unread message in folder <b>%1</b>?</qt>" , collection
.name() ),
459 i18n( "Go to Next Unread Message" ),
460 KGuiItem( i18n( "Go To" ) ),
461 KGuiItem( i18n( "Do Not Go To" ) ), // defaults
462 ":kmail_AskNextFolder", 0 );
463 return (result
== KMessageBox::Yes
);
466 bool FolderTreeView::isUnreadFolder( const QModelIndex
¤t
, QModelIndex
&index
, FolderTreeView::Move move
, bool confirm
)
468 if ( current
.isValid() ) {
470 if ( move
== FolderTreeView::Next
)
471 index
= selectNextFolder( current
);
472 else if ( move
== FolderTreeView::Previous
)
473 index
= indexAbove( current
);
475 if ( index
.isValid() ) {
476 const Akonadi::Collection collection
= index
.model()->data( current
, Akonadi::EntityTreeModel::CollectionRole
).value
<Akonadi::Collection
>();
477 if ( collection
.isValid() ) {
478 if ( collection
.statistics().unreadCount() > 0 ) {
480 selectModelIndex( current
);
483 // Skip drafts, sent mail and templates as well, when reading mail with the
484 // space bar - but not when changing into the next folder with unread mail
485 // via ctrl+ or ctrl- so we do this only if (confirm == true), which means
486 // we are doing readOn.
488 if ( collection
== Kernel::self()->draftsCollectionFolder() ||
489 collection
== Kernel::self()->templatesCollectionFolder() ||
490 collection
== Kernel::self()->sentCollectionFolder() )
493 // warn user that going to next folder - but keep track of
494 // whether he wishes to be notified again in "AskNextFolder"
495 // parameter (kept in the config file for kmail)
496 if (KMessageBox::questionYesNo(
498 i18n( "<qt>Go to the next unread message in folder <b>%1</b>?</qt>" , collection
.name() ),
499 i18n( "Go to Next Unread Message" ),
500 KGuiItem( i18n( "Go To" ) ),
501 KGuiItem( i18n( "Do Not Go To" ) ), // defaults
502 ":kmail_AskNextFolder",
506 return true; // assume selected (do not continue looping)
508 selectModelIndex( current
);
518 Akonadi::Collection
FolderTreeView::currentFolder() const
520 const QModelIndex current
= currentIndex();
521 if ( current
.isValid() ) {
522 const Akonadi::Collection collection
= current
.model()->data( current
, Akonadi::EntityTreeModel::CollectionRole
).value
<Akonadi::Collection
>();
525 return Akonadi::Collection();
528 void FolderTreeView::mousePressEvent( QMouseEvent
* e
)
530 const bool buttonPressedIsMiddle
= ( e
->button() == Qt::MidButton
);
531 emit
prefereCreateNewTab( buttonPressedIsMiddle
);
532 EntityTreeView::mousePressEvent( e
);
535 void FolderTreeView::restoreHeaderState( const QByteArray
& data
)
537 header()->restoreState( data
);
538 mCollectionStatisticsDelegate
->setUnreadCountShown( header()->isSectionHidden( 1 ) );
544 #include "foldertreeview.moc"