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