Akonadi

dragdropmanager.cpp
1/*
2 SPDX-FileCopyrightText: 2009 Stephen Kelly <steveire@gmail.com>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "akonadiwidgets_debug.h"
8#include "collectionutils.h"
9#include "dragdropmanager_p.h"
10#include "specialcollectionattribute.h"
11#include <QApplication>
12#include <QDrag>
13#include <QDropEvent>
14#include <QMenu>
15
16#include <KLocalizedString>
17#include <QMimeData>
18#include <QUrl>
19#include <QUrlQuery>
20
21#include "collection.h"
22#include "entitytreemodel.h"
23
24using namespace Akonadi;
25
26DragDropManager::DragDropManager(QAbstractItemView *view)
27 : m_view(view)
28{
29}
30
31Akonadi::Collection DragDropManager::currentDropTarget(QDropEvent *event) const
32{
33 const QModelIndex index = m_view->indexAt(event->position().toPoint());
34 auto collection = m_view->model()->data(index, EntityTreeModel::CollectionRole).value<Collection>();
35 if (!collection.isValid()) {
36 const Item item = m_view->model()->data(index, EntityTreeModel::ItemRole).value<Item>();
37 if (item.isValid()) {
38 collection = m_view->model()->data(index.parent(), EntityTreeModel::CollectionRole).value<Collection>();
39 }
40 }
41
42 return collection;
43}
44
45bool DragDropManager::dropAllowed(QDragMoveEvent *event) const
46{
47 // Check if the collection under the cursor accepts this data type
48 const Collection targetCollection = currentDropTarget(event);
49 if (targetCollection.isValid()) {
50 const QStringList supportedContentTypes = targetCollection.contentMimeTypes();
51
52 const QMimeData *data = event->mimeData();
53 if (!data) {
54 return false;
55 }
56 const QList<QUrl> urls = data->urls();
57 for (const QUrl &url : urls) {
58 const Collection collection = Collection::fromUrl(url);
59 if (collection.isValid()) {
60 if (!supportedContentTypes.contains(Collection::mimeType()) && !supportedContentTypes.contains(Collection::virtualMimeType())) {
61 break;
62 }
63
64 // Check if we don't try to drop on one of the children
65 if (hasAncestor(m_view->indexAt(event->position().toPoint()), collection.id())) {
66 break;
67 }
68 } else { // This is an item.
70 for (int i = 0; i < query.count(); ++i) {
71 if (query.at(i).first == QLatin1StringView("type")) {
72 const QString type = query.at(i).second;
73 if (!supportedContentTypes.contains(type)) {
74 break;
75 }
76 }
77 }
78 }
79 return true;
80 }
81 }
82
83 return false;
84}
85
86bool DragDropManager::hasAncestor(const QModelIndex &_index, Collection::Id parentId) const
87{
88 QModelIndex index(_index);
89 while (index.isValid()) {
90 if (m_view->model()->data(index, EntityTreeModel::CollectionIdRole).toLongLong() == parentId) {
91 return true;
92 }
93
94 index = index.parent();
95 }
96
97 return false;
98}
99
100bool DragDropManager::processDropEvent(QDropEvent *event, bool &menuCanceled, bool dropOnItem)
101{
102 const Collection targetCollection = currentDropTarget(event);
103 if (!targetCollection.isValid()) {
104 return false;
105 }
106
107 if (!mIsManualSortingActive && !dropOnItem) {
108 return false;
109 }
110
111 const QMimeData *data = event->mimeData();
112 if (!data) {
113 return false;
114 }
115 const QList<QUrl> urls = data->urls();
116 for (const QUrl &url : urls) {
117 const Collection collection = Collection::fromUrl(url);
118 if (!collection.isValid()) {
119 if (!dropOnItem) {
120 return false;
121 }
122 }
123 }
124
125 int actionCount = 0;
126 Qt::DropAction defaultAction;
127 // TODO check if the source supports moving
128
129 bool moveAllowed;
130 bool copyAllowed;
131 bool linkAllowed;
132 moveAllowed = copyAllowed = linkAllowed = false;
133
134 if ((targetCollection.rights() & (Collection::CanCreateCollection | Collection::CanCreateItem)) && (event->possibleActions() & Qt::MoveAction)) {
135 moveAllowed = true;
136 }
137 if ((targetCollection.rights() & (Collection::CanCreateCollection | Collection::CanCreateItem)) && (event->possibleActions() & Qt::CopyAction)) {
138 copyAllowed = true;
139 }
140
141 if ((targetCollection.rights() & Collection::CanLinkItem) && (event->possibleActions() & Qt::LinkAction)) {
142 linkAllowed = true;
143 }
144
145 if (mIsManualSortingActive && !dropOnItem) {
146 moveAllowed = true;
147 copyAllowed = false;
148 linkAllowed = false;
149 }
150
151 if (!moveAllowed && !copyAllowed && !linkAllowed) {
152 qCDebug(AKONADIWIDGETS_LOG) << "Cannot drop here:" << event->possibleActions() << m_view->model()->supportedDragActions()
153 << m_view->model()->supportedDropActions();
154 return false;
155 }
156
157 // first check whether the user pressed a modifier key to select a specific action
159 if (linkAllowed) {
160 defaultAction = Qt::LinkAction;
161 actionCount = 1;
162 } else {
163 return false;
164 }
166 if (copyAllowed) {
167 defaultAction = Qt::CopyAction;
168 actionCount = 1;
169 } else {
170 return false;
171 }
173 if (moveAllowed) {
174 defaultAction = Qt::MoveAction;
175 actionCount = 1;
176 } else {
177 return false;
178 }
179 }
180
181 if (actionCount == 1) {
182 qCDebug(AKONADIWIDGETS_LOG) << "Selecting drop action" << defaultAction << ", there are no other possibilities";
183 event->setDropAction(defaultAction);
184 return true;
185 }
186
187 if (!mShowDropActionMenu) {
188 if (moveAllowed) {
189 defaultAction = Qt::MoveAction;
190 } else if (copyAllowed) {
191 defaultAction = Qt::CopyAction;
192 } else if (linkAllowed) {
193 defaultAction = Qt::LinkAction;
194 } else {
195 return false;
196 }
197 event->setDropAction(defaultAction);
198 return true;
199 }
200
201 // otherwise show up a menu to allow the user to select an action
202 QMenu popup(m_view);
203 QAction *moveDropAction = nullptr;
204 QAction *copyDropAction = nullptr;
205 QAction *linkAction = nullptr;
206 QString sequence;
207
208 if (moveAllowed) {
210 sequence.chop(1); // chop superfluous '+'
211 moveDropAction = popup.addAction(QIcon::fromTheme(QStringLiteral("edit-move"), QIcon::fromTheme(QStringLiteral("go-jump"))),
212 i18n("&Move Here") + QLatin1Char('\t') + sequence);
213 }
214
215 if (copyAllowed) {
217 sequence.chop(1); // chop superfluous '+'
218 copyDropAction = popup.addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("&Copy Here") + QLatin1Char('\t') + sequence);
219 }
220
221 if (linkAllowed) {
223 sequence.chop(1); // chop superfluous '+'
224 linkAction = popup.addAction(QIcon::fromTheme(QStringLiteral("edit-link")), i18n("&Link Here") + QLatin1Char('\t') + sequence);
225 }
226
227 popup.addSeparator();
228 QAction *cancelAction =
229 popup.addAction(QIcon::fromTheme(QStringLiteral("process-stop")), i18n("C&ancel") + QLatin1Char('\t') + QKeySequence(Qt::Key_Escape).toString());
230
231 QAction *activatedAction = popup.exec(m_view->viewport()->mapToGlobal(event->position().toPoint()));
232 if (!activatedAction || (activatedAction == cancelAction)) {
233 menuCanceled = true;
234 return false;
235 } else if (activatedAction == moveDropAction) {
236 event->setDropAction(Qt::MoveAction);
237 } else if (activatedAction == copyDropAction) {
238 event->setDropAction(Qt::CopyAction);
239 } else if (activatedAction == linkAction) {
240 event->setDropAction(Qt::LinkAction);
241 }
242 return true;
243}
244
245void DragDropManager::startDrag(Qt::DropActions supportedActions)
246{
247 QModelIndexList indexes;
248 bool sourceDeletable = true;
249 const QModelIndexList lstModel = m_view->selectionModel()->selectedRows();
250 for (const QModelIndex &index : lstModel) {
251 if (!m_view->model()->flags(index).testFlag(Qt::ItemIsDragEnabled)) {
252 continue;
253 }
254
255 if (sourceDeletable) {
257 if (!source.isValid()) {
258 // index points to an item
260 sourceDeletable = source.rights() & Collection::CanDeleteItem;
261 } else {
262 // index points to a collection
263 sourceDeletable =
264 (source.rights() & Collection::CanDeleteCollection) && !source.hasAttribute<SpecialCollectionAttribute>() && !source.isVirtual();
265 }
266 }
267 indexes.append(index);
268 }
269
270 if (indexes.isEmpty()) {
271 return;
272 }
273
274 QMimeData *mimeData = m_view->model()->mimeData(indexes);
275 if (!mimeData) {
276 return;
277 }
278
279 auto drag = new QDrag(m_view);
280 drag->setMimeData(mimeData);
281 if (indexes.size() > 1) {
282 drag->setPixmap(QIcon::fromTheme(QStringLiteral("document-multiple")).pixmap(QSize(22, 22)));
283 } else {
284 QPixmap pixmap = indexes.first().data(Qt::DecorationRole).value<QIcon>().pixmap(QSize(22, 22));
285 if (pixmap.isNull()) {
286 pixmap = QIcon::fromTheme(QStringLiteral("text-plain")).pixmap(QSize(22, 22));
287 }
288 drag->setPixmap(pixmap);
289 }
290
291 if (!sourceDeletable) {
292 supportedActions &= ~Qt::MoveAction;
293 }
294
295 Qt::DropAction defaultAction = Qt::IgnoreAction;
297 defaultAction = Qt::LinkAction;
299 defaultAction = Qt::CopyAction;
301 defaultAction = Qt::MoveAction;
302 }
303
304 drag->exec(supportedActions, defaultAction);
305}
306
307bool DragDropManager::showDropActionMenu() const
308{
309 return mShowDropActionMenu;
310}
311
312void DragDropManager::setShowDropActionMenu(bool show)
313{
314 mShowDropActionMenu = show;
315}
316
317bool DragDropManager::isManualSortingActive() const
318{
319 return mIsManualSortingActive;
320}
321
322void DragDropManager::setManualSortingActive(bool active)
323{
324 mIsManualSortingActive = active;
325}
Represents a collection of PIM items.
Definition collection.h:62
qint64 Id
Describes the unique id type.
Definition collection.h:79
static QString mimeType()
Returns the mimetype used for collections.
bool hasAttribute(const QByteArray &name) const
Returns true if the collection has an attribute of the given type name, false otherwise.
static QString virtualMimeType()
Returns the mimetype used for virtual collections.
static Collection fromUrl(const QUrl &url)
Creates a collection from the given url.
@ CanDeleteItem
Can delete items in this collection.
Definition collection.h:93
@ CanDeleteCollection
Can delete this collection.
Definition collection.h:96
@ CanCreateItem
Can create new items in this collection.
Definition collection.h:92
@ CanLinkItem
Can create links to existing items in this virtual collection.
Definition collection.h:97
@ CanCreateCollection
Can create new subcollections in this collection.
Definition collection.h:95
@ ParentCollectionRole
The parent collection of the entity.
@ CollectionRole
The collection.
@ CollectionIdRole
The collection id.
An Attribute that stores the special collection type of a collection.
QString i18n(const char *text, const TYPE &arg...)
AKONADI_CALENDAR_EXPORT KCalendarCore::Event::Ptr event(const Akonadi::Item &item)
Helper integration between Akonadi and Qt.
char * toString(const EngineQuery &query)
KSERVICE_EXPORT KService::List query(FilterFunc filterFunc)
VehicleSection::Type type(QStringView coachNumber, QStringView coachClassification)
virtual QVariant data(const QModelIndex &index, int role) const const=0
Qt::KeyboardModifiers keyboardModifiers()
QPixmap pixmap(QWindow *window, const QSize &size, Mode mode, State state) const const
QIcon fromTheme(const QString &name)
QString toString(SequenceFormat format) const const
const_reference at(qsizetype i) const const
qsizetype count() const const
QList< QUrl > urls() const const
QVariant data(int role) const const
bool isValid() const const
const QAbstractItemModel * model() const const
QModelIndex parent() const const
bool isNull() const const
void chop(qsizetype n)
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
DropAction
DecorationRole
ItemIsDragEnabled
Key_Escape
ControlModifier
QList< std::pair< QString, QString > > queryItems(QUrl::ComponentFormattingOptions encoding) const const
T value() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:13:38 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.