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"
29 #include "kmfoldermgr.h"
30 #include "kmmainwidget.h"
41 #include <libkdepim/broadcaststatus.h>
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
,
56 KPIM::FolderTreeWidgetItem::Protocol proto
,
57 KPIM::FolderTreeWidgetItem::FolderType type
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..." );
84 KIcon("folder-new"), createChild
,
85 this, SLOT( slotAddChildFolder() )
89 // regular (non root, non account) folders
91 if ( !folder
->noChildren() )
93 KIcon( "folder-new" ), i18n( "&New Subfolder..." ),
94 this, SLOT( slotAddChildFolder() )
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
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();
133 if ( folder
->noContent() )
136 if ( !GlobalSettings::self()->enableFavoriteFolderView() )
139 // this works for multiple folders too
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
) );
163 // skip search folders
164 if ( fvi
->protocol() == KPIM::FolderTreeWidgetItem::Search
)
167 QString label
= fvi
->labelText();
168 label
.replace( '&',"&&" );
170 if ( fvi
->childCount() > 0 )
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() )
185 if ( ( action
== MoveFolder
|| action
== CopyFolder
)
186 && ( !fvi
->folder() || ( fvi
->folder() && !fvi
->folder()->noChildren() ) ) )
189 QString sourceFolderName
;
190 FolderViewItem
* srcItem
= static_cast< FolderViewItem
* >( currentItem() );
192 sourceFolderName
= srcItem
->text( 0 );
195 (action
== MoveFolder
|| action
== CopyFolder
) &&
196 fvi
->folder() && fvi
->folder()->child() &&
197 fvi
->folder()->child()->hasNamedFolder( sourceFolderName
)
203 popup
->addSeparator();
206 if ( action
== MoveMessage
|| action
== MoveFolder
)
207 act
= popup
->addAction( i18n("Move to This Folder") );
209 act
= popup
->addAction( i18n("Copy to This Folder") );
211 act
->setData( QVariant::fromValue
<void *>( (void *)( fvi
->folder() ) ) );
214 menu
->addMenu( popup
);
219 QAction
* act
= menu
->addAction( KIcon( fvi
->normalIcon() ), label
);
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() ) )
226 act
->setEnabled( enabled
);
231 void MainFolderView::folderToPopupMenu( MenuAction action
, QObject
* target
, QMenu
*menu
)
233 // The public version of the function
235 return; // sanity check
239 // (re-)connect the signals
243 Util::reconnectSignalSlotPair( menu
, SIGNAL( triggered( QAction
* ) ), target
, SLOT( slotMoveSelectedMessagesToFolder( QAction
* ) ) );
246 Util::reconnectSignalSlotPair( menu
, SIGNAL( triggered( QAction
* ) ), target
, SLOT( slotCopySelectedFoldersToFolder( QAction
* ) ) );
249 Util::reconnectSignalSlotPair( menu
, SIGNAL( triggered( QAction
* ) ), target
, SLOT( slotCopySelectedMessagesToFolder( QAction
* ) ) );
252 Util::reconnectSignalSlotPair( menu
, SIGNAL( triggered( QAction
* ) ), target
, SLOT( slotCopySelectedFoldersToFolder( QAction
* ) ) );
256 if ( topLevelItemCount() < 1 )
259 folderToPopupMenuInternal( action
, target
, menu
, invisibleRootItem() );
262 void MainFolderView::slotMoveSelectedFoldersToFolder( QAction
* act
)
264 KMFolder
* f
= static_cast<KMFolder
*>( act
->data().value
<void *>() );
267 moveOrCopyFolders( selectedFolders(), f
, true /*move*/ );
270 void MainFolderView::slotCopySelectedFoldersToFolder( QAction
* act
)
272 KMFolder
* f
= static_cast<KMFolder
*>( act
->data().value
<void *>() );
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
)
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();
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
)
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
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
)
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
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
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.
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
;
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();
418 // nothing in the view at all ? ... this is senseless
419 act
->validityRect
= viewport()->rect();
420 act
->status
= MainFolderViewFoldersDropAction::Reject
;
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 );
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.
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
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 );
513 if ( ( *it
)->isSelected() )
515 // this item is being dragged
516 if ( ( *it
)->parent() != commonParent
)
518 bMovingFromCommonParent
= false;
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() );
535 act
->description
= i18np( "Order folder below %2",
536 "Order %1 folders below %2",
537 draggedFolderCount
, act
->reference
->labelText() );
539 viewport()->update();
543 // not dragging from common parent: both a "copy or move" operation and a positional insert
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
)
552 if ( !( act
->moveTarget
->flags() & Qt::ItemIsDropEnabled
) )
554 if ( !acceptDropCopyOrMoveFolders( act
->moveTarget
) )
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",
565 act
->moveTarget
->labelText(), act
->reference
->labelText() );
567 act
->description
= i18np( "Move or copy folder to %2, order below %3",
568 "Move or copy %1 folders to %2, order below %3",
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,
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
)
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
)
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
)
632 FolderViewItem
*moved
= static_cast< FolderViewItem
* >( *it
);
634 int removedIdx
= commonParent
? commonParent
->indexOfChild( moved
) : indexOfTopLevelItem( moved
);
636 commonParent
->takeChild( removedIdx
);
638 takeTopLevelItem( removedIdx
);
640 if ( removedIdx
> refIdx
)
644 commonParent
->insertChild( refIdx
, moved
);
646 insertTopLevelItem( refIdx
, moved
);
649 setUpdatesEnabled( true );
651 fixSortingKeysForChildren( commonParent
? commonParent
: invisibleRootItem() );
652 sortByColumn( LabelColumn
, Qt::AscendingOrder
);
654 viewport()->update();
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
)
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
);
683 e
->setDropAction( Qt::CopyAction
);
686 e
->setDropAction( Qt::MoveAction
);
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"