Allow specializations to decide when re-indexing is necessary.
[kdepim.git] / messagelist / widget.cpp
blob02b2796c360d1ddf36da355abd843a66796d35ab
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 "widget.h"
21 #include <akonadi/collection.h>
22 #include <akonadi/item.h>
23 #include <akonadi/itemcopyjob.h>
24 #include <akonadi/itemmovejob.h>
26 #include "storagemodel.h"
27 #include "core/messageitem.h"
28 #include "core/view.h"
30 #include <QtCore/QTimer>
31 #include <QtGui/QAction>
32 #include <QtGui/QApplication>
33 #include <QtGui/QDrag>
34 #include <QtGui/QDragMoveEvent>
35 #include <QtGui/QDropEvent>
37 #include <KDE/KDebug>
38 #include <KDE/KIcon>
39 #include <KDE/KIconLoader>
40 #include <KDE/KLocale>
41 #include <KDE/KMenu>
42 #include <KDE/KXMLGUIClient>
43 #include <KDE/KXMLGUIFactory>
44 #include <KDE/KComboBox>
46 #include <Nepomuk/Tag>
48 namespace MessageList
51 class Widget::Private
53 public:
54 Private( Widget *owner )
55 : q( owner ), mXmlGuiClient( 0 ) { }
57 Akonadi::Item::List selectionAsItems() const;
58 Akonadi::Item itemForRow( int row ) const;
59 KMime::Message::Ptr messageForRow( int row ) const;
61 Widget * const q;
63 int mLastSelectedMessage;
64 KXMLGUIClient *mXmlGuiClient;
67 } // namespace MessageList
69 using namespace MessageList;
70 using namespace Akonadi;
72 Widget::Widget( QWidget *parent )
73 : Core::Widget( parent ), d( new Private( this ) )
75 populateStatusFilterCombo();
78 Widget::~Widget()
80 delete d;
83 void Widget::setXmlGuiClient( KXMLGUIClient *xmlGuiClient )
85 d->mXmlGuiClient = xmlGuiClient;
88 bool Widget::canAcceptDrag( const QDropEvent * e )
90 Collection::List collections = static_cast<const StorageModel*>( storageModel() )->displayedCollections();
92 if ( collections.size()!=1 )
93 return false; // no folder here or too many (in case we can't decide where the drop will end)
95 const Collection target = collections.first();
97 if ( ( target.rights() & Collection::CanCreateItem ) == 0 )
98 return false; // no way to drag into
100 const KUrl::List urls = KUrl::List::fromMimeData( e->mimeData() );
101 foreach ( const KUrl &url, urls ) {
102 const Collection collection = Collection::fromUrl( url );
103 if ( collection.isValid() ) { // You're not supposed to drop collections here
104 return false;
105 } else { // Yay, this is an item!
106 const QString type = url.queryItems()["type"]; // But does it have the right type?
107 if ( !target.contentMimeTypes().contains( type ) ) {
108 return false;
113 return true;
116 bool Widget::selectNextMessageItem( MessageList::Core::MessageTypeFilter messageTypeFilter,
117 MessageList::Core::ExistingSelectionBehaviour existingSelectionBehaviour,
118 bool centerItem,
119 bool loop )
121 return view()->selectNextMessageItem( messageTypeFilter, existingSelectionBehaviour, centerItem, loop );
124 bool Widget::selectPreviousMessageItem( MessageList::Core::MessageTypeFilter messageTypeFilter,
125 MessageList::Core::ExistingSelectionBehaviour existingSelectionBehaviour,
126 bool centerItem,
127 bool loop )
129 return view()->selectPreviousMessageItem( messageTypeFilter, existingSelectionBehaviour, centerItem, loop );
132 bool Widget::focusNextMessageItem( MessageList::Core::MessageTypeFilter messageTypeFilter, bool centerItem, bool loop )
134 return view()->focusNextMessageItem( messageTypeFilter, centerItem, loop );
137 bool Widget::focusPreviousMessageItem( MessageList::Core::MessageTypeFilter messageTypeFilter, bool centerItem, bool loop )
139 return view()->focusNextMessageItem( messageTypeFilter, centerItem, loop );
142 void Widget::selectFocusedMessageItem( bool centerItem )
144 view()->selectFocusedMessageItem( centerItem );
147 bool Widget::selectFirstMessageItem( MessageList::Core::MessageTypeFilter messageTypeFilter, bool centerItem )
149 return view()->selectFirstMessageItem( messageTypeFilter, centerItem );
152 void Widget::selectAll()
154 view()->setAllGroupsExpanded( true );
155 view()->selectAll();
158 void Widget::setCurrentThreadExpanded( bool expand )
160 view()->setCurrentThreadExpanded(expand );
163 void Widget::setAllThreadsExpanded( bool expand )
165 view()->setAllThreadsExpanded( expand );
168 void Widget::setAllGroupsExpanded( bool expand )
170 view()->setAllGroupsExpanded(expand);
173 void Widget::focusQuickSearch()
175 view()->focusQuickSearch();
179 void Widget::fillMessageTagCombo( KComboBox * combo )
181 foreach( const Nepomuk::Tag &nepomukTag, Nepomuk::Tag::allTags() ) {
182 QString iconName = "mail-tagged";
183 const QString label = nepomukTag.label();
184 if ( !nepomukTag.symbols().isEmpty() )
185 iconName = nepomukTag.symbols().first();
186 const QString id = nepomukTag.resourceUri().toString();
187 combo->addItem( SmallIcon( iconName ), label, QVariant( id ) );
191 void Widget::viewMessageSelected( MessageList::Core::MessageItem *msg )
193 int row = -1;
194 if ( msg ) {
195 row = msg->currentModelIndexRow();
198 if ( !msg || !msg->isValid() || !storageModel() ) {
199 d->mLastSelectedMessage = -1;
200 emit messageSelected( Item() );
201 return;
204 Q_ASSERT( row >= 0 );
206 d->mLastSelectedMessage = row;
208 emit messageSelected( d->itemForRow( row ) ); // this MAY be null
211 void Widget::viewMessageActivated( MessageList::Core::MessageItem *msg )
213 Q_ASSERT( msg ); // must not be null
214 Q_ASSERT( storageModel() );
216 if ( !msg->isValid() ) {
217 return;
220 int row = msg->currentModelIndexRow();
221 Q_ASSERT( row >= 0 );
223 // The assert below may fail when quickly opening and closing a non-selected thread.
224 // This will actually activate the item without selecting it...
225 //Q_ASSERT( d->mLastSelectedMessage == row );
227 if ( d->mLastSelectedMessage != row ) {
228 // Very ugly. We are activating a non selected message.
229 // This is very likely a double click on the plus sign near a thread leader.
230 // Dealing with mLastSelectedMessage here would be expensive: it would involve releasing the last selected,
231 // emitting signals, handling recursion... ugly.
232 // We choose a very simple solution: double clicking on the plus sign near a thread leader does
233 // NOT activate the message (i.e open it in a toplevel window) if it isn't previously selected.
234 return;
237 emit messageActivated( d->itemForRow( row ) ); // this MAY be null
240 void Widget::viewSelectionChanged()
242 emit selectionChanged();
243 if ( !currentMessageItem() ) {
244 emit messageSelected( Item() );
248 void Widget::viewMessageListContextPopupRequest( const QList< MessageList::Core::MessageItem * > &selectedItems,
249 const QPoint &globalPos )
251 if ( !d->mXmlGuiClient )
252 return;
254 QMenu *popup = static_cast<QMenu*>( d->mXmlGuiClient->factory()->container(
255 QLatin1String( "akonadi_messagelist_contextmenu" ),
256 d->mXmlGuiClient ) );
257 if ( popup ) {
258 popup->exec( globalPos );
262 void Widget::viewMessageStatusChangeRequest( MessageList::Core::MessageItem *msg, const KPIM::MessageStatus &set, const KPIM::MessageStatus &clear )
264 Q_ASSERT( msg ); // must not be null
265 Q_ASSERT( storageModel() );
267 if ( !msg->isValid() ) {
268 return;
271 int row = msg->currentModelIndexRow();
272 Q_ASSERT( row >= 0 );
274 Item item = d->itemForRow( row );
275 Q_ASSERT( item.isValid() );
277 emit messageStatusChangeRequest( item, set, clear );
280 void Widget::viewGroupHeaderContextPopupRequest( MessageList::Core::GroupHeaderItem *ghi, const QPoint &globalPos )
282 Q_UNUSED( ghi );
284 KMenu menu( this );
286 QAction *act;
288 menu.addSeparator();
290 act = menu.addAction( i18n( "Expand All Groups" ) );
291 connect( act, SIGNAL( triggered( bool ) ),
292 view(), SLOT( slotExpandAllGroups() ) );
294 act = menu.addAction( i18n( "Collapse All Groups" ) );
295 connect( act, SIGNAL( triggered( bool ) ),
296 view(), SLOT( slotCollapseAllGroups() ) );
298 menu.exec( globalPos );
301 void Widget::viewDragEnterEvent( QDragEnterEvent *e )
303 if ( !canAcceptDrag( e ) ) {
304 e->ignore();
305 return;
308 e->accept();
311 void Widget::viewDragMoveEvent( QDragMoveEvent *e )
313 if ( !canAcceptDrag( e ) ) {
314 e->ignore();
315 return;
318 e->accept();
321 enum DragMode
323 DragCopy,
324 DragMove,
325 DragCancel
328 void Widget::viewDropEvent( QDropEvent *e )
330 if ( !canAcceptDrag( e ) ) {
331 e->ignore();
332 return;
335 KUrl::List urls = KUrl::List::fromMimeData( e->mimeData() );
336 if ( urls.isEmpty() ) {
337 kWarning() << "Could not decode drag data!";
338 e->ignore();
339 return;
342 e->accept();
344 int action;
345 if ( ( e->possibleActions() & Qt::MoveAction ) == 0 ) { // We can't move anyway
346 action = DragCopy;
347 } else {
348 action = DragCancel;
349 int keybstate = QApplication::keyboardModifiers();
350 if ( keybstate & Qt::CTRL ) {
351 action = DragCopy;
353 } else if ( keybstate & Qt::SHIFT ) {
354 action = DragMove;
356 } else {
357 KMenu menu;
358 QAction *moveAction = menu.addAction( KIcon( "go-jump"), i18n( "&Move Here" ) );
359 QAction *copyAction = menu.addAction( KIcon( "edit-copy" ), i18n( "&Copy Here" ) );
360 menu.addSeparator();
361 menu.addAction( KIcon( "dialog-cancel" ), i18n( "C&ancel" ) );
363 QAction *menuChoice = menu.exec( QCursor::pos() );
364 if ( menuChoice == moveAction ) {
365 action = DragMove;
366 } else if ( menuChoice == copyAction ) {
367 action = DragCopy;
368 } else {
369 action = DragCancel;
374 Collection::List collections = static_cast<const StorageModel*>( storageModel() )->displayedCollections();
375 Collection target = collections.first();
376 Item::List items;
377 foreach ( const KUrl &url, urls ) {
378 items << Item::fromUrl( url );
381 if ( action == DragCopy ) {
382 new ItemCopyJob( items, target, this );
383 } else if ( action == DragMove ) {
384 new ItemMoveJob( items, target, this );
389 void Widget::viewStartDragRequest()
391 Collection::List collections = static_cast<const StorageModel*>( storageModel() )->displayedCollections();
393 if ( collections.isEmpty() )
394 return; // no folder here
396 QList<Item> items = d->selectionAsItems();
397 if ( items.isEmpty() )
398 return;
400 bool readOnly = false;
402 foreach ( const Collection c, collections ) {
403 // We won't be able to remove items from this collection
404 if ( ( c.rights() & Collection::CanDeleteItem ) == 0 ) {
405 // So the drag will be read-only
406 readOnly = true;
410 KUrl::List urls;
411 foreach ( const Item i, items ) {
412 urls << i.url( Item::UrlWithMimeType );
415 QMimeData *mimeData = new QMimeData;
416 urls.populateMimeData( mimeData );
418 QDrag *drag = new QDrag( view()->viewport() );
419 drag->setMimeData( mimeData );
421 // Set pixmap
422 QPixmap pixmap;
423 if( items.size() == 1 ) {
424 pixmap = QPixmap( DesktopIcon("mail-message", KIconLoader::SizeSmall) );
425 } else {
426 pixmap = QPixmap( DesktopIcon("document-multiple", KIconLoader::SizeSmall) );
429 // Calculate hotspot (as in Konqueror)
430 if( !pixmap.isNull() ) {
431 drag->setHotSpot( QPoint( pixmap.width() / 2, pixmap.height() / 2 ) );
432 drag->setPixmap( pixmap );
435 if ( readOnly )
436 drag->exec( Qt::CopyAction );
437 else
438 drag->exec( Qt::CopyAction | Qt::MoveAction );
441 Item::List Widget::Private::selectionAsItems() const
443 Item::List res;
444 QList<Core::MessageItem *> selection = q->view()->selectionAsMessageItemList();
446 foreach ( Core::MessageItem *mi, selection ) {
447 Item i = itemForRow( mi->currentModelIndexRow() );
448 Q_ASSERT( i.isValid() );
449 res << i;
452 return res;
455 Item Widget::Private::itemForRow( int row ) const
457 return static_cast<const StorageModel*>( q->storageModel() )->itemForRow( row );
460 KMime::Message::Ptr Widget::Private::messageForRow( int row ) const
462 return static_cast<const StorageModel*>( q->storageModel() )->messageForRow( row );
465 Item Widget::currentItem() const
467 Core::MessageItem *mi = view()->currentMessageItem();
469 if ( mi == 0 ) {
470 return Item();
473 return d->itemForRow( mi->currentModelIndexRow() );
476 KMime::Message::Ptr Widget::currentMessage() const
478 Core::MessageItem *mi = view()->currentMessageItem();
480 if ( mi == 0 ) {
481 return KMime::Message::Ptr();
484 return d->messageForRow( mi->currentModelIndexRow() );
488 QList<KMime::Message::Ptr > Widget::selectionAsMessageList( bool includeCollapsedChildren ) const
490 QList<KMime::Message::Ptr> lstMiPtr;
491 QList<Core::MessageItem *> lstMi = view()->selectionAsMessageItemList( includeCollapsedChildren );
492 if ( lstMi.isEmpty() ) {
493 return lstMiPtr;
495 foreach( Core::MessageItem *it, lstMi ) {
496 lstMiPtr.append( d->messageForRow( it->currentModelIndexRow() ) );
498 return lstMiPtr;
501 QList<Akonadi::Item> Widget::selectionAsMessageItemList( bool includeCollapsedChildren ) const
503 QList<Item> lstMiPtr;
504 QList<Core::MessageItem *> lstMi = view()->selectionAsMessageItemList( includeCollapsedChildren );
505 if ( lstMi.isEmpty() ) {
506 return lstMiPtr;
508 foreach( Core::MessageItem *it, lstMi ) {
509 lstMiPtr.append( d->itemForRow( it->currentModelIndexRow() ) );
511 return lstMiPtr;
514 QList<Akonadi::Item> Widget::currentThreadAsMessageList() const
516 QList<Item> lstMiPtr;
517 QList<Core::MessageItem *> lstMi = view()->currentThreadAsMessageItemList();
518 if ( lstMi.isEmpty() ) {
519 return lstMiPtr;
521 foreach( Core::MessageItem *it, lstMi ) {
522 lstMiPtr.append( d->itemForRow( it->currentModelIndexRow() ) );
524 return lstMiPtr;
528 KPIM::MessageStatus Widget::currentFilterStatus() const
530 return view()->currentFilterStatus();
533 QString Widget::currentFilterSearchString() const
535 return view()->currentFilterSearchString();
539 bool Widget::isThreaded() const
541 return view()->isThreaded();
544 bool Widget::selectionEmpty() const
546 return view()->selectionEmpty();
549 bool Widget::getSelectionStats(
550 Akonadi::Item::List &selectedItems,
551 Akonadi::Item::List &selectedVisibleItems,
552 bool * allSelectedBelongToSameThread,
553 bool includeCollapsedChildren ) const
555 if ( !storageModel() )
556 return false;
558 selectedItems.clear();
559 selectedVisibleItems.clear();
561 QList< Core::MessageItem * > selected = view()->selectionAsMessageItemList( includeCollapsedChildren );
563 Core::MessageItem * topmost = 0;
565 *allSelectedBelongToSameThread = true;
567 foreach( Core::MessageItem *it, selected ) {
568 const Item item = d->itemForRow( it->currentModelIndexRow() );
569 selectedItems.append( item );
570 if ( view()->isDisplayedWithParentsExpanded( it ) )
571 selectedVisibleItems.append( item );
572 if ( topmost == 0 )
573 topmost = ( *it ).topmostMessage();
574 else {
575 if ( topmost != ( *it ).topmostMessage() )
576 *allSelectedBelongToSameThread = false;
579 return true;
582 void Widget::deletePersistentSet( MessageList::Core::MessageItemSetReference ref )
584 view()->deletePersistentSet( ref );
587 void Widget::markMessageItemsAsAboutToBeRemoved( MessageList::Core::MessageItemSetReference ref, bool bMark )
589 QList< Core::MessageItem * > lstPersistent = view()->persistentSetCurrentMessageItemList( ref );
590 if ( !lstPersistent.isEmpty() )
591 view()->markMessageItemsAsAboutToBeRemoved( lstPersistent, bMark );
594 QList<Akonadi::Item> Widget::itemListFromPersistentSet( MessageList::Core::MessageItemSetReference ref )
596 QList<Akonadi::Item> lstItem;
597 QList< Core::MessageItem * > refList = view()->persistentSetCurrentMessageItemList( ref );
598 if ( !refList.isEmpty() ) {
599 foreach( Core::MessageItem *it, refList ) {
600 lstItem.append( d->itemForRow( it->currentModelIndexRow() ) );
603 return lstItem;
607 MessageList::Core::MessageItemSetReference Widget::selectionAsPersistentSet( bool includeCollapsedChildren ) const
609 QList<Core::MessageItem *> lstMi = view()->selectionAsMessageItemList( includeCollapsedChildren );
610 if ( lstMi.isEmpty() ) {
611 return -1;
613 return view()->createPersistentSet( lstMi );
616 MessageList::Core::MessageItemSetReference Widget::currentThreadAsPersistentSet() const
618 QList<Item> lstMiPtr;
619 QList<Core::MessageItem *> lstMi = view()->currentThreadAsMessageItemList();
620 if ( lstMi.isEmpty() ) {
621 return -1;
623 return view()->createPersistentSet( lstMi );