dragdropmanager.cpp
00001 /* 00002 Copyright (c) 2009 Stephen Kelly <steveire@gmail.com> 00003 00004 This library is free software; you can redistribute it and/or modify it 00005 under the terms of the GNU Library General Public License as published by 00006 the Free Software Foundation; either version 2 of the License, or (at your 00007 option) any later version. 00008 00009 This library is distributed in the hope that it will be useful, but WITHOUT 00010 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 00011 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public 00012 License for more details. 00013 00014 You should have received a copy of the GNU Library General Public License 00015 along with this library; see the file COPYING.LIB. If not, write to the 00016 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 00017 02110-1301, USA. 00018 */ 00019 00020 #include "dragdropmanager_p.h" 00021 #include "specialcollectionattribute_p.h" 00022 #include "collectionutils_p.h" 00023 00024 #include <QtGui/QApplication> 00025 #include <QtGui/QDropEvent> 00026 #include <QtGui/QMenu> 00027 00028 #include <KDE/KIcon> 00029 #include <KDE/KLocale> 00030 #include <KDE/KUrl> 00031 00032 #include "akonadi/collection.h" 00033 #include "akonadi/entitytreemodel.h" 00034 00035 using namespace Akonadi; 00036 00037 DragDropManager::DragDropManager( QAbstractItemView *view ) 00038 : mShowDropActionMenu( true ), m_view( view ) 00039 { 00040 } 00041 00042 Akonadi::Collection DragDropManager::currentDropTarget( QDropEvent *event ) const 00043 { 00044 const QModelIndex index = m_view->indexAt( event->pos() ); 00045 Collection collection = m_view->model()->data( index, EntityTreeModel::CollectionRole ).value<Collection>(); 00046 if ( !collection.isValid() ) { 00047 const Item item = m_view->model()->data( index, EntityTreeModel::ItemRole ).value<Item>(); 00048 if ( item.isValid() ) 00049 collection = m_view->model()->data( index.parent(), EntityTreeModel::CollectionRole ).value<Collection>(); 00050 } 00051 00052 return collection; 00053 } 00054 00055 bool DragDropManager::dropAllowed( QDragMoveEvent *event ) const 00056 { 00057 // Check if the collection under the cursor accepts this data type 00058 const Collection targetCollection = currentDropTarget( event ); 00059 if ( targetCollection.isValid() ) { 00060 const QStringList supportedContentTypes = targetCollection.contentMimeTypes(); 00061 00062 const QMimeData *data = event->mimeData(); 00063 const KUrl::List urls = KUrl::List::fromMimeData( data ); 00064 foreach ( const KUrl &url, urls ) { 00065 const Collection collection = Collection::fromUrl( url ); 00066 if ( collection.isValid() ) { 00067 if ( !supportedContentTypes.contains( Collection::mimeType() ) ) 00068 break; 00069 00070 // Check if we don't try to drop on one of the children 00071 if ( hasAncestor( m_view->indexAt( event->pos() ), collection.id() ) ) 00072 break; 00073 } else { // This is an item. 00074 const QString type = url.queryItems()[ QString::fromLatin1( "type" ) ]; 00075 if ( !supportedContentTypes.contains( type ) ) 00076 break; 00077 } 00078 00079 return true; 00080 } 00081 } 00082 00083 return false; 00084 } 00085 00086 bool DragDropManager::hasAncestor( const QModelIndex &_index, Collection::Id parentId ) const 00087 { 00088 QModelIndex index( _index ); 00089 while ( index.isValid() ) { 00090 if ( m_view->model()->data( index, EntityTreeModel::CollectionIdRole ).toLongLong() == parentId ) 00091 return true; 00092 00093 index = index.parent(); 00094 } 00095 00096 return false; 00097 } 00098 00099 bool DragDropManager::processDropEvent( QDropEvent *event, bool &menuCanceled, bool dropOnItem ) 00100 { 00101 const Collection targetCollection = currentDropTarget( event ); 00102 if ( !targetCollection.isValid() ) 00103 return false; 00104 00105 const QStringList supportedContentTypes = targetCollection.contentMimeTypes(); 00106 00107 const QMimeData *data = event->mimeData(); 00108 const KUrl::List urls = KUrl::List::fromMimeData( data ); 00109 foreach ( const KUrl &url, urls ) { 00110 const Collection collection = Collection::fromUrl( url ); 00111 if( !collection.isValid() ) { 00112 if ( !dropOnItem ) { 00113 return false; 00114 } 00115 } 00116 } 00117 00118 int actionCount = 0; 00119 Qt::DropAction defaultAction; 00120 // TODO check if the source supports moving 00121 00122 bool moveAllowed, copyAllowed, linkAllowed; 00123 moveAllowed = copyAllowed = linkAllowed = false; 00124 00125 if ( (targetCollection.rights() & (Collection::CanCreateCollection | Collection::CanCreateItem)) 00126 && (event->possibleActions() & Qt::MoveAction) ) { 00127 moveAllowed = true; 00128 } 00129 if ( (targetCollection.rights() & (Collection::CanCreateCollection | Collection::CanCreateItem)) 00130 && (event->possibleActions() & Qt::CopyAction) ) { 00131 copyAllowed = true; 00132 } 00133 00134 if ( (targetCollection.rights() & Collection::CanLinkItem) && (event->possibleActions() & Qt::LinkAction) ) { 00135 linkAllowed = true; 00136 } 00137 00138 if ( !moveAllowed && !copyAllowed && !linkAllowed ) { 00139 kDebug() << "Cannot drop here:" << event->possibleActions() << m_view->model()->supportedDragActions() << m_view->model()->supportedDropActions(); 00140 return false; 00141 } 00142 00143 // first check whether the user pressed a modifier key to select a specific action 00144 if ( (QApplication::keyboardModifiers() & Qt::ControlModifier) && 00145 (QApplication::keyboardModifiers() & Qt::ShiftModifier) ) { 00146 if ( linkAllowed ) { 00147 defaultAction = Qt::LinkAction; 00148 actionCount = 1; 00149 } else 00150 return false; 00151 } else if ( (QApplication::keyboardModifiers() & Qt::ControlModifier) ) { 00152 if ( copyAllowed ) { 00153 defaultAction = Qt::CopyAction; 00154 actionCount = 1; 00155 } else 00156 return false; 00157 } else if ( (QApplication::keyboardModifiers() & Qt::ShiftModifier) ) { 00158 if ( moveAllowed ) { 00159 defaultAction = Qt::MoveAction; 00160 actionCount = 1; 00161 } else 00162 return false; 00163 } 00164 00165 if ( actionCount == 1 ) { 00166 kDebug() << "Selecting drop action" << defaultAction << ", there are no other possibilities"; 00167 event->setDropAction( defaultAction ); 00168 return true; 00169 } 00170 00171 if ( !mShowDropActionMenu ) { 00172 if ( moveAllowed ) 00173 defaultAction = Qt::MoveAction; 00174 else if ( copyAllowed ) 00175 defaultAction = Qt::CopyAction; 00176 else if ( linkAllowed ) 00177 defaultAction = Qt::LinkAction; 00178 else 00179 return false; 00180 event->setDropAction( defaultAction ); 00181 return true; 00182 } 00183 00184 // otherwise show up a menu to allow the user to select an action 00185 QMenu popup( m_view ); 00186 QAction* moveDropAction = 0; 00187 QAction* copyDropAction = 0; 00188 QAction* linkAction = 0; 00189 QString sequence; 00190 00191 if ( moveAllowed ) { 00192 sequence = QKeySequence( Qt::ShiftModifier ).toString(); 00193 sequence.chop( 1 ); // chop superfluous '+' 00194 moveDropAction = popup.addAction( KIcon( QString::fromLatin1( "go-jump" ) ), i18n( "&Move Here" ) + QLatin1Char( '\t' ) + sequence ); 00195 } 00196 00197 if ( copyAllowed ) { 00198 sequence = QKeySequence( Qt::ControlModifier ).toString(); 00199 sequence.chop( 1 ); // chop superfluous '+' 00200 copyDropAction = popup.addAction( KIcon( QString::fromLatin1( "edit-copy" ) ), i18n( "&Copy Here" ) + QLatin1Char( '\t' ) + sequence ); 00201 } 00202 00203 if ( linkAllowed ) { 00204 sequence = QKeySequence( Qt::ControlModifier + Qt::ShiftModifier ).toString(); 00205 sequence.chop( 1 ); // chop superfluous '+' 00206 linkAction = popup.addAction( KIcon( QLatin1String( "edit-link" ) ), i18n( "&Link Here" ) + QLatin1Char( '\t' ) + sequence ); 00207 } 00208 00209 popup.addSeparator(); 00210 popup.addAction( KIcon( QString::fromLatin1( "process-stop" ) ), i18n( "C&ancel" ) + QLatin1Char( '\t' ) + QKeySequence( Qt::Key_Escape ).toString() ); 00211 00212 QAction *activatedAction = popup.exec( QCursor::pos() ); 00213 if ( !activatedAction ) { 00214 menuCanceled = true; 00215 return false; 00216 } else if ( activatedAction == moveDropAction ) { 00217 event->setDropAction( Qt::MoveAction ); 00218 } else if ( activatedAction == copyDropAction ) { 00219 event->setDropAction( Qt::CopyAction ); 00220 } else if ( activatedAction == linkAction ) { 00221 event->setDropAction( Qt::LinkAction ); 00222 } else { 00223 menuCanceled = true; 00224 return false; 00225 } 00226 return true; 00227 } 00228 00229 void DragDropManager::startDrag( Qt::DropActions supportedActions ) 00230 { 00231 QModelIndexList indexes; 00232 bool sourceDeletable = true; 00233 foreach ( const QModelIndex &index, m_view->selectionModel()->selectedRows() ) { 00234 if ( !m_view->model()->flags( index ).testFlag( Qt::ItemIsDragEnabled ) ) 00235 continue; 00236 00237 if ( sourceDeletable ) { 00238 Collection source = index.data( EntityTreeModel::CollectionRole ).value<Collection>(); 00239 if ( !source.isValid() ) { 00240 // index points to an item 00241 source = index.data( EntityTreeModel::ParentCollectionRole ).value<Collection>(); 00242 sourceDeletable = source.rights() & Collection::CanDeleteItem; 00243 } else { 00244 // index points to a collection 00245 sourceDeletable = ( source.rights() & Collection::CanDeleteCollection ) && !source.hasAttribute<SpecialCollectionAttribute>() && !CollectionUtils::isVirtual( source ); 00246 } 00247 } 00248 indexes.append( index ); 00249 } 00250 00251 if ( indexes.isEmpty() ) 00252 return; 00253 00254 QMimeData *mimeData = m_view->model()->mimeData( indexes ); 00255 if ( !mimeData ) 00256 return; 00257 00258 QDrag *drag = new QDrag( m_view ); 00259 drag->setMimeData( mimeData ); 00260 if ( indexes.size() > 1 ) { 00261 drag->setPixmap( KIcon( QLatin1String( "document-multiple" ) ).pixmap( QSize( 22, 22 ) ) ); 00262 } else { 00263 QPixmap pixmap = indexes.first().data( Qt::DecorationRole ).value<QIcon>().pixmap( QSize( 22, 22 ) ); 00264 if ( pixmap.isNull() ) 00265 pixmap = KIcon( QLatin1String( "text-plain" ) ).pixmap( QSize( 22, 22 ) ); 00266 drag->setPixmap( pixmap ); 00267 } 00268 00269 if ( !sourceDeletable ) 00270 supportedActions &= ~Qt::MoveAction; 00271 00272 Qt::DropAction defaultAction = Qt::IgnoreAction; 00273 if ( (QApplication::keyboardModifiers() & Qt::ControlModifier) && 00274 (QApplication::keyboardModifiers() & Qt::ShiftModifier) ) { 00275 defaultAction = Qt::LinkAction; 00276 } else if ( (QApplication::keyboardModifiers() & Qt::ControlModifier) ) { 00277 defaultAction = Qt::CopyAction; 00278 } else if ( (QApplication::keyboardModifiers() & Qt::ShiftModifier) ) { 00279 defaultAction = Qt::MoveAction; 00280 } 00281 00282 drag->exec( supportedActions, defaultAction ); 00283 } 00284 00285 bool DragDropManager::showDropActionMenu() const 00286 { 00287 return mShowDropActionMenu; 00288 } 00289 00290 void DragDropManager::setShowDropActionMenu( bool show ) 00291 { 00292 mShowDropActionMenu = show; 00293 }