Akonadi

dragdropmanager.cpp
1 /*
2  SPDX-FileCopyrightText: 2009 Stephen Kelly <[email protected]>
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 
24 using namespace Akonadi;
25 
26 DragDropManager::DragDropManager(QAbstractItemView *view)
27  : m_view(view)
28 {
29 }
30 
31 Akonadi::Collection DragDropManager::currentDropTarget(QDropEvent *event) const
32 {
33  const QModelIndex index = m_view->indexAt(event->pos());
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 
45 bool 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->pos()), 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 == QLatin1String("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 
86 bool 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 
100 bool 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 = popup.addAction(QIcon::fromTheme(QStringLiteral("process-stop")), i18n("C&ancel") + QLatin1Char('\t') + QKeySequence(Qt::Key_Escape).toString());
229 
230  QAction *activatedAction = popup.exec(m_view->viewport()->mapToGlobal(event->pos()));
231  if (!activatedAction || (activatedAction == cancelAction)) {
232  menuCanceled = true;
233  return false;
234  } else if (activatedAction == moveDropAction) {
235  event->setDropAction(Qt::MoveAction);
236  } else if (activatedAction == copyDropAction) {
237  event->setDropAction(Qt::CopyAction);
238  } else if (activatedAction == linkAction) {
239  event->setDropAction(Qt::LinkAction);
240  }
241  return true;
242 }
243 
244 void DragDropManager::startDrag(Qt::DropActions supportedActions)
245 {
246  QModelIndexList indexes;
247  bool sourceDeletable = true;
248  const QModelIndexList lstModel = m_view->selectionModel()->selectedRows();
249  for (const QModelIndex &index : lstModel) {
250  if (!m_view->model()->flags(index).testFlag(Qt::ItemIsDragEnabled)) {
251  continue;
252  }
253 
254  if (sourceDeletable) {
255  auto source = index.data(EntityTreeModel::CollectionRole).value<Collection>();
256  if (!source.isValid()) {
257  // index points to an item
259  sourceDeletable = source.rights() & Collection::CanDeleteItem;
260  } else {
261  // index points to a collection
262  sourceDeletable =
263  (source.rights() & Collection::CanDeleteCollection) && !source.hasAttribute<SpecialCollectionAttribute>() && !source.isVirtual();
264  }
265  }
266  indexes.append(index);
267  }
268 
269  if (indexes.isEmpty()) {
270  return;
271  }
272 
273  QMimeData *mimeData = m_view->model()->mimeData(indexes);
274  if (!mimeData) {
275  return;
276  }
277 
278  auto drag = new QDrag(m_view);
279  drag->setMimeData(mimeData);
280  if (indexes.size() > 1) {
281  drag->setPixmap(QIcon::fromTheme(QStringLiteral("document-multiple")).pixmap(QSize(22, 22)));
282  } else {
283  QPixmap pixmap = indexes.first().data(Qt::DecorationRole).value<QIcon>().pixmap(QSize(22, 22));
284  if (pixmap.isNull()) {
285  pixmap = QIcon::fromTheme(QStringLiteral("text-plain")).pixmap(QSize(22, 22));
286  }
287  drag->setPixmap(pixmap);
288  }
289 
290  if (!sourceDeletable) {
291  supportedActions &= ~Qt::MoveAction;
292  }
293 
294  Qt::DropAction defaultAction = Qt::IgnoreAction;
296  defaultAction = Qt::LinkAction;
298  defaultAction = Qt::CopyAction;
300  defaultAction = Qt::MoveAction;
301  }
302 
303  drag->exec(supportedActions, defaultAction);
304 }
305 
306 bool DragDropManager::showDropActionMenu() const
307 {
308  return mShowDropActionMenu;
309 }
310 
311 void DragDropManager::setShowDropActionMenu(bool show)
312 {
313  mShowDropActionMenu = show;
314 }
315 
316 bool DragDropManager::isManualSortingActive() const
317 {
318  return mIsManualSortingActive;
319 }
320 
321 void DragDropManager::setManualSortingActive(bool active)
322 {
323  mIsManualSortingActive = active;
324 }
bool isValid() const
Returns whether the item is valid.
Definition: item.cpp:88
Qt::KeyboardModifiers keyboardModifiers()
static Collection fromUrl(const QUrl &url)
Creates a collection from the given url.
Definition: collection.cpp:267
DecorationRole
@ CanCreateCollection
Can create new subcollections in this collection.
Definition: collection.h:95
@ CanCreateItem
Can create new items in this collection.
Definition: collection.h:92
virtual QVariant data(const QModelIndex &index, int role) const const=0
int count(const T &value) const const
T value() const const
bool contains(const QString &str, Qt::CaseSensitivity cs) const const
QIcon fromTheme(const QString &name)
@ CollectionIdRole
The collection id.
void chop(int n)
Represents a collection of PIM items.
Definition: collection.h:61
KSERVICE_EXPORT KService::List query(FilterFunc filterFunc)
QVariant data(int role) const const
ItemIsDragEnabled
QString i18n(const char *text, const TYPE &arg...)
@ CollectionRole
The collection.
char * toString(const T &value)
QString toString(QKeySequence::SequenceFormat format) const const
bool hasAttribute(const QByteArray &name) const
Returns true if the collection has an attribute of the given type name, false otherwise.
Definition: collection.cpp:161
const T & at(int i) const const
static QString virtualMimeType()
Returns the mimetype used for virtual collections.
Definition: collection.cpp:297
AKONADI_CALENDAR_EXPORT KCalendarCore::Event::Ptr event(const Akonadi::Item &item)
An Attribute that stores the special collection type of a collection.
static QString mimeType()
Returns the mimetype used for collections.
Definition: collection.cpp:292
Key_Escape
bool isValid() const const
bool isNull() const const
@ CanDeleteCollection
Can delete this collection.
Definition: collection.h:96
QList< QUrl > urls() const const
VehicleSection::Type type(QStringView coachNumber, QStringView coachClassification)
DropAction
QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) const const
QModelIndex parent() const const
@ CanDeleteItem
Can delete items in this collection.
Definition: collection.h:93
@ ParentCollectionRole
The parent collection of the entity.
ControlModifier
@ CanLinkItem
Can create links to existing items in this virtual collection.
Definition: collection.h:97
qint64 Id
Describes the unique id type.
Definition: collection.h:79
const QAbstractItemModel * model() const const
Represents a PIM item stored in Akonadi storage.
Definition: item.h:104
QList< QPair< QString, QString > > queryItems(QUrl::ComponentFormattingOptions encoding) const const
Helper integration between Akonadi and Qt.
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Mon Jun 27 2022 04:01:06 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.