Backport r950340 | aacid | 2009-04-06 23:21:18 +0200 (Mon, 06 Apr 2009) | 4 lines
[kdepim.git] / kmail / mainfolderview.cpp
blob05a390c69a3c3f27a6e2ce3a462ed1a8614d70b3
1 /******************************************************************************
3 * Copyright 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
5 * Based on kmfoldertree.cpp which was Copyrighted by the KMail Development
6 * Team and didn't report detailed author list.
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 *****************************************************************************/
24 #include "mainfolderview.h"
26 #include "globalsettings.h"
28 #include "kmfolder.h"
29 #include "kmfoldermgr.h"
30 #include "kmmainwidget.h"
31 #include "util.h"
33 #include <kmenu.h>
34 #include <kicon.h>
35 #include <klocale.h>
37 #include <QMenu>
38 #include <QAction>
39 #include <QVariant>
41 #include <libkdepim/broadcaststatus.h>
43 namespace KMail
46 MainFolderView::MainFolderView( KMMainWidget *mainWidget, FolderViewManager *manager, QWidget *parent, const char *name )
47 : FolderView( mainWidget, manager, parent, "MainFolderView", name )
49 setIconSize( QSize( 16, 16 ) ); // default to smaller icons
52 FolderViewItem * MainFolderView::createItem(
53 FolderViewItem *parent,
54 const QString &label,
55 KMFolder *folder,
56 KPIM::FolderTreeWidgetItem::Protocol proto,
57 KPIM::FolderTreeWidgetItem::FolderType type
60 if ( parent )
61 return new FolderViewItem( parent, label, folder, proto, type );
62 return new FolderViewItem( this, label, folder, proto, type );
65 //=====================================================================================
66 // Contextual menu related stuff
69 void MainFolderView::fillContextMenuTreeStructureRelatedActions( KMenu *menu, FolderViewItem *item, bool multiSelection )
71 KMFolder *folder = item->folder();
73 if ( !multiSelection )
75 // Treat the special case of the root and account folders
76 if ( ( !folder ) || ( folder->noContent() && ( item->folderType() == FolderViewItem::Root ) ) )
78 if ( folder || ( ( item->folderType() == FolderViewItem::Root ) && ( item->protocol() == FolderViewItem::Local ) ) )
80 // local root or account folder
81 QString createChild = folder ? i18n( "&New Subfolder..." ) : i18n( "&New Folder..." );
83 menu->addAction(
84 KIcon("folder-new"), createChild,
85 this, SLOT( slotAddChildFolder() )
88 } else {
89 // regular (non root, non account) folders
91 if ( !folder->noChildren() )
92 menu->addAction(
93 KIcon( "folder-new" ), i18n( "&New Subfolder..." ),
94 this, SLOT( slotAddChildFolder() )
99 if ( folder )
101 // These two will work also for multiple folders
103 if( item->folderType() != FolderViewItem::Root ) // disable copy/move for root folders since the underlying copy/move code screws up completely on this
105 // copy folder
106 QMenu *copyMenu = menu->addMenu( KIcon("edit-copy"),
107 multiSelection ? i18n( "&Copy Folders To" ) :
108 i18n( "&Copy Folder To" ) );
109 folderToPopupMenu( CopyFolder, this, copyMenu );
111 // FIXME: if AT LEAST ONE folder is moveable
112 if ( folder && folder->isMoveable() )
114 QMenu *moveMenu = menu->addMenu( KIcon( "go-jump" ),
115 multiSelection ? i18n( "&Move Folders To" ) :
116 i18n( "&Move Folder To" ) );
117 folderToPopupMenu( MoveFolder, this, moveMenu );
121 if ( !multiSelection && !folder->isSystemFolder() )
122 menu->addAction( mainWidget()->action( "delete_folder" ) );
127 void MainFolderView::fillContextMenuViewStructureRelatedActions( KMenu *menu, FolderViewItem *item, bool /*multiSelection*/ )
129 KMFolder * folder = item->folder();
130 if ( !folder )
131 return;
133 if ( folder->noContent() )
134 return;
136 if ( !GlobalSettings::self()->enableFavoriteFolderView() )
137 return;
139 // this works for multiple folders too
140 menu->addAction(
141 KIcon("bookmark-new"), i18n("Add to Favorite Folders"),
142 this, SLOT( slotAddToFavorites() )
146 //=====================================================================================
147 // Folder Tree Structure to Popup Menu
150 void MainFolderView::folderToPopupMenuInternal(
151 MenuAction action, QObject * target, QMenu *menu, QTreeWidgetItem * parentItem
154 int childCount = parentItem->childCount();
156 for ( int idx = 0; idx < childCount; idx++ )
158 FolderViewItem * fvi = static_cast< FolderViewItem * >( parentItem->child( idx ) );
160 if ( !fvi )
161 continue;
163 // skip search folders
164 if ( fvi->protocol() == KPIM::FolderTreeWidgetItem::Search )
165 continue;
167 QString label = fvi->labelText();
168 label.replace( '&',"&&" );
170 if ( fvi->childCount() > 0 )
172 // new level
173 QMenu* popup = new QMenu( menu );
175 popup->setObjectName( "subMenu" );
176 popup->setTitle( label );
177 popup->setIcon( KIcon( fvi->normalIcon() ) );
179 folderToPopupMenuInternal( action, target, popup, fvi );
181 bool subMenu = false;
182 if ( ( action == MoveMessage || action == CopyMessage ) &&
183 fvi->folder() && !fvi->folder()->noContent() )
184 subMenu = true;
185 if ( ( action == MoveFolder || action == CopyFolder )
186 && ( !fvi->folder() || ( fvi->folder() && !fvi->folder()->noChildren() ) ) )
187 subMenu = true;
189 QString sourceFolderName;
190 FolderViewItem* srcItem = static_cast< FolderViewItem * >( currentItem() );
191 if ( srcItem )
192 sourceFolderName = srcItem->text( 0 );
194 if (
195 (action == MoveFolder || action == CopyFolder) &&
196 fvi->folder() && fvi->folder()->child() &&
197 fvi->folder()->child()->hasNamedFolder( sourceFolderName )
199 subMenu = false;
201 if ( subMenu )
203 popup->addSeparator();
205 QAction* act;
206 if ( action == MoveMessage || action == MoveFolder )
207 act = popup->addAction( i18n("Move to This Folder") );
208 else
209 act = popup->addAction( i18n("Copy to This Folder") );
211 act->setData( QVariant::fromValue<void *>( (void *)( fvi->folder() ) ) );
214 menu->addMenu( popup );
216 } else {
218 // insert an item
219 QAction* act = menu->addAction( KIcon( fvi->normalIcon() ), label );
220 if ( fvi->folder() )
221 act->setData( QVariant::fromValue<void *>( (void *)( fvi->folder() ) ) );
222 bool enabled = (fvi->folder() ? true : false);
223 if ( fvi->folder() &&
224 ( fvi->folder()->isReadOnly() || fvi->folder()->noContent() ) )
225 enabled = false;
226 act->setEnabled( enabled );
231 void MainFolderView::folderToPopupMenu( MenuAction action, QObject * target, QMenu *menu )
233 // The public version of the function
234 if ( !menu )
235 return; // sanity check
237 menu->clear();
239 // (re-)connect the signals
240 switch ( action )
242 case MoveMessage:
243 Util::reconnectSignalSlotPair( menu, SIGNAL( triggered( QAction * ) ), target, SLOT( slotMoveSelectedMessagesToFolder( QAction* ) ) );
244 break;
245 case MoveFolder:
246 Util::reconnectSignalSlotPair( menu, SIGNAL( triggered( QAction * ) ), target, SLOT( slotCopySelectedFoldersToFolder( QAction* ) ) );
247 break;
248 case CopyMessage:
249 Util::reconnectSignalSlotPair( menu, SIGNAL( triggered( QAction * ) ), target, SLOT( slotCopySelectedMessagesToFolder( QAction* ) ) );
250 break;
251 case CopyFolder:
252 Util::reconnectSignalSlotPair( menu, SIGNAL( triggered( QAction * ) ), target, SLOT( slotCopySelectedFoldersToFolder( QAction* ) ) );
253 break;
256 if ( topLevelItemCount() < 1 )
257 return; // done
259 folderToPopupMenuInternal( action, target, menu, invisibleRootItem() );
262 void MainFolderView::slotMoveSelectedFoldersToFolder( QAction* act )
264 KMFolder * f = static_cast<KMFolder *>( act->data().value<void *>() );
265 if ( !f )
266 return;
267 moveOrCopyFolders( selectedFolders(), f, true /*move*/ );
270 void MainFolderView::slotCopySelectedFoldersToFolder( QAction* act )
272 KMFolder * f = static_cast<KMFolder *>( act->data().value<void *>() );
273 if ( !f )
274 return;
275 moveOrCopyFolders( selectedFolders(), f, false /*copy, don't move*/ );
279 //=======================================================================================
280 // DND Machinery: we allow moving folders around and eventually sorting stuff by dnd
283 bool MainFolderView::acceptDropCopyOrMoveFolders( FolderViewItem *item )
285 if ( !item )
286 return false; // obviously
288 if ( item->protocol() == FolderViewItem::Search )
289 return false; // nothing can be dragged into search folders
291 QList< QPointer< KMFolder > > lFolders = DraggedFolderList::get();
292 if ( lFolders.isEmpty() )
293 return false; // nothing we can accept
295 KMFolder *fld = item->folder();
297 if ( fld )
299 if ( fld->isReadOnly() || fld->noContent() )
300 return false; // content can't be added to folder
302 // make sure ALL of the items can be copied or moved to this folder
304 // parents can't be moved into children so make sure that fld is not
305 // a child of one of the dragged folders
306 for ( QList< QPointer< KMFolder > >::Iterator it = lFolders.begin(); it != lFolders.end(); ++it )
308 if ( !( *it ) )
309 return false; // one of the folders was lost in the way: don't bother with the copy
310 if ( ( *it ) == fld )
311 return false; // dragging a folder over itself
312 if ( ( *it ) == fld->ownerFolder() )
313 return false; // dragging a folder over its direct owner (parent)
314 if ( ( *it )->hasDescendant( fld ) )
315 return false; // dragging a folder over one of its descendants
316 if ( ! ( ( *it )->ownerFolder() || ( ( *it )->folderType() == KMFolderTypeMbox ) || ( ( *it )->folderType() == KMFolderTypeMaildir ) ) )
317 return false; // dragging a non local root
320 } else {
321 if (
322 ( item->protocol() != FolderViewItem::Local ) ||
323 ( item->folderType() != FolderViewItem::Root )
325 return false; // non local top-level folder (can be searches, but nothing can be dragged into at the moment)
327 // local root for sure: this is a toplevel item, no parents
328 // just make sure that no dragged folder is already a direct child of local root
329 for ( QList< QPointer< KMFolder > >::Iterator it = lFolders.begin(); it != lFolders.end(); ++it )
331 if ( !( *it ) )
332 return false; // one of the folders was lost in the way: don't bother with the copy
333 if ( ( *it )->parent() == &( kmkernel->folderMgr()->dir() ) )
334 return false; // dragged folder is a direct child of local top-level
335 if ( ! ( ( *it )->ownerFolder() || ( ( *it )->folderType() == KMFolderTypeMbox ) || ( ( *it )->folderType() == KMFolderTypeMaildir ) ) )
336 return false; // dragging a non local root
340 return true; // item would accept a copy/move of all the dragged folders
344 class MainFolderViewFoldersDropAction
346 public:
347 enum DropFoldersStatus
349 Accept, ///< Drag is accepted: we can do something with it
350 Reject ///< Drag is rejected: can't do anything with it at the moment
353 public:
354 // out
355 QRect validityRect; ///< The rect where the results of this structure remain valid
356 DropFoldersStatus status; ///< Do we accept or reject ?
357 // out if status == Accept
358 bool doCopyOrMove; ///< A Copy/Move is required ?
359 FolderViewItem *moveTarget; ///< Copy/Move where ? (valid only if mustCopyOrMove is true)
360 bool doPositionalInsert;
361 MainFolderView::DropInsertPosition insertPosition; ///< Where to insert the data with respect to insertReference ?
362 FolderViewItem *reference; ///< The reference item that defines our insertions
363 QString description; ///< Description of the operation that will be done
366 void MainFolderView::computeFoldersDropAction( QDropEvent *e, MainFolderViewFoldersDropAction *act )
368 // Ok, this is quite complex.
370 // It attempts to determine what to do with the currently dragged folders
371 // if they were dropped on the item in the current mouse position.
373 // The rules:
375 // - If we're in the upper part of an item and dnd sorting is enabled
376 // then we might be wanting to just place the dragged stuff above the item.
378 // - If we're in the lower part of an item and dnd sorting is enabled
379 // then we might be wanting to just place the dragged stuff below the item.
381 // - If we're in the middle of an item then we might be wanting to
382 // move/copy the dragged stuff INSIDE the item.
384 // - If we're in the upper or lower part of an item but the dragged stuff
385 // has a parent that is not the items's parent then the sorting
386 // operation might ALSO require a copy or move (to the parent).
388 // - If dnd sorting is not enabled then we always attempt to copy or move stuff INSIDE o->item.
390 // - To complicate the things a bit more, copying/moving might be not possible
391 // for several reasons...
393 // This function attempts to deal with all these rules and by the meantime
394 // give to the user some visual feedback about what is going on.
397 // first of all, we allow only folder drags coming from THIS view
398 if ( e->source() != this )
400 act->validityRect = viewport()->rect();
401 act->status = MainFolderViewFoldersDropAction::Reject;
402 return;
405 act->reference = static_cast<FolderViewItem *>( itemAt( e->pos() ) );
406 int draggedFolderCount = DraggedFolderList::get().count();
408 bool bWasOutsideItem = false;
410 if ( !act->reference )
412 // not over an item: try to use the last item in the view as reference
413 bWasOutsideItem = true;
415 int cc = topLevelItemCount();
416 if ( cc < 1 )
418 // nothing in the view at all ? ... this is senseless
419 act->validityRect = viewport()->rect();
420 act->status = MainFolderViewFoldersDropAction::Reject;
421 return;
424 act->reference = static_cast<FolderViewItem *>( topLevelItem( cc - 1 ) );
425 // now item != 0 (and item is visible)
428 QRect r = visualItemRect( act->reference );
429 QRect mouseRect( e->pos().x() - 1, e->pos().y() - 1, 2, 2 );
431 // set defaults
432 act->status = MainFolderViewFoldersDropAction::Reject;
433 act->validityRect = mouseRect;
435 bool bCopyOrMoveToCurrentIsPossible = \
436 !bWasOutsideItem && /* a copy/move operation would look bad in this case */
437 ( act->reference->flags() & Qt::ItemIsDropEnabled ) && /* item has drops disabled */
438 acceptDropCopyOrMoveFolders( act->reference ); /* item can't accept the drop for some other reason */
440 if ( sortingPolicy() != SortByDragAndDropKey )
442 // when sorting by dnd is disabled then we always act on the current item
443 act->validityRect = r;
445 if ( bCopyOrMoveToCurrentIsPossible )
447 // sorting by dnd is disabled but we can copy/move messages to the reference folder.
448 act->moveTarget = act->reference;
449 act->status = MainFolderViewFoldersDropAction::Accept;
450 act->doCopyOrMove = true;
451 act->doPositionalInsert = false;
452 act->description = i18np( "Move or copy folder to %2",
453 "Move or copy %1 folders to %2", draggedFolderCount,
454 act->moveTarget->labelText() );
455 } // else if we cannot move folders to the current item and sorting by dnd is disabled
456 // hence there is nothing more we can do here: ignore.
457 return;
460 // sortingPolicy() == SortByDragAndDropKey
462 // now try to actually compute the drop position
464 act->doPositionalInsert = false;
466 if ( e->pos().y() < ( r.top() + ( r.height() / 4 ) ) )
468 // would drop above item
469 act->validityRect = QRect( r.left(), r.top(), r.width(), r.height() / 4 );
470 act->doPositionalInsert = true;
471 act->insertPosition = AboveReference;
472 } else if ( e->pos().y() > ( r.bottom() - ( r.height() / 4 ) ) )
474 act->validityRect = QRect( r.left(), r.bottom() - ( r.height() / 4 ), r.width(), r.height() / 4 );
475 act->doPositionalInsert = true;
476 act->insertPosition = BelowReference;
479 if ( !act->doPositionalInsert )
481 // we're actually exactly over the item
482 act->validityRect = QRect( r.left(), r.top() + ( r.height() / 4 ), r.width(), r.height() / 2 );
484 if ( bCopyOrMoveToCurrentIsPossible )
486 // we can move the folders.
487 act->moveTarget = act->reference;
488 act->status = MainFolderViewFoldersDropAction::Accept;
489 act->doCopyOrMove = true;
490 act->description = i18np( "Move or copy folder to %2",
491 "Move or copy %1 folders to %2",
492 draggedFolderCount, act->moveTarget->labelText() );
493 } // else if we cannot move folders to the current item thus ignore
495 return;
498 // Ok, now we're trying to do positional insert for sure.
500 // If we're moving items from the act->reference's parent then
501 // the thingie is easy: no copy or move is needed.
503 QTreeWidgetItem *commonParent = static_cast< QTreeWidgetItem * >( act->reference )->parent();
504 // commonParent might be null here!
506 // please note that the drag comes from our view so we're actually
507 // dragging the selected items!
508 bool bMovingFromCommonParent = true;
510 QTreeWidgetItemIterator it( this );
511 while ( *it )
513 if ( ( *it )->isSelected() )
515 // this item is being dragged
516 if ( ( *it )->parent() != commonParent )
518 bMovingFromCommonParent = false;
519 break;
522 ++it;
525 if ( bMovingFromCommonParent )
527 // this is just a sort inside a single hierarchy level
528 act->status = MainFolderViewFoldersDropAction::Accept;
529 act->doCopyOrMove = false;
530 if ( act->insertPosition == AboveReference )
531 act->description = i18np( "Order folder above %2",
532 "Order %1 folders above %2",
533 draggedFolderCount, act->reference->labelText() );
534 else
535 act->description = i18np( "Order folder below %2",
536 "Order %1 folders below %2",
537 draggedFolderCount, act->reference->labelText() );
539 viewport()->update();
540 return;
543 // not dragging from common parent: both a "copy or move" operation and a positional insert
544 // is needed...
546 // check if commonParent can accept a "copy or move" operation.
547 act->moveTarget = static_cast< FolderViewItem * >( commonParent );
549 // if moving to common parent is not possible then sorting is also not possible
550 if ( !act->moveTarget )
551 return;
552 if ( !( act->moveTarget->flags() & Qt::ItemIsDropEnabled ) )
553 return;
554 if ( !acceptDropCopyOrMoveFolders( act->moveTarget ) )
555 return;
557 // moving to a common parent (act->moveTarget) is possible
559 act->status = MainFolderViewFoldersDropAction::Accept;
560 act->doCopyOrMove = true;
561 if ( act->insertPosition == AboveReference )
562 act->description = i18np( "Move or copy folder to %2, order above %3",
563 "Move or copy %1 folders to %2, order above %3",
564 draggedFolderCount,
565 act->moveTarget->labelText(), act->reference->labelText() );
566 else
567 act->description = i18np( "Move or copy folder to %2, order below %3",
568 "Move or copy %1 folders to %2, order below %3",
569 draggedFolderCount,
570 act->moveTarget->labelText(), act->reference->labelText() );
575 void MainFolderView::handleFoldersDragMoveEvent( QDragMoveEvent *e )
577 MainFolderViewFoldersDropAction act;
579 computeFoldersDropAction( e, &act );
580 if ( act.status == MainFolderViewFoldersDropAction::Accept )
582 e->accept( act.validityRect );
583 setDropIndicatorData(
584 act.doCopyOrMove ? act.moveTarget : 0,
585 act.doPositionalInsert ? act.reference : 0,
586 act.insertPosition
588 } else {
589 e->ignore( act.validityRect );
590 setDropIndicatorData( 0, 0 );
593 KPIM::BroadcastStatus::instance()->setStatusMsg( act.description );
596 void MainFolderView::handleFoldersDropEvent( QDropEvent *e )
598 MainFolderViewFoldersDropAction act;
600 computeFoldersDropAction( e, &act );
601 if ( act.status != MainFolderViewFoldersDropAction::Accept )
603 e->ignore();
604 return;
607 e->accept();
609 if ( !act.doCopyOrMove )
611 // assert( act.doPositionalInsert );
613 // only a move (within the same parent)
615 QTreeWidgetItem *commonParent = static_cast< QTreeWidgetItem * >( act.reference )->parent();
617 int refIdx = commonParent ? commonParent->indexOfChild( act.reference ) : indexOfTopLevelItem( act.reference );
618 if ( act.insertPosition == AboveReference )
619 refIdx--;
621 // we're dragging items from THIS view
622 setUpdatesEnabled( false );
624 QList< QTreeWidgetItem * > lSelected = selectedItems();
627 for ( QList< QTreeWidgetItem * >::Iterator it = lSelected.begin(); it != lSelected.end(); ++it )
629 if ( !( *it ) )
630 continue; // umphf
632 FolderViewItem *moved = static_cast< FolderViewItem * >( *it );
634 int removedIdx = commonParent ? commonParent->indexOfChild( moved ) : indexOfTopLevelItem( moved );
635 if ( commonParent )
636 commonParent->takeChild( removedIdx );
637 else
638 takeTopLevelItem( removedIdx );
640 if ( removedIdx > refIdx )
641 refIdx++;
643 if ( commonParent )
644 commonParent->insertChild( refIdx, moved );
645 else
646 insertTopLevelItem( refIdx, moved );
649 setUpdatesEnabled( true );
651 fixSortingKeysForChildren( commonParent ? commonParent : invisibleRootItem() );
652 sortByColumn( LabelColumn, Qt::AscendingOrder );
654 viewport()->update();
656 return;
659 // must do a copy or move
660 // folders must be first copied/moved and then sorted
662 // Get the dragged folder list
663 QList<QPointer<KMFolder> > lFolders = DraggedFolderList::get();
664 if ( lFolders.isEmpty() )
665 return; // nothing we can accept
667 bool bCanMove = true;
669 // Check that each pointer is not null and compute possible dnd modes
670 for ( QList<QPointer<KMFolder> >::Iterator it = lFolders.begin(); it != lFolders.end(); ++it )
672 if ( !( *it ) )
673 return; // yes, that's pessimistic, but if we lost a folder then something weird happened anyway: stay safe
674 if ( !( ( *it )->isMoveable() ) )
675 bCanMove = false; // at least one folder is not moveable, allow copy only
678 // ask for dnd mode now
679 int action = dragMode( true, bCanMove );
680 switch ( action )
682 case DragCopy:
683 e->setDropAction( Qt::CopyAction );
684 break;
685 case DragMove:
686 e->setDropAction( Qt::MoveAction );
687 break;
688 default:
689 return; // cancelled
690 break;
693 if ( act.doPositionalInsert )
695 // will need positional insertion AFTER the items are copied
696 // FIXME: this is missing... let's see if somebody complains before I implement it :P
699 moveOrCopyFolders( lFolders, act.moveTarget->folder(), (action == DragMove) );
706 #include "mainfolderview.moc"