1 /* ============================================================
2 *
3 * This file is a part of digiKam project
4 * https://www.digikam.org
5 *
6 * Date : 2009-11-21
7 * Description : Qt Model for Tag - drag and drop handling
8 *
9 * Copyright (C) 2009 by Johannes Wienke <languitar at semipol dot de>
10 * Copyright (C) 2013 by Veaceslav Munteanu <veaceslav dot munteanu90 at gmail dot com>
11 * Copyright (C) 2013-2021 by Gilles Caulier <caulier dot gilles at gmail dot com>
12 *
13 * This program is free software; you can redistribute it
14 * and/or modify it under the terms of the GNU General
15 * Public License as published by the Free Software Foundation;
16 * either version 2, or (at your option)
17 * any later version.
18 *
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * ============================================================ */
25
26 #include "tagdragdrop.h"
27
28 // Qt includes
29
30 #include <QDropEvent>
31 #include <QMenu>
32 #include <QIcon>
33 #include <QMessageBox>
34 #include <QApplication>
35
36 // KDE includes
37
38 #include <klocalizedstring.h>
39
40 // Local includes
41
42 #include "digikam_debug.h"
43 #include "albummanager.h"
44 #include "ddragobjects.h"
45 #include "iteminfo.h"
46 #include "albumtreeview.h"
47
48 namespace Digikam
49 {
50
TagDragDropHandler(TagModel * const model)51 TagDragDropHandler::TagDragDropHandler(TagModel* const model)
52 : AlbumModelDragDropHandler(model)
53 {
54 }
55
model() const56 TagModel* TagDragDropHandler::model() const
57 {
58 return (static_cast<TagModel*>(m_model));
59 }
60
dropEvent(QAbstractItemView * view,const QDropEvent * e,const QModelIndex & droppedOn)61 bool TagDragDropHandler::dropEvent(QAbstractItemView* view,
62 const QDropEvent* e,
63 const QModelIndex& droppedOn)
64 {
65 if (accepts(e, droppedOn) == Qt::IgnoreAction)
66 {
67 return false;
68 }
69
70 TAlbum* const destAlbum = model()->albumForIndex(droppedOn);
71
72 if (DTagListDrag::canDecode(e->mimeData()))
73 {
74 QList<int> tagIDs;
75
76 if (!DTagListDrag::decode(e->mimeData(), tagIDs))
77 {
78 return false;
79 }
80
81 if (tagIDs.isEmpty())
82 {
83 return false;
84 }
85
86 QMenu popMenu(view);
87 QAction* const gotoAction = popMenu.addAction(QIcon::fromTheme(QLatin1String("go-jump")), i18n("&Move Here"));
88 QAction* const mergeAction = popMenu.addAction(QIcon::fromTheme(QLatin1String("merge")), i18n("M&erge Here"));
89 popMenu.addSeparator();
90 popMenu.addAction(QIcon::fromTheme(QLatin1String("dialog-cancel")), i18n("C&ancel"));
91 popMenu.setMouseTracking(true);
92 QAction* const choice = popMenu.exec(QCursor::pos());
93
94 for (int index = 0 ; index < tagIDs.count() ; ++index)
95 {
96 TAlbum* const talbum = AlbumManager::instance()->findTAlbum(tagIDs.at(index));
97
98 if (!talbum)
99 {
100 return false;
101 }
102
103 if (destAlbum && (talbum == destAlbum))
104 {
105 return false;
106 }
107
108 if (choice == gotoAction)
109 {
110 TAlbum* newParentTag = nullptr;
111
112 if (!destAlbum)
113 {
114 // move dragItem to the root
115
116 newParentTag = AlbumManager::instance()->findTAlbum(0);
117 }
118 else
119 {
120 // move dragItem as child of dropItem
121
122 newParentTag = destAlbum;
123 }
124
125 QString errMsg;
126
127 if (!AlbumManager::instance()->moveTAlbum(talbum, newParentTag, errMsg))
128 {
129 QMessageBox::critical(view, qApp->applicationName(), errMsg);
130 }
131
132 if (view && !view->isVisible())
133 {
134 view->setVisible(true);
135 }
136 }
137 else if (choice == mergeAction)
138 {
139 if (!destAlbum)
140 {
141 return false;
142 }
143
144 QString errMsg;
145
146 if (!AlbumManager::instance()->mergeTAlbum(talbum, destAlbum, true, errMsg))
147 {
148 QMessageBox::critical(view, qApp->applicationName(), errMsg);
149 }
150
151 if (view && !view->isVisible())
152 {
153 view->setVisible(true);
154 }
155 }
156 }
157
158 return true;
159 }
160 else if (DItemDrag::canDecode(e->mimeData()))
161 {
162 QList<QUrl> urls;
163 QList<int> albumIDs;
164 QList<qlonglong> imageIDs;
165
166 if (!DItemDrag::decode(e->mimeData(), urls, albumIDs, imageIDs))
167 {
168 return false;
169 }
170
171 if (urls.isEmpty() || albumIDs.isEmpty() || imageIDs.isEmpty())
172 {
173 return false;
174 }
175
176 if ((imageIDs.size() == 1) && ItemInfo(imageIDs.first()).tagIds().contains(destAlbum->id()))
177 {
178 // Setting the dropped image as the album thumbnail
179 // If the ctrl key is pressed, when dropping the image, the
180 // thumbnail is set without a popup menu
181
182 bool set = false;
183
184 if (e->keyboardModifiers() == Qt::ControlModifier)
185 {
186 set = true;
187 }
188 else
189 {
190 QMenu popMenu(view);
191 QAction* setAction = nullptr;
192
193 if (imageIDs.count() == 1)
194 {
195 setAction = popMenu.addAction(i18n("Set as Tag Thumbnail"));
196 }
197
198 popMenu.addSeparator();
199 popMenu.addAction( QIcon::fromTheme(QLatin1String("dialog-cancel")), i18n("C&ancel") );
200
201 popMenu.setMouseTracking(true);
202 QAction* const choice = popMenu.exec(QCursor::pos());
203 set = (choice == setAction);
204 }
205
206 if (set)
207 {
208 QString errMsg;
209 AlbumManager::instance()->updateTAlbumIcon(destAlbum, QString(), imageIDs.first(), errMsg);
210 }
211
212 return true;
213 }
214
215 // If a ctrl key is pressed while dropping the drag object,
216 // the tag is assigned to the images without showing a
217 // popup menu.
218
219 bool assign = false;
220
221 // Use selected tags instead of dropped on.
222
223 QList<int> tagIdList;
224 QStringList tagNames;
225
226 AbstractAlbumTreeView* const tview = dynamic_cast<AbstractAlbumTreeView*>(view);
227
228 if (!tview)
229 {
230 return false;
231 }
232
233 QList<Album*> selTags = tview->selectedItems();
234
235 for (int it = 0 ; it < selTags.count() ; ++it)
236 {
237 TAlbum* const temp = dynamic_cast<TAlbum*>(selTags.at(it));
238
239 if (temp)
240 {
241 tagIdList << temp->id();
242 tagNames << temp->title();
243 }
244 }
245
246 // If nothing selected, use dropped on tag
247
248 if (tagIdList.isEmpty())
249 {
250 tagIdList << destAlbum->id();
251 tagNames << destAlbum->title();
252 }
253
254 if (e->keyboardModifiers() == Qt::ControlModifier)
255 {
256 assign = true;
257 }
258 else
259 {
260 QMenu popMenu(view);
261 QAction* const assignAction = popMenu.addAction(QIcon::fromTheme(QLatin1String("tag")),
262 i18n("Assign Tag(s) '%1' to Items", tagNames.join(QLatin1String(", "))));
263 popMenu.addSeparator();
264 popMenu.addAction( QIcon::fromTheme(QLatin1String("dialog-cancel")), i18n("C&ancel") );
265
266 popMenu.setMouseTracking(true);
267 QAction* const choice = popMenu.exec(QCursor::pos());
268 assign = (choice == assignAction);
269 }
270
271 if (assign)
272 {
273 emit assignTags(imageIDs, tagIdList);
274 }
275
276 return true;
277 }
278
279 return false;
280 }
281
accepts(const QDropEvent * e,const QModelIndex & dropIndex)282 Qt::DropAction TagDragDropHandler::accepts(const QDropEvent* e, const QModelIndex& dropIndex)
283 {
284 TAlbum* const destAlbum = model()->albumForIndex(dropIndex);
285
286 if (DTagListDrag::canDecode(e->mimeData()))
287 {
288 /*
289 int droppedId = 0;
290 */
291 QList<int> droppedId;
292
293 if (!DTagListDrag::decode(e->mimeData(), droppedId))
294 {
295 qCDebug(DIGIKAM_GENERAL_LOG) << "List decode error" << droppedId.isEmpty();
296 return Qt::IgnoreAction;
297 }
298
299 TAlbum* const droppedAlbum = AlbumManager::instance()->findTAlbum(droppedId.first());
300
301 if (!droppedAlbum)
302 {
303 return Qt::IgnoreAction;
304 }
305
306 // Allow dragging on empty space to move the dragged album under the root albumForIndex
307 // unless the itemDrag is already at root level
308
309 if (!destAlbum)
310 {
311 Album* const palbum = droppedAlbum->parent();
312
313 if (!palbum)
314 {
315 return Qt::IgnoreAction;
316 }
317
318 if (palbum->isRoot())
319 {
320 return Qt::IgnoreAction;
321 }
322 else
323 {
324 return Qt::MoveAction;
325 }
326 }
327
328 // Dragging an item on itself makes no sense
329
330 if (destAlbum == droppedAlbum)
331 {
332 return Qt::IgnoreAction;
333 }
334
335 // Dragging a parent on its child makes no sense
336
337 if (droppedAlbum && droppedAlbum->isAncestorOf(destAlbum))
338 {
339 return Qt::IgnoreAction;
340 }
341
342 return Qt::MoveAction;
343 }
344 else if (DItemDrag::canDecode(e->mimeData()) && destAlbum && destAlbum->parent())
345 {
346 // Only other possibility is image items being dropped
347 // And allow this only if there is a Tag to be dropped
348 // on and also the Tag is not root.
349
350 return Qt::CopyAction;
351 }
352
353 return Qt::IgnoreAction;
354 }
355
mimeTypes() const356 QStringList TagDragDropHandler::mimeTypes() const
357 {
358 QStringList mimeTypes;
359
360 mimeTypes << DTagListDrag::mimeTypes()
361 << DItemDrag::mimeTypes();
362
363 return mimeTypes;
364 }
365
createMimeData(const QList<Album * > & albums)366 QMimeData* TagDragDropHandler::createMimeData(const QList<Album*>& albums)
367 {
368
369 if (albums.isEmpty())
370 {
371 qCDebug(DIGIKAM_GENERAL_LOG) << "Cannot drag no tag";
372 return nullptr;
373 }
374
375 QList<int> ids;
376
377 foreach (Album* const album, albums)
378 {
379 ids << album->id();
380 }
381
382 return new DTagListDrag(ids);
383 }
384
385 } // namespace Digikam
386