You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

704 lines
23 KiB

/******************************************************************************
*
* Copyright 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
*
* Based on kmfoldertree.cpp which was Copyrighted by the KMail Development
* Team and didn't report detailed author list.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*****************************************************************************/
#include "mainfolderview.h"
#include "globalsettings.h"
#include "kmfolder.h"
#include "kmfoldermgr.h"
#include "kmmainwidget.h"
#include "util.h"
#include <kmenu.h>
#include <kicon.h>
#include <klocale.h>
#include <QMenu>
#include <QAction>
#include <QVariant>
#include <libkdepim/broadcaststatus.h>
namespace KMail
{
MainFolderView::MainFolderView( KMMainWidget *mainWidget, FolderViewManager *manager, QWidget *parent, const char *name )
: FolderView( mainWidget, manager, parent, "MainFolderView", name )
{
setIconSize( QSize( 16, 16 ) ); // default to smaller icons
}
FolderViewItem * MainFolderView::createItem(
FolderViewItem *parent,
const QString &label,
KMFolder *folder,
KPIM::FolderTreeWidgetItem::Protocol proto,
KPIM::FolderTreeWidgetItem::FolderType type
)
{
if ( parent )
return new FolderViewItem( parent, label, folder, proto, type );
return new FolderViewItem( this, label, folder, proto, type );
}
//=====================================================================================
// Contextual menu related stuff
//
void MainFolderView::fillContextMenuTreeStructureRelatedActions( KMenu *menu, FolderViewItem *item, bool multiSelection )
{
KMFolder *folder = item->folder();
if ( !multiSelection )
{
// Treat the special case of the root and account folders
if ( ( !folder ) || ( folder->noContent() && ( item->folderType() == FolderViewItem::Root ) ) )
{
if ( folder || ( ( item->folderType() == FolderViewItem::Root ) && ( item->protocol() == FolderViewItem::Local ) ) )
{
// local root or account folder
QString createChild = folder ? i18n( "&New Subfolder..." ) : i18n( "&New Folder..." );
menu->addAction(
KIcon("folder-new"), createChild,
this, SLOT( slotAddChildFolder() )
);
}
} else {
// regular (non root, non account) folders
if ( !folder->noChildren() )
menu->addAction(
KIcon( "folder-new" ), i18n( "&New Subfolder..." ),
this, SLOT( slotAddChildFolder() )
);
}
}
if ( folder )
{
// These two will work also for multiple folders
if( item->folderType() != FolderViewItem::Root ) // disable copy/move for root folders since the underlying copy/move code screws up completely on this
{
// copy folder
QMenu *copyMenu = menu->addMenu( KIcon("edit-copy"),
multiSelection ? i18n( "&Copy Folders To" ) :
i18n( "&Copy Folder To" ) );
folderToPopupMenu( CopyFolder, this, copyMenu );
// FIXME: if AT LEAST ONE folder is moveable
if ( folder && folder->isMoveable() )
{
QMenu *moveMenu = menu->addMenu( KIcon( "go-jump" ),
multiSelection ? i18n( "&Move Folders To" ) :
i18n( "&Move Folder To" ) );
folderToPopupMenu( MoveFolder, this, moveMenu );
}
}
if ( !multiSelection && !folder->isSystemFolder() )
menu->addAction( mainWidget()->action( "delete_folder" ) );
}
}
void MainFolderView::fillContextMenuViewStructureRelatedActions( KMenu *menu, FolderViewItem *item, bool /*multiSelection*/ )
{
KMFolder * folder = item->folder();
if ( !folder )
return;
if ( folder->noContent() )
return;
if ( !GlobalSettings::self()->enableFavoriteFolderView() )
return;
// this works for multiple folders too
menu->addAction(
KIcon("bookmark-new"), i18n("Add to Favorite Folders"),
this, SLOT( slotAddToFavorites() )
);
}
//=====================================================================================
// Folder Tree Structure to Popup Menu
//
void MainFolderView::folderToPopupMenuInternal(
MenuAction action, QObject * target, QMenu *menu, QTreeWidgetItem * parentItem
)
{
int childCount = parentItem->childCount();
for ( int idx = 0; idx < childCount; idx++ )
{
FolderViewItem * fvi = static_cast< FolderViewItem * >( parentItem->child( idx ) );
if ( !fvi )
continue;
// skip search folders
if ( fvi->protocol() == KPIM::FolderTreeWidgetItem::Search )
continue;
QString label = fvi->labelText();
label.replace( '&',"&&" );
if ( fvi->childCount() > 0 )
{
// new level
QMenu* popup = new QMenu( menu );
popup->setObjectName( "subMenu" );
popup->setTitle( label );
folderToPopupMenuInternal( action, target, popup, fvi );
bool subMenu = false;
if ( ( action == MoveMessage || action == CopyMessage ) &&
fvi->folder() && !fvi->folder()->noContent() )
subMenu = true;
if ( ( action == MoveFolder || action == CopyFolder )
&& ( !fvi->folder() || ( fvi->folder() && !fvi->folder()->noChildren() ) ) )
subMenu = true;
QString sourceFolderName;
FolderViewItem* srcItem = static_cast< FolderViewItem * >( currentItem() );
if ( srcItem )
sourceFolderName = srcItem->text( 0 );
if (
(action == MoveFolder || action == CopyFolder) &&
fvi->folder() && fvi->folder()->child() &&
fvi->folder()->child()->hasNamedFolder( sourceFolderName )
)
subMenu = false;
if ( subMenu )
{
QAction* act;
if ( action == MoveMessage || action == MoveFolder )
act = popup->addAction( i18n("Move to This Folder") );
else
act = popup->addAction( i18n("Copy to This Folder") );
popup->addSeparator();
act->setData( QVariant::fromValue<void *>( (void *)( fvi->folder() ) ) );
}
menu->addMenu( popup );
} else {
// insert an item
QAction* act = menu->addAction( label );
if ( fvi->folder() )
act->setData( QVariant::fromValue<void *>( (void *)( fvi->folder() ) ) );
bool enabled = (fvi->folder() ? true : false);
if ( fvi->folder() &&
( fvi->folder()->isReadOnly() || fvi->folder()->noContent() ) )
enabled = false;
act->setEnabled( enabled );
}
}
}
void MainFolderView::folderToPopupMenu( MenuAction action, QObject * target, QMenu *menu )
{
// The public version of the function
if ( !menu )
return; // sanity check
menu->clear();
// (re-)connect the signals
switch ( action )
{
case MoveMessage:
Util::reconnectSignalSlotPair( menu, SIGNAL( triggered( QAction * ) ), target, SLOT( slotMoveSelectedMessagesToFolder( QAction* ) ) );
break;
case MoveFolder:
Util::reconnectSignalSlotPair( menu, SIGNAL( triggered( QAction * ) ), target, SLOT( slotCopySelectedFoldersToFolder( QAction* ) ) );
break;
case CopyMessage:
Util::reconnectSignalSlotPair( menu, SIGNAL( triggered( QAction * ) ), target, SLOT( slotCopySelectedMessagesToFolder( QAction* ) ) );
break;
case CopyFolder:
Util::reconnectSignalSlotPair( menu, SIGNAL( triggered( QAction * ) ), target, SLOT( slotCopySelectedFoldersToFolder( QAction* ) ) );
break;
}
if ( topLevelItemCount() < 1 )
return; // done
folderToPopupMenuInternal( action, target, menu, invisibleRootItem() );
}
void MainFolderView::slotMoveSelectedFoldersToFolder( QAction* act )
{
KMFolder * f = static_cast<KMFolder *>( act->data().value<void *>() );
if ( !f )
return;
moveOrCopyFolders( selectedFolders(), f, true /*move*/ );
}
void MainFolderView::slotCopySelectedFoldersToFolder( QAction* act )
{
KMFolder * f = static_cast<KMFolder *>( act->data().value<void *>() );
if ( !f )
return;
moveOrCopyFolders( selectedFolders(), f, false /*copy, don't move*/ );
}
//=======================================================================================
// DND Machinery: we allow moving folders around and eventually sorting stuff by dnd
//
bool MainFolderView::acceptDropCopyOrMoveFolders( FolderViewItem *item )
{
if ( !item )
return false; // obviously
if ( item->protocol() == FolderViewItem::Search )
return false; // nothing can be dragged into search folders
QList< QPointer< KMFolder > > lFolders = DraggedFolderList::get();
if ( lFolders.isEmpty() )
return false; // nothing we can accept
KMFolder *fld = item->folder();
if ( fld )
{
if ( fld->isReadOnly() || fld->noContent() )
return false; // content can't be added to folder
// make sure ALL of the items can be copied or moved to this folder
// parents can't be moved into children so make sure that fld is not
// a child of one of the dragged folders
for ( QList< QPointer< KMFolder > >::Iterator it = lFolders.begin(); it != lFolders.end(); ++it )
{
if ( !( *it ) )
return false; // one of the folders was lost in the way: don't bother with the copy
if ( ( *it ) == fld )
return false; // dragging a folder over itself
if ( ( *it ) == fld->ownerFolder() )
return false; // dragging a folder over its direct owner (parent)
if ( ( *it )->hasDescendant( fld ) )
return false; // dragging a folder over one of its descendants
if ( ! ( ( *it )->ownerFolder() || ( ( *it )->folderType() == KMFolderTypeMbox ) || ( ( *it )->folderType() == KMFolderTypeMaildir ) ) )
return false; // dragging a non local root
}
} else {
if (
( item->protocol() != FolderViewItem::Local ) ||
( item->folderType() != FolderViewItem::Root )
)
return false; // non local top-level folder (can be searches, but nothing can be dragged into at the moment)
// local root for sure: this is a toplevel item, no parents
// just make sure that no dragged folder is already a direct child of local root
for ( QList< QPointer< KMFolder > >::Iterator it = lFolders.begin(); it != lFolders.end(); ++it )
{
if ( !( *it ) )
return false; // one of the folders was lost in the way: don't bother with the copy
if ( ( *it )->parent() == &( kmkernel->folderMgr()->dir() ) )
return false; // dragged folder is a direct child of local top-level
if ( ! ( ( *it )->ownerFolder() || ( ( *it )->folderType() == KMFolderTypeMbox ) || ( ( *it )->folderType() == KMFolderTypeMaildir ) ) )
return false; // dragging a non local root
}
}
return true; // item would accept a copy/move of all the dragged folders
}
class MainFolderViewFoldersDropAction
{
public:
enum DropFoldersStatus
{
Accept, ///< Drag is accepted: we can do something with it
Reject ///< Drag is rejected: can't do anything with it at the moment
};
public:
// out
QRect validityRect; ///< The rect where the results of this structure remain valid
DropFoldersStatus status; ///< Do we accept or reject ?
// out if status == Accept
bool doCopyOrMove; ///< A Copy/Move is required ?
FolderViewItem *moveTarget; ///< Copy/Move where ? (valid only if mustCopyOrMove is true)
bool doPositionalInsert;
MainFolderView::DropInsertPosition insertPosition; ///< Where to insert the data with respect to insertReference ?
FolderViewItem *reference; ///< The reference item that defines our insertions
QString description; ///< Description of the operation that will be done
};
void MainFolderView::computeFoldersDropAction( QDropEvent *e, MainFolderViewFoldersDropAction *act )
{
// Ok, this is quite complex.
//
// It attempts to determine what to do with the currently dragged folders
// if they were dropped on the item in the current mouse position.
//
// The rules:
//
// - If we're in the upper part of an item and dnd sorting is enabled
// then we might be wanting to just place the dragged stuff above the item.
//
// - If we're in the lower part of an item and dnd sorting is enabled
// then we might be wanting to just place the dragged stuff below the item.
//
// - If we're in the middle of an item then we might be wanting to
// move/copy the dragged stuff INSIDE the item.
//
// - If we're in the upper or lower part of an item but the dragged stuff
// has a parent that is not the items's parent then the sorting
// operation might ALSO require a copy or move (to the parent).
//
// - If dnd sorting is not enabled then we always attempt to copy or move stuff INSIDE o->item.
//
// - To complicate the things a bit more, copying/moving might be not possible
// for several reasons...
//
// This function attempts to deal with all these rules and by the meantime
// give to the user some visual feedback about what is going on.
//
// first of all, we allow only folder drags coming from THIS view
if ( e->source() != this )
{
act->validityRect = viewport()->rect();
act->status = MainFolderViewFoldersDropAction::Reject;
return;
}
act->reference = static_cast<FolderViewItem *>( itemAt( e->pos() ) );
int draggedFolderCount = DraggedFolderList::get().count();
bool bWasOutsideItem = false;
if ( !act->reference )
{
// not over an item: try to use the last item in the view as reference
bWasOutsideItem = true;
int cc = topLevelItemCount();
if ( cc < 1 )
{
// nothing in the view at all ? ... this is senseless
act->validityRect = viewport()->rect();
act->status = MainFolderViewFoldersDropAction::Reject;
return;
}
act->reference = static_cast<FolderViewItem *>( topLevelItem( cc - 1 ) );
// now item != 0 (and item is visible)
}
QRect r = visualItemRect( act->reference );
QRect mouseRect( e->pos().x() - 1, e->pos().y() - 1, 2, 2 );
// set defaults
act->status = MainFolderViewFoldersDropAction::Reject;
act->validityRect = mouseRect;
bool bCopyOrMoveToCurrentIsPossible = \
!bWasOutsideItem && /* a copy/move operation would look bad in this case */
( act->reference->flags() & Qt::ItemIsDropEnabled ) && /* item has drops disabled */
acceptDropCopyOrMoveFolders( act->reference ); /* item can't accept the drop for some other reason */
if ( sortingPolicy() != SortByDragAndDropKey )
{
// when sorting by dnd is disabled then we always act on the current item
act->validityRect = r;
if ( bCopyOrMoveToCurrentIsPossible )
{
// sorting by dnd is disabled but we can copy/move messages to the reference folder.
act->moveTarget = act->reference;
act->status = MainFolderViewFoldersDropAction::Accept;
act->doCopyOrMove = true;
act->doPositionalInsert = false;
act->description = i18np( "Move or copy folder to %2",
"Move or copy %1 folders to %2", draggedFolderCount,
act->moveTarget->labelText() );
} // else if we cannot move folders to the current item and sorting by dnd is disabled
// hence there is nothing more we can do here: ignore.
return;
}
// sortingPolicy() == SortByDragAndDropKey
// now try to actually compute the drop position
act->doPositionalInsert = false;
if ( e->pos().y() < ( r.top() + ( r.height() / 4 ) ) )
{
// would drop above item
act->validityRect = QRect( r.left(), r.top(), r.width(), r.height() / 4 );
act->doPositionalInsert = true;
act->insertPosition = AboveReference;
} else if ( e->pos().y() > ( r.bottom() - ( r.height() / 4 ) ) )
{
act->validityRect = QRect( r.left(), r.bottom() - ( r.height() / 4 ), r.width(), r.height() / 4 );
act->doPositionalInsert = true;
act->insertPosition = BelowReference;
}
if ( !act->doPositionalInsert )
{
// we're actually exactly over the item
act->validityRect = QRect( r.left(), r.top() + ( r.height() / 4 ), r.width(), r.height() / 2 );
if ( bCopyOrMoveToCurrentIsPossible )
{
// we can move the folders.
act->moveTarget = act->reference;
act->status = MainFolderViewFoldersDropAction::Accept;
act->doCopyOrMove = true;
act->description = i18np( "Move or copy folder to %2",
"Move or copy %1 folders to %2",
draggedFolderCount, act->moveTarget->labelText() );
} // else if we cannot move folders to the current item thus ignore
return;
}
// Ok, now we're trying to do positional insert for sure.
// If we're moving items from the act->reference's parent then
// the thingie is easy: no copy or move is needed.
QTreeWidgetItem *commonParent = static_cast< QTreeWidgetItem * >( act->reference )->parent();
// commonParent might be null here!
// please note that the drag comes from our view so we're actually
// dragging the selected items!
bool bMovingFromCommonParent = true;
QTreeWidgetItemIterator it( this );
while ( *it )
{
if ( ( *it )->isSelected() )
{
// this item is being dragged
if ( ( *it )->parent() != commonParent )
{
bMovingFromCommonParent = false;
break;
}
}
++it;
}
if ( bMovingFromCommonParent )
{
// this is just a sort inside a single hierarchy level
act->status = MainFolderViewFoldersDropAction::Accept;
act->doCopyOrMove = false;
if ( act->insertPosition == AboveReference )
act->description = i18np( "Order folder above %2",
"Order %1 folders above %2",
draggedFolderCount, act->reference->labelText() );
else
act->description = i18np( "Order folder below %2",
"Order %1 folders below %2",
draggedFolderCount, act->reference->labelText() );
viewport()->update();
return;
}
// not dragging from common parent: both a "copy or move" operation and a positional insert
// is needed...
// check if commonParent can accept a "copy or move" operation.
act->moveTarget = static_cast< FolderViewItem * >( commonParent );
// if moving to common parent is not possible then sorting is also not possible
if ( !act->moveTarget )
return;
if ( !( act->moveTarget->flags() & Qt::ItemIsDropEnabled ) )
return;
if ( !acceptDropCopyOrMoveFolders( act->moveTarget ) )
return;
// moving to a common parent (act->moveTarget) is possible
act->status = MainFolderViewFoldersDropAction::Accept;
act->doCopyOrMove = true;
if ( act->insertPosition == AboveReference )
act->description = i18np( "Move or copy folder to %2, order above %3",
"Move or copy %1 folders to %2, order above %3",
draggedFolderCount,
act->moveTarget->labelText(), act->reference->labelText() );
else
act->description = i18np( "Move or copy folder to %2, order below %3",
"Move or copy %1 folders to %2, order below %3",
draggedFolderCount,
act->moveTarget->labelText(), act->reference->labelText() );
}
void MainFolderView::handleFoldersDragMoveEvent( QDragMoveEvent *e )
{
MainFolderViewFoldersDropAction act;
computeFoldersDropAction( e, &act );
if ( act.status == MainFolderViewFoldersDropAction::Accept )
{
e->accept( act.validityRect );
setDropIndicatorData(
act.doCopyOrMove ? act.moveTarget : 0,
act.doPositionalInsert ? act.reference : 0,
act.insertPosition
);
} else {
e->ignore( act.validityRect );
setDropIndicatorData( 0, 0 );
}
KPIM::BroadcastStatus::instance()->setStatusMsg( act.description );
}
void MainFolderView::handleFoldersDropEvent( QDropEvent *e )
{
MainFolderViewFoldersDropAction act;
computeFoldersDropAction( e, &act );
if ( act.status != MainFolderViewFoldersDropAction::Accept )
{
e->ignore();
return;
}
e->accept();
if ( !act.doCopyOrMove )
{
// assert( act.doPositionalInsert );
// only a move (within the same parent)
QTreeWidgetItem *commonParent = static_cast< QTreeWidgetItem * >( act.reference )->parent();
int refIdx = commonParent ? commonParent->indexOfChild( act.reference ) : indexOfTopLevelItem( act.reference );
if ( act.insertPosition == AboveReference )
refIdx--;
// we're dragging items from THIS view
setUpdatesEnabled( false );
QList< QTreeWidgetItem * > lSelected = selectedItems();
for ( QList< QTreeWidgetItem * >::Iterator it = lSelected.begin(); it != lSelected.end(); ++it )
{
if ( !( *it ) )
continue; // umphf
FolderViewItem *moved = static_cast< FolderViewItem * >( *it );
int removedIdx = commonParent ? commonParent->indexOfChild( moved ) : indexOfTopLevelItem( moved );
if ( commonParent )
commonParent->takeChild( removedIdx );
else
takeTopLevelItem( removedIdx );
if ( removedIdx > refIdx )
refIdx++;
if ( commonParent )
commonParent->insertChild( refIdx, moved );
else
insertTopLevelItem( refIdx, moved );
}
setUpdatesEnabled( true );
fixSortingKeysForChildren( commonParent ? commonParent : invisibleRootItem() );
sortByColumn( LabelColumn, Qt::AscendingOrder );
viewport()->update();
return;
}
// must do a copy or move
// folders must be first copied/moved and then sorted
// Get the dragged folder list
QList<QPointer<KMFolder> > lFolders = DraggedFolderList::get();
if ( lFolders.isEmpty() )
return; // nothing we can accept
bool bCanMove = true;
// Check that each pointer is not null and compute possible dnd modes
for ( QList<QPointer<KMFolder> >::Iterator it = lFolders.begin(); it != lFolders.end(); ++it )
{
if ( !( *it ) )
return; // yes, that's pessimistic, but if we lost a folder then something weird happened anyway: stay safe
if ( !( ( *it )->isMoveable() ) )
bCanMove = false; // at least one folder is not moveable, allow copy only
}
// ask for dnd mode now
int action = dragMode( true, bCanMove );
switch ( action )
{
case DragCopy:
e->setDropAction( Qt::CopyAction );
break;
case DragMove:
e->setDropAction( Qt::MoveAction );
break;
default:
return; // cancelled
break;
}
if ( act.doPositionalInsert )
{
// will need positional insertion AFTER the items are copied
// FIXME: this is missing... let's see if somebody complains before I implement it :P
}
moveOrCopyFolders( lFolders, act.moveTarget->folder(), (action == DragMove) );
}
}
#include "mainfolderview.moc"