Allow specializations to decide when re-indexing is necessary.
[kdepim.git] / messagelist / pane.cpp
blobecb6acd26bd2407d2f92edc62b6dd14b7b64441b
1 /*
2 Copyright (c) 2009 Kevin Ottens <ervin@kde.org>
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 #include "pane.h"
21 #include <KDE/KActionCollection>
22 #include <KDE/KActionMenu>
23 #include <KDE/KIcon>
24 #include <KDE/KLocale>
25 #include <KDE/KMenu>
26 #include <KDE/KXMLGUIClient>
28 #include <QtCore/QAbstractItemModel>
29 #include <QtGui/QAbstractProxyModel>
30 #include <QtGui/QItemSelectionModel>
31 #include <QtGui/QTabBar>
32 #include <QtGui/QToolButton>
34 #include "storagemodel.h"
35 #include "widget.h"
36 #include "core/settings.h"
37 #include "core/manager.h"
38 #include "messagecore/messagestatus.h"
39 #include "core/model.h"
41 namespace MessageList
44 class Pane::Private
46 public:
47 Private( Pane *owner )
48 : q( owner ), mXmlGuiClient( 0 ), mActionMenu( 0 ) { }
50 void onSelectionChanged( const QItemSelection &selected, const QItemSelection &deselected );
51 void onNewTabClicked();
52 void onCloseTabClicked();
53 void onCurrentTabChanged();
54 void onTabContextMenuRequest( const QPoint &pos );
56 QItemSelection mapSelectionToSource( const QItemSelection &selection ) const;
57 QItemSelection mapSelectionFromSource( const QItemSelection &selection ) const;
58 void updateTabControls();
60 Pane * const q;
62 KXMLGUIClient *mXmlGuiClient;
63 KActionMenu *mActionMenu;
65 QAbstractItemModel *mModel;
66 QItemSelectionModel *mSelectionModel;
68 QHash<Widget*, QItemSelectionModel*> mWidgetSelectionHash;
69 QList<const QAbstractProxyModel*> mProxyStack;
71 QToolButton *mNewTabButton;
72 QToolButton *mCloseTabButton;
76 } // namespace MessageList
78 using namespace Akonadi;
79 using namespace MessageList;
82 Pane::Pane( QAbstractItemModel *model, QItemSelectionModel *selectionModel, QWidget *parent )
83 : QTabWidget( parent ), d( new Private( this ) )
85 d->mModel = model;
86 d->mSelectionModel = selectionModel;
88 // Build the proxy stack
89 const QAbstractProxyModel *proxyModel = qobject_cast<const QAbstractProxyModel*>( d->mSelectionModel->model() );
91 while (proxyModel) {
92 if (static_cast<const QAbstractItemModel*>(proxyModel) == d->mModel) {
93 break;
96 d->mProxyStack << proxyModel;
97 const QAbstractProxyModel *nextProxyModel = qobject_cast<const QAbstractProxyModel*>(proxyModel->sourceModel());
99 if (!nextProxyModel) {
100 // It's the final model in the chain, so it is necessarily the sourceModel.
101 Q_ASSERT(qobject_cast<const QAbstractItemModel*>(proxyModel->sourceModel()) == d->mModel);
102 break;
104 proxyModel = nextProxyModel;
105 } // Proxy stack done
107 d->mNewTabButton = new QToolButton( this );
108 d->mNewTabButton->setIcon( KIcon( "tab-new" ) );
109 d->mNewTabButton->adjustSize();
110 d->mNewTabButton->setToolTip( i18nc("@info:tooltip", "Open a new tab"));
111 setCornerWidget( d->mNewTabButton, Qt::TopLeftCorner );
112 connect( d->mNewTabButton, SIGNAL( clicked() ),
113 SLOT( onNewTabClicked() ) );
115 d->mCloseTabButton = new QToolButton( this );
116 d->mCloseTabButton->setIcon( KIcon( "tab-close" ) );
117 d->mCloseTabButton->adjustSize();
118 d->mCloseTabButton->setToolTip( i18nc("@info:tooltip", "Close the current tab"));
119 setCornerWidget( d->mCloseTabButton, Qt::TopRightCorner );
120 connect( d->mCloseTabButton, SIGNAL( clicked() ),
121 SLOT( onCloseTabClicked() ) );
123 createNewTab();
124 setMovable( true );
126 connect( d->mSelectionModel, SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
127 this, SLOT(onSelectionChanged(QItemSelection, QItemSelection)) );
128 connect( this, SIGNAL(currentChanged(int)),
129 this, SLOT(onCurrentTabChanged()) );
131 setContextMenuPolicy( Qt::CustomContextMenu );
132 connect( this, SIGNAL(customContextMenuRequested(QPoint)),
133 this, SLOT(onTabContextMenuRequest(QPoint)) );
135 connect( Core::Settings::self(), SIGNAL(configChanged()),
136 this, SLOT(updateTabControls()) );
139 Pane::~Pane()
141 delete d;
144 void Pane::setXmlGuiClient( KXMLGUIClient *xmlGuiClient )
146 d->mXmlGuiClient = xmlGuiClient;
148 for ( int i=0; i<count(); i++ ) {
149 Widget *w = qobject_cast<Widget *>( widget( i ) );
150 w->setXmlGuiClient( d->mXmlGuiClient );
153 // Setup "View->Message List" actions.
154 if ( xmlGuiClient ) {
155 if ( d->mActionMenu ) {
156 d->mXmlGuiClient->actionCollection()->removeAction( d->mActionMenu );
158 d->mActionMenu = new KActionMenu( KIcon(), i18n( "Message List" ), this );
159 d->mXmlGuiClient->actionCollection()->addAction( "view_message_list", d->mActionMenu );
160 const Widget * const w = static_cast<Widget*>( currentWidget() );
161 w->view()->fillViewMenu( d->mActionMenu->menu() );
165 bool Pane::selectNextMessageItem( MessageList::Core::MessageTypeFilter messageTypeFilter,
166 MessageList::Core::ExistingSelectionBehaviour existingSelectionBehaviour,
167 bool centerItem,
168 bool loop )
170 Widget *w = static_cast<Widget*>( currentWidget() );
172 if ( w ) {
173 if ( w->view()->model()->isLoading() )
174 return true;
176 return w->selectNextMessageItem( messageTypeFilter, existingSelectionBehaviour, centerItem, loop );
177 } else {
178 return false;
182 bool Pane::selectPreviousMessageItem( MessageList::Core::MessageTypeFilter messageTypeFilter,
183 MessageList::Core::ExistingSelectionBehaviour existingSelectionBehaviour,
184 bool centerItem,
185 bool loop )
187 Widget *w = static_cast<Widget*>( currentWidget() );
189 if ( w ) {
190 if ( w->view()->model()->isLoading() )
191 return true;
193 return w->selectPreviousMessageItem( messageTypeFilter, existingSelectionBehaviour, centerItem, loop );
194 } else {
195 return false;
199 bool Pane::focusNextMessageItem( MessageList::Core::MessageTypeFilter messageTypeFilter, bool centerItem, bool loop )
201 Widget *w = static_cast<Widget*>( currentWidget() );
203 if ( w ) {
204 if ( w->view()->model()->isLoading() )
205 return true;
207 return w->focusNextMessageItem( messageTypeFilter, centerItem, loop );
208 } else {
209 return false;
213 bool Pane::focusPreviousMessageItem( MessageList::Core::MessageTypeFilter messageTypeFilter, bool centerItem, bool loop )
215 Widget *w = static_cast<Widget*>( currentWidget() );
217 if ( w ) {
218 if ( w->view()->model()->isLoading() )
219 return true;
221 return w->focusNextMessageItem( messageTypeFilter, centerItem, loop );
222 } else {
223 return false;
227 void Pane::selectFocusedMessageItem( bool centerItem )
229 Widget *w = static_cast<Widget*>( currentWidget() );
231 if ( w ) {
232 if ( w->view()->model()->isLoading() )
233 return;
235 w->selectFocusedMessageItem( centerItem );
239 bool Pane::selectFirstMessageItem( MessageList::Core::MessageTypeFilter messageTypeFilter, bool centerItem )
241 Widget *w = static_cast<Widget*>( currentWidget() );
243 if ( w ) {
244 if ( w->view()->model()->isLoading() )
245 return true;
247 return w->selectFirstMessageItem( messageTypeFilter, centerItem );
248 } else {
249 return false;
253 void Pane::selectAll()
255 Widget *w = static_cast<Widget*>( currentWidget() );
257 if ( w ) {
258 if ( w->view()->model()->isLoading() )
259 return;
261 w->selectAll();
266 void Pane::setCurrentThreadExpanded( bool expand )
268 Widget *w = static_cast<Widget*>( currentWidget() );
270 if ( w ) {
271 if ( w->view()->model()->isLoading() )
272 return;
274 w->setCurrentThreadExpanded(expand );
278 void Pane::setAllThreadsExpanded( bool expand )
280 Widget *w = static_cast<Widget*>( currentWidget() );
282 if ( w ) {
283 if ( w->view()->model()->isLoading() )
284 return;
286 w->setAllThreadsExpanded( expand );
290 void Pane::setAllGroupsExpanded( bool expand )
292 Widget *w = static_cast<Widget*>( currentWidget() );
294 if ( w ) {
295 if ( w->view()->model()->isLoading() )
296 return;
298 w->setAllGroupsExpanded(expand);
302 void Pane::focusQuickSearch()
304 Widget *w = static_cast<Widget*>( currentWidget() );
306 if ( w ) {
307 w->focusQuickSearch();
311 void Pane::Private::onSelectionChanged( const QItemSelection &selected, const QItemSelection &deselected )
313 Widget *w = static_cast<Widget*>( q->currentWidget() );
314 QItemSelectionModel *s = mWidgetSelectionHash[w];
316 s->select( mapSelectionToSource( selected ), QItemSelectionModel::Select );
317 s->select( mapSelectionToSource( deselected ), QItemSelectionModel::Deselect );
319 QString label;
320 QIcon icon = KIcon( "folder" );
321 foreach ( const QModelIndex &index, s->selectedRows() ) {
322 label+= index.data( Qt::DisplayRole ).toString()+", ";
324 label.chop( 2 );
326 if ( label.isEmpty() ) {
327 label = i18nc( "@title:tab Empty messagelist", "Empty" );
328 icon = QIcon();
329 } else if ( s->selectedRows().size()==1 ) {
330 icon = s->selectedRows().first().data( Qt::DecorationRole ).value<QIcon>();
333 int index = q->indexOf( w );
334 q->setTabText( index, label );
335 q->setTabIcon( index, icon );
338 void Pane::Private::onNewTabClicked()
340 q->createNewTab();
341 updateTabControls();
344 void Pane::Private::onCloseTabClicked()
346 Widget *w = static_cast<Widget*>( q->currentWidget() );
347 if ( !w || (q->count() < 2) ) {
348 return;
351 delete w;
352 updateTabControls();
355 void Pane::Private::onCurrentTabChanged()
357 Widget *w = static_cast<Widget*>( q->currentWidget() );
358 QItemSelectionModel *s = mWidgetSelectionHash[w];
360 disconnect( mSelectionModel, SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
361 q, SLOT(onSelectionChanged(QItemSelection, QItemSelection)) );
363 mSelectionModel->select( mapSelectionFromSource( s->selection() ),
364 QItemSelectionModel::ClearAndSelect );
366 connect( mSelectionModel, SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
367 q, SLOT(onSelectionChanged(QItemSelection, QItemSelection)) );
370 void Pane::Private::onTabContextMenuRequest( const QPoint &pos )
372 QTabBar *bar = q->tabBar();
373 int index = bar->tabAt( bar->mapFrom( q, pos ) );
374 if ( index == -1 ) return;
376 Widget *w = qobject_cast<Widget *>( q->widget( index ) );
377 if ( !w ) return;
379 KMenu menu( q );
380 QAction *action;
382 action = menu.addAction( i18nc( "@action:inmenu", "Close Tab" ) );
383 action->setEnabled( q->count() > 1 );
384 action->setIcon( KIcon( "tab-close" ) );
385 connect( action, SIGNAL(triggered(bool)),
386 q, SLOT(onCloseTabClicked()) ); // Reuse the logic...
388 QAction *allOther = menu.addAction( i18nc("@action:inmenu", "Close All Other Tabs" ) );
389 allOther->setEnabled( q->count() > 1 );
390 allOther->setIcon( KIcon( "tab-close-other" ) );
392 action = menu.exec( q->mapToGlobal( pos ) );
394 if ( action == allOther ) { // Close all other tabs
395 QList<Widget *> widgets;
396 int index = q->indexOf( w );
398 for ( int i=0; i<q->count(); i++ ) {
399 if ( i==index) continue; // Skip the current one
401 Widget *other = qobject_cast<Widget *>( q->widget( i ) );
402 widgets << other;
405 foreach ( Widget *other, widgets ) {
406 delete other;
409 updateTabControls();
413 void Pane::setCurrentFolder( const Akonadi::Collection &col, bool preferEmptyTab, Core::PreSelectionMode preSelectionMode, const QString &overrideLabel )
415 Widget *w = static_cast<Widget*>( currentWidget() );
417 if ( w ) {
418 QItemSelectionModel *s = d->mWidgetSelectionHash[w];
419 MessageList::StorageModel *m = new MessageList::StorageModel( d->mModel, s, w );
420 w->setStorageModel( m, preSelectionMode );
421 if ( !overrideLabel.isEmpty() ) {
422 int index = indexOf( w );
423 setTabText( index, overrideLabel );
428 void Pane::createNewTab()
430 Widget * w = new Widget( this );
431 w->setXmlGuiClient( d->mXmlGuiClient );
432 addTab( w, i18nc( "@title:tab Empty messagelist", "Empty" ) );
434 QItemSelectionModel *s = new QItemSelectionModel( d->mModel, w );
435 MessageList::StorageModel *m = new MessageList::StorageModel( d->mModel, s, w );
436 w->setStorageModel( m );
438 d->mWidgetSelectionHash[w] = s;
440 connect( w, SIGNAL(messageSelected(Akonadi::Item)),
441 this, SIGNAL(messageSelected(Akonadi::Item)) );
442 connect( w, SIGNAL(messageActivated(Akonadi::Item)),
443 this, SIGNAL(messageActivated(Akonadi::Item)) );
444 connect( w, SIGNAL(selectionChanged()),
445 this, SIGNAL(selectionChanged()) );
446 connect( w, SIGNAL(messageStatusChangeRequest(Akonadi::Item, KPIM::MessageStatus, KPIM::MessageStatus)),
447 this, SIGNAL(messageStatusChangeRequest(Akonadi::Item, KPIM::MessageStatus, KPIM::MessageStatus)) );
449 connect( w, SIGNAL(statusMessage(QString)),
450 this, SIGNAL(statusMessage(QString)) );
452 connect( w, SIGNAL( fullSearchRequest() ), this, SIGNAL( fullSearchRequest() ) );
453 d->updateTabControls();
454 setCurrentWidget( w );
457 QItemSelection Pane::Private::mapSelectionToSource( const QItemSelection &selection ) const
459 QItemSelection result = selection;
461 foreach ( const QAbstractProxyModel *proxy, mProxyStack ) {
462 result = proxy->mapSelectionToSource( result );
465 return result;
468 QItemSelection Pane::Private::mapSelectionFromSource( const QItemSelection &selection ) const
470 QItemSelection result = selection;
472 typedef QList<const QAbstractProxyModel*>::ConstIterator Iterator;
474 for ( Iterator it = mProxyStack.end()-1; it!=mProxyStack.begin(); --it ) {
475 result = (*it)->mapSelectionFromSource( result );
477 result = mProxyStack.first()->mapSelectionFromSource( result );
479 return result;
482 void Pane::Private::updateTabControls()
484 mCloseTabButton->setEnabled( q->count()>1 );
486 if ( Core::Settings::self()->autoHideTabBarWithSingleTab() ) {
487 q->tabBar()->setVisible( q->count()>1 );
488 } else {
489 q->tabBar()->setVisible( true );
493 Item Pane::currentItem() const
495 Widget *w = static_cast<Widget*>( currentWidget() );
497 if ( w == 0 ) {
498 return Item();
501 return w->currentItem();
504 KMime::Message::Ptr Pane::currentMessage() const
506 Widget *w = static_cast<Widget*>( currentWidget() );
508 if ( w == 0 ) {
509 return KMime::Message::Ptr();
512 return w->currentMessage();
515 QList<KMime::Message::Ptr > Pane::selectionAsMessageList( bool includeCollapsedChildren ) const
517 Widget *w = static_cast<Widget*>( currentWidget() );
518 if ( w == 0 ) {
519 return QList<KMime::Message::Ptr>();
521 return w->selectionAsMessageList( includeCollapsedChildren );
524 QList<Akonadi::Item> Pane::selectionAsMessageItemList( bool includeCollapsedChildren ) const
526 Widget *w = static_cast<Widget*>( currentWidget() );
527 if ( w == 0 ) {
528 return QList<Akonadi::Item>();
530 return w->selectionAsMessageItemList( includeCollapsedChildren );
534 QList<Akonadi::Item> Pane::currentThreadAsMessageList() const
536 Widget *w = static_cast<Widget*>( currentWidget() );
537 if ( w == 0 ) {
538 return QList<Akonadi::Item>();
540 return w->currentThreadAsMessageList();
543 QList<Akonadi::Item> Pane::itemListFromPersistentSet( MessageList::Core::MessageItemSetReference ref )
545 Widget *w = static_cast<Widget*>( currentWidget() );
546 if ( w == 0 ) {
547 return QList<Akonadi::Item>();
549 return w->itemListFromPersistentSet(ref);
553 void Pane::deletePersistentSet( MessageList::Core::MessageItemSetReference ref )
555 Widget *w = static_cast<Widget*>( currentWidget() );
556 if ( w ) {
557 w->deletePersistentSet( ref );
561 void Pane::markMessageItemsAsAboutToBeRemoved( MessageList::Core::MessageItemSetReference ref, bool bMark )
563 Widget *w = static_cast<Widget*>( currentWidget() );
564 if ( w ) {
565 w->markMessageItemsAsAboutToBeRemoved( ref, bMark );
569 KPIM::MessageStatus Pane::currentFilterStatus() const
571 Widget *w = static_cast<Widget*>( currentWidget() );
572 if ( w == 0 ) {
573 return KPIM::MessageStatus();
575 return w->currentFilterStatus();
578 QString Pane::currentFilterSearchString() const
580 Widget *w = static_cast<Widget*>( currentWidget() );
581 if ( w == 0 ) {
582 return QString();
584 return w->currentFilterSearchString();
587 bool Pane::isThreaded() const
589 Widget *w = static_cast<Widget*>( currentWidget() );
590 if ( w == 0 ) {
591 return false;
593 return w->isThreaded();
596 bool Pane::selectionEmpty() const
598 Widget *w = static_cast<Widget*>( currentWidget() );
599 if ( w == 0 ) {
600 return false;
602 return w->selectionEmpty();
605 bool Pane::getSelectionStats( Akonadi::Item::List &selectedItems,
606 Akonadi::Item::List &selectedVisibleItems,
607 bool * allSelectedBelongToSameThread,
608 bool includeCollapsedChildren ) const
610 Widget * w = static_cast<Widget*>( currentWidget() );
611 if ( w == 0 ) {
612 return false;
615 return w->getSelectionStats(
616 selectedItems, selectedVisibleItems,
617 allSelectedBelongToSameThread, includeCollapsedChildren
621 MessageList::Core::MessageItemSetReference Pane::selectionAsPersistentSet( bool includeCollapsedChildren ) const
623 Widget *w = static_cast<Widget*>( currentWidget() );
624 if ( w == 0 )
625 return -1;
626 return w->selectionAsPersistentSet( includeCollapsedChildren );
629 MessageList::Core::MessageItemSetReference Pane::currentThreadAsPersistentSet() const
631 Widget *w = static_cast<Widget*>( currentWidget() );
632 if ( w == 0 )
633 return -1;
634 return w->currentThreadAsPersistentSet();
637 void Pane::focusView()
639 Widget *w = static_cast<Widget*>( currentWidget() );
640 if ( w ) {
641 QWidget *view = w->view();
642 if ( view )
643 view->setFocus();
647 void Pane::reloadGlobalConfiguration()
649 d->updateTabControls();
651 Core::Settings::self()->writeConfig();
654 #include "pane.moc"