Akonadi

dragdropmanager.cpp
1 /*
2  Copyright (c) 2009 Stephen Kelly <[email protected]>
3 
4  This library is free software; you can redistribute it and/or modify it
5  under the terms of the GNU Library General Public License as published by
6  the Free Software Foundation; either version 2 of the License, or (at your
7  option) any later version.
8 
9  This library is distributed in the hope that it will be useful, but WITHOUT
10  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
12  License for more details.
13 
14  You should have received a copy of the GNU Library General Public License
15  along with this library; see the file COPYING.LIB. If not, write to the
16  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17  02110-1301, USA.
18 */
19 
20 #include "dragdropmanager_p.h"
21 #include "specialcollectionattribute.h"
22 #include "collectionutils.h"
23 #include "akonadiwidgets_debug.h"
24 #include <QApplication>
25 #include <QDropEvent>
26 #include <QMenu>
27 #include <QDrag>
28 
29 #include <KLocalizedString>
30 #include <QUrl>
31 #include <QUrlQuery>
32 #include <QMimeData>
33 
34 #include "collection.h"
35 #include "entitytreemodel.h"
36 
37 using namespace Akonadi;
38 
39 DragDropManager::DragDropManager(QAbstractItemView *view)
40  : mShowDropActionMenu(true)
41  , mIsManualSortingActive(false)
42  , m_view(view)
43 {
44 }
45 
46 Akonadi::Collection DragDropManager::currentDropTarget(QDropEvent *event) const
47 {
48  const QModelIndex index = m_view->indexAt(event->pos());
49  Collection collection = m_view->model()->data(index, EntityTreeModel::CollectionRole).value<Collection>();
50  if (!collection.isValid()) {
51  const Item item = m_view->model()->data(index, EntityTreeModel::ItemRole).value<Item>();
52  if (item.isValid()) {
53  collection = m_view->model()->data(index.parent(), EntityTreeModel::CollectionRole).value<Collection>();
54  }
55  }
56 
57  return collection;
58 }
59 
60 bool DragDropManager::dropAllowed(QDragMoveEvent *event) const
61 {
62  // Check if the collection under the cursor accepts this data type
63  const Collection targetCollection = currentDropTarget(event);
64  if (targetCollection.isValid()) {
65  const QStringList supportedContentTypes = targetCollection.contentMimeTypes();
66 
67  const QMimeData *data = event->mimeData();
68  if (!data) {
69  return false;
70  }
71  const QList<QUrl> urls = data->urls();
72  for (const QUrl &url : urls) {
73  const Collection collection = Collection::fromUrl(url);
74  if (collection.isValid()) {
75  if (!supportedContentTypes.contains(Collection::mimeType()) &&
76  !supportedContentTypes.contains(Collection::virtualMimeType())) {
77  break;
78  }
79 
80  // Check if we don't try to drop on one of the children
81  if (hasAncestor(m_view->indexAt(event->pos()), collection.id())) {
82  break;
83  }
84  } else { // This is an item.
86  for (int i = 0; i < query.count(); ++i) {
87  if (query.at(i).first == QLatin1String("type")) {
88  const QString type = query.at(i).second;
89  if (!supportedContentTypes.contains(type)) {
90  break;
91  }
92  }
93  }
94  }
95  return true;
96  }
97  }
98 
99  return false;
100 }
101 
102 bool DragDropManager::hasAncestor(const QModelIndex &_index, Collection::Id parentId) const
103 {
104  QModelIndex index(_index);
105  while (index.isValid()) {
106  if (m_view->model()->data(index, EntityTreeModel::CollectionIdRole).toLongLong() == parentId) {
107  return true;
108  }
109 
110  index = index.parent();
111  }
112 
113  return false;
114 }
115 
116 bool DragDropManager::processDropEvent(QDropEvent *event, bool &menuCanceled, bool dropOnItem)
117 {
118  const Collection targetCollection = currentDropTarget(event);
119  if (!targetCollection.isValid()) {
120  return false;
121  }
122 
123  if (!mIsManualSortingActive && !dropOnItem) {
124  return false;
125  }
126 
127  const QMimeData *data = event->mimeData();
128  if (!data) {
129  return false;
130  }
131  const QList<QUrl> urls = data->urls();
132  for (const QUrl &url : urls) {
133  const Collection collection = Collection::fromUrl(url);
134  if (!collection.isValid()) {
135  if (!dropOnItem) {
136  return false;
137  }
138  }
139  }
140 
141  int actionCount = 0;
142  Qt::DropAction defaultAction;
143  // TODO check if the source supports moving
144 
145  bool moveAllowed, copyAllowed, linkAllowed;
146  moveAllowed = copyAllowed = linkAllowed = false;
147 
148  if ((targetCollection.rights() & (Collection::CanCreateCollection | Collection::CanCreateItem)) &&
149  (event->possibleActions() & Qt::MoveAction)) {
150  moveAllowed = true;
151  }
152  if ((targetCollection.rights() & (Collection::CanCreateCollection | Collection::CanCreateItem)) &&
153  (event->possibleActions() & Qt::CopyAction)) {
154  copyAllowed = true;
155  }
156 
157  if ((targetCollection.rights() & Collection::CanLinkItem) &&
158  (event->possibleActions() & Qt::LinkAction)) {
159  linkAllowed = true;
160  }
161 
162  if (mIsManualSortingActive && !dropOnItem) {
163  moveAllowed = true;
164  copyAllowed = false;
165  linkAllowed = false;
166  }
167 
168  if (!moveAllowed && !copyAllowed && !linkAllowed) {
169  qCDebug(AKONADIWIDGETS_LOG) << "Cannot drop here:" << event->possibleActions() << m_view->model()->supportedDragActions() << m_view->model()->supportedDropActions();
170  return false;
171  }
172 
173  // first check whether the user pressed a modifier key to select a specific action
174  if ((QApplication::keyboardModifiers() & Qt::ControlModifier) &&
175  (QApplication::keyboardModifiers() & Qt::ShiftModifier)) {
176  if (linkAllowed) {
177  defaultAction = Qt::LinkAction;
178  actionCount = 1;
179  } else {
180  return false;
181  }
182  } else if ((QApplication::keyboardModifiers() & Qt::ControlModifier)) {
183  if (copyAllowed) {
184  defaultAction = Qt::CopyAction;
185  actionCount = 1;
186  } else {
187  return false;
188  }
189  } else if ((QApplication::keyboardModifiers() & Qt::ShiftModifier)) {
190  if (moveAllowed) {
191  defaultAction = Qt::MoveAction;
192  actionCount = 1;
193  } else {
194  return false;
195  }
196  }
197 
198  if (actionCount == 1) {
199  qCDebug(AKONADIWIDGETS_LOG) << "Selecting drop action" << defaultAction << ", there are no other possibilities";
200  event->setDropAction(defaultAction);
201  return true;
202  }
203 
204  if (!mShowDropActionMenu) {
205  if (moveAllowed) {
206  defaultAction = Qt::MoveAction;
207  } else if (copyAllowed) {
208  defaultAction = Qt::CopyAction;
209  } else if (linkAllowed) {
210  defaultAction = Qt::LinkAction;
211  } else {
212  return false;
213  }
214  event->setDropAction(defaultAction);
215  return true;
216  }
217 
218  // otherwise show up a menu to allow the user to select an action
219  QMenu popup(m_view);
220  QAction *moveDropAction = nullptr;
221  QAction *copyDropAction = nullptr;
222  QAction *linkAction = nullptr;
223  QString sequence;
224 
225  if (moveAllowed) {
226  sequence = QKeySequence(Qt::ShiftModifier).toString();
227  sequence.chop(1); // chop superfluous '+'
228  moveDropAction = popup.addAction(QIcon::fromTheme(QStringLiteral("go-jump")), i18n("&Move Here") + QLatin1Char('\t') + sequence);
229  }
230 
231  if (copyAllowed) {
232  sequence = QKeySequence(Qt::ControlModifier).toString();
233  sequence.chop(1); // chop superfluous '+'
234  copyDropAction = popup.addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("&Copy Here") + QLatin1Char('\t') + sequence);
235  }
236 
237  if (linkAllowed) {
238  sequence = QKeySequence(Qt::ControlModifier + Qt::ShiftModifier).toString();
239  sequence.chop(1); // chop superfluous '+'
240  linkAction = popup.addAction(QIcon::fromTheme(QStringLiteral("edit-link")), i18n("&Link Here") + QLatin1Char('\t') + sequence);
241  }
242 
243  popup.addSeparator();
244  popup.addAction(QIcon::fromTheme(QStringLiteral("process-stop")), i18n("C&ancel") + QLatin1Char('\t') + QKeySequence(Qt::Key_Escape).toString());
245 
246  QAction *activatedAction = popup.exec(QCursor::pos());
247  if (!activatedAction) {
248  menuCanceled = true;
249  return false;
250  } else if (activatedAction == moveDropAction) {
251  event->setDropAction(Qt::MoveAction);
252  } else if (activatedAction == copyDropAction) {
253  event->setDropAction(Qt::CopyAction);
254  } else if (activatedAction == linkAction) {
255  event->setDropAction(Qt::LinkAction);
256  } else {
257  menuCanceled = true;
258  return false;
259  }
260  return true;
261 }
262 
263 void DragDropManager::startDrag(Qt::DropActions supportedActions)
264 {
265  QModelIndexList indexes;
266  bool sourceDeletable = true;
267  const QModelIndexList lstModel = m_view->selectionModel()->selectedRows();
268  for (const QModelIndex &index : lstModel) {
269  if (!m_view->model()->flags(index).testFlag(Qt::ItemIsDragEnabled)) {
270  continue;
271  }
272 
273  if (sourceDeletable) {
275  if (!source.isValid()) {
276  // index points to an item
278  sourceDeletable = source.rights() & Collection::CanDeleteItem;
279  } else {
280  // index points to a collection
281  sourceDeletable = (source.rights() & Collection::CanDeleteCollection) && !source.hasAttribute<SpecialCollectionAttribute>() && !source.isVirtual();
282  }
283  }
284  indexes.append(index);
285  }
286 
287  if (indexes.isEmpty()) {
288  return;
289  }
290 
291  QMimeData *mimeData = m_view->model()->mimeData(indexes);
292  if (!mimeData) {
293  return;
294  }
295 
296  QDrag *drag = new QDrag(m_view);
297  drag->setMimeData(mimeData);
298  if (indexes.size() > 1) {
299  drag->setPixmap(QIcon::fromTheme(QStringLiteral("document-multiple")).pixmap(QSize(22, 22)));
300  } else {
301  QPixmap pixmap = indexes.first().data(Qt::DecorationRole).value<QIcon>().pixmap(QSize(22, 22));
302  if (pixmap.isNull()) {
303  pixmap = QIcon::fromTheme(QStringLiteral("text-plain")).pixmap(QSize(22, 22));
304  }
305  drag->setPixmap(pixmap);
306  }
307 
308  if (!sourceDeletable) {
309  supportedActions &= ~Qt::MoveAction;
310  }
311 
312  Qt::DropAction defaultAction = Qt::IgnoreAction;
313  if ((QApplication::keyboardModifiers() & Qt::ControlModifier) &&
314  (QApplication::keyboardModifiers() & Qt::ShiftModifier)) {
315  defaultAction = Qt::LinkAction;
316  } else if ((QApplication::keyboardModifiers() & Qt::ControlModifier)) {
317  defaultAction = Qt::CopyAction;
318  } else if ((QApplication::keyboardModifiers() & Qt::ShiftModifier)) {
319  defaultAction = Qt::MoveAction;
320  }
321 
322  drag->exec(supportedActions, defaultAction);
323 }
324 
325 bool DragDropManager::showDropActionMenu() const
326 {
327  return mShowDropActionMenu;
328 }
329 
330 void DragDropManager::setShowDropActionMenu(bool show)
331 {
332  mShowDropActionMenu = show;
333 }
334 
335 bool DragDropManager::isManualSortingActive() const
336 {
337  return mIsManualSortingActive;
338 }
339 
340 void DragDropManager::setManualSortingActive(bool active)
341 {
342  mIsManualSortingActive = active;
343 }
Type type(const QString &mimeType)
bool isValid() const
Returns whether the collection is valid.
Definition: collection.cpp:137
Qt::DropActions possibleActions() const const
void setMimeData(QMimeData *data)
std::optional< QSqlQuery > query(const QString &queryStatement)
Returns the cached (and prepared) query for queryStatement.
Definition: querycache.cpp:108
QList< QPair< QString, QString > > queryItems(QUrl::ComponentFormattingOptions encoding) const const
void setPixmap(const QPixmap &pixmap)
Represents a collection of PIM items.
Definition: collection.h:76
qint64 Id
Describes the unique id type.
Definition: collection.h:82
const T & at(int i) const const
QPoint pos() const const
bool contains(const QString &str, Qt::CaseSensitivity cs) const const
static QString virtualMimeType()
Returns the mimetype used for virtual collections.
Definition: collection.cpp:313
An Attribute that stores the special collection type of a collection.
T value() const const
static QString mimeType()
Returns the mimetype used for collections.
Definition: collection.cpp:308
Can create new subcollections in this collection.
Definition: collection.h:98
void chop(int n)
Qt::KeyboardModifiers keyboardModifiers()
QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) const const
Qt::DropAction exec(Qt::DropActions supportedActions)
bool isValid() const const
int count(const T &value) const const
Can create links to existing items in this virtual collection.
Definition: collection.h:100
Can delete items in this collection.
Definition: collection.h:96
Can create new items in this collection.
Definition: collection.h:95
Can delete this collection.
Definition: collection.h:99
The parent collection of the entity.
QModelIndex parent() const const
Rights rights() const
Returns the rights the user has on the collection.
Definition: collection.cpp:242
bool isNull() 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:177
QString i18n(const char *text, const TYPE &arg...)
Id id() const
Returns the unique identifier of the collection.
Definition: collection.cpp:112
QPoint pos()
QString toString(QKeySequence::SequenceFormat format) const const
char * toString(const T &value)
QVariant data(int role) const const
QList< QUrl > urls() const const
Helper integration between Akonadi and Qt.
QStringList contentMimeTypes() const
Returns a list of possible content mimetypes, e.g.
Definition: collection.cpp:256
QIcon fromTheme(const QString &name)
typedef DropActions
bool isVirtual() const
Returns whether the collection is virtual, for example a search collection.
Definition: collection.cpp:364
static Collection fromUrl(const QUrl &url)
Creates a collection from the given url.
Definition: collection.cpp:283
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Fri Jun 5 2020 23:08:54 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.