File indexing completed on 2024-06-16 04:50:37
0001 /* 0002 SPDX-FileCopyrightText: 2009 Stephen Kelly <steveire@gmail.com> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "akonadiwidgets_debug.h" 0008 #include "collectionutils.h" 0009 #include "dragdropmanager_p.h" 0010 #include "specialcollectionattribute.h" 0011 #include <QApplication> 0012 #include <QDrag> 0013 #include <QDropEvent> 0014 #include <QMenu> 0015 0016 #include <KLocalizedString> 0017 #include <QMimeData> 0018 #include <QUrl> 0019 #include <QUrlQuery> 0020 0021 #include "collection.h" 0022 #include "entitytreemodel.h" 0023 0024 using namespace Akonadi; 0025 0026 DragDropManager::DragDropManager(QAbstractItemView *view) 0027 : m_view(view) 0028 { 0029 } 0030 0031 Akonadi::Collection DragDropManager::currentDropTarget(QDropEvent *event) const 0032 { 0033 const QModelIndex index = m_view->indexAt(event->position().toPoint()); 0034 auto collection = m_view->model()->data(index, EntityTreeModel::CollectionRole).value<Collection>(); 0035 if (!collection.isValid()) { 0036 const Item item = m_view->model()->data(index, EntityTreeModel::ItemRole).value<Item>(); 0037 if (item.isValid()) { 0038 collection = m_view->model()->data(index.parent(), EntityTreeModel::CollectionRole).value<Collection>(); 0039 } 0040 } 0041 0042 return collection; 0043 } 0044 0045 bool DragDropManager::dropAllowed(QDragMoveEvent *event) const 0046 { 0047 // Check if the collection under the cursor accepts this data type 0048 const Collection targetCollection = currentDropTarget(event); 0049 if (targetCollection.isValid()) { 0050 const QStringList supportedContentTypes = targetCollection.contentMimeTypes(); 0051 0052 const QMimeData *data = event->mimeData(); 0053 if (!data) { 0054 return false; 0055 } 0056 const QList<QUrl> urls = data->urls(); 0057 for (const QUrl &url : urls) { 0058 const Collection collection = Collection::fromUrl(url); 0059 if (collection.isValid()) { 0060 if (!supportedContentTypes.contains(Collection::mimeType()) && !supportedContentTypes.contains(Collection::virtualMimeType())) { 0061 break; 0062 } 0063 0064 // Check if we don't try to drop on one of the children 0065 if (hasAncestor(m_view->indexAt(event->position().toPoint()), collection.id())) { 0066 break; 0067 } 0068 } else { // This is an item. 0069 const QList<QPair<QString, QString>> query = QUrlQuery(url).queryItems(); 0070 for (int i = 0; i < query.count(); ++i) { 0071 if (query.at(i).first == QLatin1StringView("type")) { 0072 const QString type = query.at(i).second; 0073 if (!supportedContentTypes.contains(type)) { 0074 break; 0075 } 0076 } 0077 } 0078 } 0079 return true; 0080 } 0081 } 0082 0083 return false; 0084 } 0085 0086 bool DragDropManager::hasAncestor(const QModelIndex &_index, Collection::Id parentId) const 0087 { 0088 QModelIndex index(_index); 0089 while (index.isValid()) { 0090 if (m_view->model()->data(index, EntityTreeModel::CollectionIdRole).toLongLong() == parentId) { 0091 return true; 0092 } 0093 0094 index = index.parent(); 0095 } 0096 0097 return false; 0098 } 0099 0100 bool DragDropManager::processDropEvent(QDropEvent *event, bool &menuCanceled, bool dropOnItem) 0101 { 0102 const Collection targetCollection = currentDropTarget(event); 0103 if (!targetCollection.isValid()) { 0104 return false; 0105 } 0106 0107 if (!mIsManualSortingActive && !dropOnItem) { 0108 return false; 0109 } 0110 0111 const QMimeData *data = event->mimeData(); 0112 if (!data) { 0113 return false; 0114 } 0115 const QList<QUrl> urls = data->urls(); 0116 for (const QUrl &url : urls) { 0117 const Collection collection = Collection::fromUrl(url); 0118 if (!collection.isValid()) { 0119 if (!dropOnItem) { 0120 return false; 0121 } 0122 } 0123 } 0124 0125 int actionCount = 0; 0126 Qt::DropAction defaultAction; 0127 // TODO check if the source supports moving 0128 0129 bool moveAllowed; 0130 bool copyAllowed; 0131 bool linkAllowed; 0132 moveAllowed = copyAllowed = linkAllowed = false; 0133 0134 if ((targetCollection.rights() & (Collection::CanCreateCollection | Collection::CanCreateItem)) && (event->possibleActions() & Qt::MoveAction)) { 0135 moveAllowed = true; 0136 } 0137 if ((targetCollection.rights() & (Collection::CanCreateCollection | Collection::CanCreateItem)) && (event->possibleActions() & Qt::CopyAction)) { 0138 copyAllowed = true; 0139 } 0140 0141 if ((targetCollection.rights() & Collection::CanLinkItem) && (event->possibleActions() & Qt::LinkAction)) { 0142 linkAllowed = true; 0143 } 0144 0145 if (mIsManualSortingActive && !dropOnItem) { 0146 moveAllowed = true; 0147 copyAllowed = false; 0148 linkAllowed = false; 0149 } 0150 0151 if (!moveAllowed && !copyAllowed && !linkAllowed) { 0152 qCDebug(AKONADIWIDGETS_LOG) << "Cannot drop here:" << event->possibleActions() << m_view->model()->supportedDragActions() 0153 << m_view->model()->supportedDropActions(); 0154 return false; 0155 } 0156 0157 // first check whether the user pressed a modifier key to select a specific action 0158 if ((QApplication::keyboardModifiers() & Qt::ControlModifier) && (QApplication::keyboardModifiers() & Qt::ShiftModifier)) { 0159 if (linkAllowed) { 0160 defaultAction = Qt::LinkAction; 0161 actionCount = 1; 0162 } else { 0163 return false; 0164 } 0165 } else if ((QApplication::keyboardModifiers() & Qt::ControlModifier)) { 0166 if (copyAllowed) { 0167 defaultAction = Qt::CopyAction; 0168 actionCount = 1; 0169 } else { 0170 return false; 0171 } 0172 } else if ((QApplication::keyboardModifiers() & Qt::ShiftModifier)) { 0173 if (moveAllowed) { 0174 defaultAction = Qt::MoveAction; 0175 actionCount = 1; 0176 } else { 0177 return false; 0178 } 0179 } 0180 0181 if (actionCount == 1) { 0182 qCDebug(AKONADIWIDGETS_LOG) << "Selecting drop action" << defaultAction << ", there are no other possibilities"; 0183 event->setDropAction(defaultAction); 0184 return true; 0185 } 0186 0187 if (!mShowDropActionMenu) { 0188 if (moveAllowed) { 0189 defaultAction = Qt::MoveAction; 0190 } else if (copyAllowed) { 0191 defaultAction = Qt::CopyAction; 0192 } else if (linkAllowed) { 0193 defaultAction = Qt::LinkAction; 0194 } else { 0195 return false; 0196 } 0197 event->setDropAction(defaultAction); 0198 return true; 0199 } 0200 0201 // otherwise show up a menu to allow the user to select an action 0202 QMenu popup(m_view); 0203 QAction *moveDropAction = nullptr; 0204 QAction *copyDropAction = nullptr; 0205 QAction *linkAction = nullptr; 0206 QString sequence; 0207 0208 if (moveAllowed) { 0209 sequence = QKeySequence(Qt::ShiftModifier).toString(); 0210 sequence.chop(1); // chop superfluous '+' 0211 moveDropAction = popup.addAction(QIcon::fromTheme(QStringLiteral("edit-move"), QIcon::fromTheme(QStringLiteral("go-jump"))), 0212 i18n("&Move Here") + QLatin1Char('\t') + sequence); 0213 } 0214 0215 if (copyAllowed) { 0216 sequence = QKeySequence(Qt::ControlModifier).toString(); 0217 sequence.chop(1); // chop superfluous '+' 0218 copyDropAction = popup.addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("&Copy Here") + QLatin1Char('\t') + sequence); 0219 } 0220 0221 if (linkAllowed) { 0222 sequence = QKeySequence(Qt::ControlModifier | Qt::ShiftModifier).toString(); 0223 sequence.chop(1); // chop superfluous '+' 0224 linkAction = popup.addAction(QIcon::fromTheme(QStringLiteral("edit-link")), i18n("&Link Here") + QLatin1Char('\t') + sequence); 0225 } 0226 0227 popup.addSeparator(); 0228 QAction *cancelAction = 0229 popup.addAction(QIcon::fromTheme(QStringLiteral("process-stop")), i18n("C&ancel") + QLatin1Char('\t') + QKeySequence(Qt::Key_Escape).toString()); 0230 0231 QAction *activatedAction = popup.exec(m_view->viewport()->mapToGlobal(event->position().toPoint())); 0232 if (!activatedAction || (activatedAction == cancelAction)) { 0233 menuCanceled = true; 0234 return false; 0235 } else if (activatedAction == moveDropAction) { 0236 event->setDropAction(Qt::MoveAction); 0237 } else if (activatedAction == copyDropAction) { 0238 event->setDropAction(Qt::CopyAction); 0239 } else if (activatedAction == linkAction) { 0240 event->setDropAction(Qt::LinkAction); 0241 } 0242 return true; 0243 } 0244 0245 void DragDropManager::startDrag(Qt::DropActions supportedActions) 0246 { 0247 QModelIndexList indexes; 0248 bool sourceDeletable = true; 0249 const QModelIndexList lstModel = m_view->selectionModel()->selectedRows(); 0250 for (const QModelIndex &index : lstModel) { 0251 if (!m_view->model()->flags(index).testFlag(Qt::ItemIsDragEnabled)) { 0252 continue; 0253 } 0254 0255 if (sourceDeletable) { 0256 auto source = index.data(EntityTreeModel::CollectionRole).value<Collection>(); 0257 if (!source.isValid()) { 0258 // index points to an item 0259 source = index.data(EntityTreeModel::ParentCollectionRole).value<Collection>(); 0260 sourceDeletable = source.rights() & Collection::CanDeleteItem; 0261 } else { 0262 // index points to a collection 0263 sourceDeletable = 0264 (source.rights() & Collection::CanDeleteCollection) && !source.hasAttribute<SpecialCollectionAttribute>() && !source.isVirtual(); 0265 } 0266 } 0267 indexes.append(index); 0268 } 0269 0270 if (indexes.isEmpty()) { 0271 return; 0272 } 0273 0274 QMimeData *mimeData = m_view->model()->mimeData(indexes); 0275 if (!mimeData) { 0276 return; 0277 } 0278 0279 auto drag = new QDrag(m_view); 0280 drag->setMimeData(mimeData); 0281 if (indexes.size() > 1) { 0282 drag->setPixmap(QIcon::fromTheme(QStringLiteral("document-multiple")).pixmap(QSize(22, 22))); 0283 } else { 0284 QPixmap pixmap = indexes.first().data(Qt::DecorationRole).value<QIcon>().pixmap(QSize(22, 22)); 0285 if (pixmap.isNull()) { 0286 pixmap = QIcon::fromTheme(QStringLiteral("text-plain")).pixmap(QSize(22, 22)); 0287 } 0288 drag->setPixmap(pixmap); 0289 } 0290 0291 if (!sourceDeletable) { 0292 supportedActions &= ~Qt::MoveAction; 0293 } 0294 0295 Qt::DropAction defaultAction = Qt::IgnoreAction; 0296 if ((QApplication::keyboardModifiers() & Qt::ControlModifier) && (QApplication::keyboardModifiers() & Qt::ShiftModifier)) { 0297 defaultAction = Qt::LinkAction; 0298 } else if ((QApplication::keyboardModifiers() & Qt::ControlModifier)) { 0299 defaultAction = Qt::CopyAction; 0300 } else if ((QApplication::keyboardModifiers() & Qt::ShiftModifier)) { 0301 defaultAction = Qt::MoveAction; 0302 } 0303 0304 drag->exec(supportedActions, defaultAction); 0305 } 0306 0307 bool DragDropManager::showDropActionMenu() const 0308 { 0309 return mShowDropActionMenu; 0310 } 0311 0312 void DragDropManager::setShowDropActionMenu(bool show) 0313 { 0314 mShowDropActionMenu = show; 0315 } 0316 0317 bool DragDropManager::isManualSortingActive() const 0318 { 0319 return mIsManualSortingActive; 0320 } 0321 0322 void DragDropManager::setManualSortingActive(bool active) 0323 { 0324 mIsManualSortingActive = active; 0325 }