1 | /* |
2 | Copyright (c) 2009 Stephen Kelly <steveire@gmail.com> |
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_p.h" |
22 | #include "collectionutils_p.h" |
23 | |
24 | #include <QApplication> |
25 | #include <QDropEvent> |
26 | #include <QMenu> |
27 | #include <QDrag> |
28 | |
29 | #include <KDE/KIcon> |
30 | #include <KDE/KLocalizedString> |
31 | #include <KDE/KUrl> |
32 | |
33 | #include "akonadi/collection.h" |
34 | #include "akonadi/entitytreemodel.h" |
35 | |
36 | using namespace Akonadi; |
37 | |
38 | DragDropManager::DragDropManager(QAbstractItemView *view) |
39 | : mShowDropActionMenu(true) |
40 | , mIsManualSortingActive(false) |
41 | , m_view(view) |
42 | { |
43 | } |
44 | |
45 | Akonadi::Collection DragDropManager::currentDropTarget(QDropEvent *event) const |
46 | { |
47 | const QModelIndex index = m_view->indexAt(event->pos()); |
48 | Collection collection = m_view->model()->data(index, EntityTreeModel::CollectionRole).value<Collection>(); |
49 | if (!collection.isValid()) { |
50 | const Item item = m_view->model()->data(index, EntityTreeModel::ItemRole).value<Item>(); |
51 | if (item.isValid()) { |
52 | collection = m_view->model()->data(index.parent(), EntityTreeModel::CollectionRole).value<Collection>(); |
53 | } |
54 | } |
55 | |
56 | return collection; |
57 | } |
58 | |
59 | bool DragDropManager::dropAllowed(QDragMoveEvent *event) const |
60 | { |
61 | // Check if the collection under the cursor accepts this data type |
62 | const Collection targetCollection = currentDropTarget(event); |
63 | if (targetCollection.isValid()) { |
64 | const QStringList supportedContentTypes = targetCollection.contentMimeTypes(); |
65 | |
66 | const QMimeData *data = event->mimeData(); |
67 | const KUrl::List urls = KUrl::List::fromMimeData(data); |
68 | foreach (const KUrl &url, urls) { |
69 | const Collection collection = Collection::fromUrl(url); |
70 | if (collection.isValid()) { |
71 | if (!supportedContentTypes.contains(Collection::mimeType()) && |
72 | !supportedContentTypes.contains(Collection::virtualMimeType())) { |
73 | break; |
74 | } |
75 | |
76 | // Check if we don't try to drop on one of the children |
77 | if (hasAncestor(m_view->indexAt(event->pos()), collection.id())) { |
78 | break; |
79 | } |
80 | } else { // This is an item. |
81 | const QString type = url.queryItems()[QString::fromLatin1("type" )]; |
82 | if (!supportedContentTypes.contains(type)) { |
83 | break; |
84 | } |
85 | } |
86 | |
87 | return true; |
88 | } |
89 | } |
90 | |
91 | return false; |
92 | } |
93 | |
94 | bool DragDropManager::hasAncestor(const QModelIndex &_index, Collection::Id parentId) const |
95 | { |
96 | QModelIndex index(_index); |
97 | while (index.isValid()) { |
98 | if (m_view->model()->data(index, EntityTreeModel::CollectionIdRole).toLongLong() == parentId) { |
99 | return true; |
100 | } |
101 | |
102 | index = index.parent(); |
103 | } |
104 | |
105 | return false; |
106 | } |
107 | |
108 | bool DragDropManager::processDropEvent(QDropEvent *event, bool &, bool dropOnItem) |
109 | { |
110 | const Collection targetCollection = currentDropTarget(event); |
111 | if (!targetCollection.isValid()) { |
112 | return false; |
113 | } |
114 | |
115 | if (!mIsManualSortingActive && !dropOnItem) { |
116 | return false; |
117 | } |
118 | |
119 | const QStringList supportedContentTypes = targetCollection.contentMimeTypes(); |
120 | |
121 | const QMimeData *data = event->mimeData(); |
122 | const KUrl::List urls = KUrl::List::fromMimeData(data); |
123 | foreach (const KUrl &url, urls) { |
124 | const Collection collection = Collection::fromUrl(url); |
125 | if (!collection.isValid()) { |
126 | if (!dropOnItem) { |
127 | return false; |
128 | } |
129 | } |
130 | } |
131 | |
132 | int actionCount = 0; |
133 | Qt::DropAction defaultAction; |
134 | // TODO check if the source supports moving |
135 | |
136 | bool moveAllowed, copyAllowed, linkAllowed; |
137 | moveAllowed = copyAllowed = linkAllowed = false; |
138 | |
139 | if ((targetCollection.rights() & (Collection::CanCreateCollection | Collection::CanCreateItem)) && |
140 | (event->possibleActions() & Qt::MoveAction)) { |
141 | moveAllowed = true; |
142 | } |
143 | if ((targetCollection.rights() & (Collection::CanCreateCollection | Collection::CanCreateItem)) && |
144 | (event->possibleActions() & Qt::CopyAction)) { |
145 | copyAllowed = true; |
146 | } |
147 | |
148 | if ((targetCollection.rights() & Collection::CanLinkItem) && |
149 | (event->possibleActions() & Qt::LinkAction)) { |
150 | linkAllowed = true; |
151 | } |
152 | |
153 | if (mIsManualSortingActive && !dropOnItem) { |
154 | moveAllowed = true; |
155 | copyAllowed = false; |
156 | linkAllowed = false; |
157 | } |
158 | |
159 | if (!moveAllowed && !copyAllowed && !linkAllowed) { |
160 | kDebug() << "Cannot drop here:" << event->possibleActions() << m_view->model()->supportedDragActions() << m_view->model()->supportedDropActions(); |
161 | return false; |
162 | } |
163 | |
164 | // first check whether the user pressed a modifier key to select a specific action |
165 | if ((QApplication::keyboardModifiers() & Qt::ControlModifier) && |
166 | (QApplication::keyboardModifiers() & Qt::ShiftModifier)) { |
167 | if (linkAllowed) { |
168 | defaultAction = Qt::LinkAction; |
169 | actionCount = 1; |
170 | } else { |
171 | return false; |
172 | } |
173 | } else if ((QApplication::keyboardModifiers() & Qt::ControlModifier)) { |
174 | if (copyAllowed) { |
175 | defaultAction = Qt::CopyAction; |
176 | actionCount = 1; |
177 | } else { |
178 | return false; |
179 | } |
180 | } else if ((QApplication::keyboardModifiers() & Qt::ShiftModifier)) { |
181 | if (moveAllowed) { |
182 | defaultAction = Qt::MoveAction; |
183 | actionCount = 1; |
184 | } else { |
185 | return false; |
186 | } |
187 | } |
188 | |
189 | if (actionCount == 1) { |
190 | kDebug() << "Selecting drop action" << defaultAction << ", there are no other possibilities" ; |
191 | event->setDropAction(defaultAction); |
192 | return true; |
193 | } |
194 | |
195 | if (!mShowDropActionMenu) { |
196 | if (moveAllowed) { |
197 | defaultAction = Qt::MoveAction; |
198 | } else if (copyAllowed) { |
199 | defaultAction = Qt::CopyAction; |
200 | } else if (linkAllowed) { |
201 | defaultAction = Qt::LinkAction; |
202 | } else { |
203 | return false; |
204 | } |
205 | event->setDropAction(defaultAction); |
206 | return true; |
207 | } |
208 | |
209 | // otherwise show up a menu to allow the user to select an action |
210 | QMenu (m_view); |
211 | QAction *moveDropAction = 0; |
212 | QAction *copyDropAction = 0; |
213 | QAction *linkAction = 0; |
214 | QString sequence; |
215 | |
216 | if (moveAllowed) { |
217 | sequence = QKeySequence(Qt::ShiftModifier).toString(); |
218 | sequence.chop(1); // chop superfluous '+' |
219 | moveDropAction = popup.addAction(KIcon(QString::fromLatin1("go-jump" )), i18n("&Move Here" ) + QLatin1Char('\t') + sequence); |
220 | } |
221 | |
222 | if (copyAllowed) { |
223 | sequence = QKeySequence(Qt::ControlModifier).toString(); |
224 | sequence.chop(1); // chop superfluous '+' |
225 | copyDropAction = popup.addAction(KIcon(QString::fromLatin1("edit-copy" )), i18n("&Copy Here" ) + QLatin1Char('\t') + sequence); |
226 | } |
227 | |
228 | if (linkAllowed) { |
229 | sequence = QKeySequence(Qt::ControlModifier + Qt::ShiftModifier).toString(); |
230 | sequence.chop(1); // chop superfluous '+' |
231 | linkAction = popup.addAction(KIcon(QLatin1String("edit-link" )), i18n("&Link Here" ) + QLatin1Char('\t') + sequence); |
232 | } |
233 | |
234 | popup.addSeparator(); |
235 | popup.addAction(KIcon(QString::fromLatin1("process-stop" )), i18n("C&ancel" ) + QLatin1Char('\t') + QKeySequence(Qt::Key_Escape).toString()); |
236 | |
237 | QAction *activatedAction = popup.exec(QCursor::pos()); |
238 | if (!activatedAction) { |
239 | menuCanceled = true; |
240 | return false; |
241 | } else if (activatedAction == moveDropAction) { |
242 | event->setDropAction(Qt::MoveAction); |
243 | } else if (activatedAction == copyDropAction) { |
244 | event->setDropAction(Qt::CopyAction); |
245 | } else if (activatedAction == linkAction) { |
246 | event->setDropAction(Qt::LinkAction); |
247 | } else { |
248 | menuCanceled = true; |
249 | return false; |
250 | } |
251 | return true; |
252 | } |
253 | |
254 | void DragDropManager::startDrag(Qt::DropActions supportedActions) |
255 | { |
256 | QModelIndexList indexes; |
257 | bool sourceDeletable = true; |
258 | foreach (const QModelIndex &index, m_view->selectionModel()->selectedRows()) { |
259 | if (!m_view->model()->flags(index).testFlag(Qt::ItemIsDragEnabled)) { |
260 | continue; |
261 | } |
262 | |
263 | if (sourceDeletable) { |
264 | Collection source = index.data(EntityTreeModel::CollectionRole).value<Collection>(); |
265 | if (!source.isValid()) { |
266 | // index points to an item |
267 | source = index.data(EntityTreeModel::ParentCollectionRole).value<Collection>(); |
268 | sourceDeletable = source.rights() & Collection::CanDeleteItem; |
269 | } else { |
270 | // index points to a collection |
271 | sourceDeletable = (source.rights() & Collection::CanDeleteCollection) && !source.hasAttribute<SpecialCollectionAttribute>() && !source.isVirtual(); |
272 | } |
273 | } |
274 | indexes.append(index); |
275 | } |
276 | |
277 | if (indexes.isEmpty()) { |
278 | return; |
279 | } |
280 | |
281 | QMimeData *mimeData = m_view->model()->mimeData(indexes); |
282 | if (!mimeData) { |
283 | return; |
284 | } |
285 | |
286 | QDrag *drag = new QDrag(m_view); |
287 | drag->setMimeData(mimeData); |
288 | if (indexes.size() > 1) { |
289 | drag->setPixmap(KIcon(QLatin1String("document-multiple" )).pixmap(QSize(22, 22))); |
290 | } else { |
291 | QPixmap pixmap = indexes.first().data(Qt::DecorationRole).value<QIcon>().pixmap(QSize(22, 22)); |
292 | if (pixmap.isNull()) { |
293 | pixmap = KIcon(QLatin1String("text-plain" )).pixmap(QSize(22, 22)); |
294 | } |
295 | drag->setPixmap(pixmap); |
296 | } |
297 | |
298 | if (!sourceDeletable) { |
299 | supportedActions &= ~Qt::MoveAction; |
300 | } |
301 | |
302 | Qt::DropAction defaultAction = Qt::IgnoreAction; |
303 | if ((QApplication::keyboardModifiers() & Qt::ControlModifier) && |
304 | (QApplication::keyboardModifiers() & Qt::ShiftModifier)) { |
305 | defaultAction = Qt::LinkAction; |
306 | } else if ((QApplication::keyboardModifiers() & Qt::ControlModifier)) { |
307 | defaultAction = Qt::CopyAction; |
308 | } else if ((QApplication::keyboardModifiers() & Qt::ShiftModifier)) { |
309 | defaultAction = Qt::MoveAction; |
310 | } |
311 | |
312 | drag->exec(supportedActions, defaultAction); |
313 | } |
314 | |
315 | bool DragDropManager::() const |
316 | { |
317 | return mShowDropActionMenu; |
318 | } |
319 | |
320 | void DragDropManager::(bool show) |
321 | { |
322 | mShowDropActionMenu = show; |
323 | } |
324 | |
325 | bool DragDropManager::isManualSortingActive() const |
326 | { |
327 | return mIsManualSortingActive; |
328 | } |
329 | |
330 | void DragDropManager::setManualSortingActive(bool active) |
331 | { |
332 | mIsManualSortingActive = active; |
333 | } |
334 | |