File indexing completed on 2025-01-19 03:50:34

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2009-04-16
0007  * Description : Qt Model for Albums - drag and drop handling
0008  *
0009  * SPDX-FileCopyrightText: 2005-2006 by Joern Ahrens <joern dot ahrens at kdemail dot net>
0010  * SPDX-FileCopyrightText: 2006-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0011  * SPDX-FileCopyrightText: 2009-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
0012  * SPDX-FileCopyrightText: 2009      by Andi Clemens <andi dot clemens at gmail dot com>
0013  * SPDX-FileCopyrightText: 2015      by Mohamed_Anwer <m_dot_anwer at gmx dot com>
0014  *
0015  * SPDX-License-Identifier: GPL-2.0-or-later
0016  *
0017  * ============================================================ */
0018 
0019 #include "albumdragdrop.h"
0020 
0021 // Qt includes
0022 
0023 #include <QDropEvent>
0024 #include <QMenu>
0025 #include <QIcon>
0026 
0027 // KDE includes
0028 
0029 #include <klocalizedstring.h>
0030 
0031 // Local includes
0032 
0033 #include "digikam_debug.h"
0034 #include "albummanager.h"
0035 #include "albumpointer.h"
0036 #include "importui.h"
0037 #include "ddragobjects.h"
0038 #include "dio.h"
0039 #include "iteminfo.h"
0040 #include "iteminfolist.h"
0041 
0042 namespace Digikam
0043 {
0044 
0045 AlbumDragDropHandler::AlbumDragDropHandler(AlbumModel* const model)
0046     : AlbumModelDragDropHandler(model)
0047 {
0048 }
0049 
0050 AlbumModel* AlbumDragDropHandler::model() const
0051 {
0052     return (static_cast<AlbumModel*>(m_model));
0053 }
0054 
0055 bool AlbumDragDropHandler::dropEvent(QAbstractItemView* view,
0056                                      const QDropEvent* e,
0057                                      const QModelIndex& droppedOn)
0058 {
0059     if (accepts(e, droppedOn) == Qt::IgnoreAction)
0060     {
0061         return false;
0062     }
0063 
0064     AlbumPointer<PAlbum> destAlbum = model()->albumForIndex(droppedOn);
0065 
0066     if (!destAlbum)
0067     {
0068         return false;
0069     }
0070 
0071     if (DAlbumDrag::canDecode(e->mimeData()))
0072     {
0073         QList<QUrl> urls;
0074         int         albumId = 0;
0075 
0076         if (!DAlbumDrag::decode(e->mimeData(), urls, albumId))
0077         {
0078             return false;
0079         }
0080 
0081         AlbumPointer<PAlbum> droppedAlbum = AlbumManager::instance()->findPAlbum(albumId);
0082 
0083         if (!droppedAlbum)
0084         {
0085             return false;
0086         }
0087 
0088         QMenu popMenu(view);
0089         QAction* const moveAction = popMenu.addAction(QIcon::fromTheme(QLatin1String("go-jump")),   i18n("&Move Here"));
0090         QAction* const copyAction = popMenu.addAction(QIcon::fromTheme(QLatin1String("edit-copy")), i18n("&Copy Here"));
0091         popMenu.addSeparator();
0092         popMenu.addAction(QIcon::fromTheme(QLatin1String("dialog-cancel")), i18n("C&ancel"));
0093         popMenu.setMouseTracking(true);
0094         QAction* const choice     = popMenu.exec(QCursor::pos());
0095 
0096         if      (choice == moveAction)
0097         {
0098             DIO::move(droppedAlbum, destAlbum);
0099         }
0100         else if (choice == copyAction)
0101         {
0102             DIO::copy(droppedAlbum, destAlbum);
0103         }
0104 
0105         return true;
0106     }
0107     else if (DItemDrag::canDecode(e->mimeData()))
0108     {
0109 
0110         QList<QUrl>      urls;
0111         QList<int>       albumIDs;
0112         QList<qlonglong> imageIDs;
0113 
0114         if (!DItemDrag::decode(e->mimeData(), urls, albumIDs, imageIDs))
0115         {
0116             return false;
0117         }
0118 
0119         if (urls.isEmpty() || albumIDs.isEmpty() || imageIDs.isEmpty())
0120         {
0121             return false;
0122         }
0123 
0124         // Check if items dropped come from outside current album.
0125         // This can be the case with recursive content album mode.
0126 
0127         ItemInfoList extImgInfList;
0128 
0129         for (QList<qlonglong>::const_iterator it = imageIDs.constBegin(); it != imageIDs.constEnd(); ++it)
0130         {
0131             ItemInfo info(*it);
0132 
0133             if (info.albumId() != destAlbum->id())
0134             {
0135                 extImgInfList << info;
0136             }
0137         }
0138 
0139         if (extImgInfList.isEmpty())
0140         {
0141             // Setting the dropped image as the album thumbnail
0142             // If the ctrl key is pressed, when dropping the image, the
0143             // thumbnail is set without a popup menu
0144 
0145             bool set = false;
0146 
0147 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0148 
0149             if (e->modifiers() == Qt::ControlModifier)
0150 
0151 #else
0152 
0153             if (e->keyboardModifiers() == Qt::ControlModifier)
0154 
0155 #endif
0156 
0157             {
0158                 set = true;
0159             }
0160             else
0161             {
0162                 QMenu popMenu(view);
0163                 QAction* setAction    = nullptr;
0164 
0165                 if (imageIDs.count() == 1)
0166                 {
0167                     setAction = popMenu.addAction(i18n("Set as Album Thumbnail"));
0168                 }
0169 
0170                 popMenu.addSeparator();
0171                 popMenu.addAction(QIcon::fromTheme(QLatin1String("dialog-cancel")), i18n("C&ancel"));
0172                 popMenu.setMouseTracking(true);
0173                 QAction* const choice = popMenu.exec(QCursor::pos());
0174                 set                   = (setAction == choice);
0175             }
0176 
0177             if (set && destAlbum)
0178             {
0179                 QString errMsg;
0180                 AlbumManager::instance()->updatePAlbumIcon(destAlbum, imageIDs.first(), errMsg);
0181             }
0182 
0183             return true;
0184         }
0185 
0186         bool ddMove       = false;
0187         bool ddCopy       = false;
0188         bool setThumbnail = false;
0189 
0190 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0191 
0192         if      (e->modifiers() == Qt::ShiftModifier)
0193 
0194 #else
0195 
0196         if      (e->keyboardModifiers() == Qt::ShiftModifier)
0197 
0198 #endif
0199 
0200         {
0201             // If shift key is pressed while dragging, move the drag object without
0202             // displaying popup menu -> move
0203 
0204             ddMove = true;
0205         }
0206 
0207 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0208 
0209         else if (e->modifiers() == Qt::ControlModifier)
0210 
0211 #else
0212 
0213         else if (e->keyboardModifiers() == Qt::ControlModifier)
0214 
0215 #endif
0216 
0217         {
0218             // If ctrl key is pressed while dragging, copy the drag object without
0219             // displaying popup menu -> copy
0220 
0221             ddCopy = true;
0222         }
0223         else
0224         {
0225             QMenu popMenu(view);
0226             QAction* const moveAction = popMenu.addAction(QIcon::fromTheme(QLatin1String("go-jump")),   i18n("&Move Here"));
0227             QAction* const copyAction = popMenu.addAction(QIcon::fromTheme(QLatin1String("edit-copy")), i18n("&Copy Here"));
0228             QAction* thumbnailAction  = nullptr;
0229 
0230             if (imageIDs.count() == 1)
0231             {
0232                 thumbnailAction = popMenu.addAction(i18n("Set as Album Thumbnail"));
0233             }
0234 
0235             popMenu.addSeparator();
0236             popMenu.addAction(QIcon::fromTheme(QLatin1String("dialog-cancel")), i18n("C&ancel"));
0237             popMenu.setMouseTracking(true);
0238             QAction* const choice     = popMenu.exec(QCursor::pos());
0239 
0240             if (choice)
0241             {
0242                 if      (choice == moveAction)
0243                 {
0244                     ddMove = true;
0245                 }
0246                 else if (choice == copyAction)
0247                 {
0248                     ddCopy = true;
0249                 }
0250                 else if (choice == thumbnailAction)
0251                 {
0252                     setThumbnail = true;
0253                 }
0254             }
0255         }
0256 
0257         if (!destAlbum)
0258         {
0259             return false;
0260         }
0261 
0262         if      (ddMove)
0263         {
0264             DIO::move(extImgInfList, destAlbum);
0265         }
0266         else if (ddCopy)
0267         {
0268             DIO::copy(extImgInfList, destAlbum);
0269         }
0270         else if (setThumbnail)
0271         {
0272             QString errMsg;
0273             AlbumManager::instance()->updatePAlbumIcon(destAlbum, extImgInfList.first().id(), errMsg);
0274         }
0275 
0276         return true;
0277     }
0278 
0279     // -- DnD from Camera GUI ----------------------------
0280 
0281     else if (DCameraItemListDrag::canDecode(e->mimeData()))
0282     {
0283         ImportUI* const ui = dynamic_cast<ImportUI*>(e->source());
0284 
0285         if (ui)
0286         {
0287             QMenu popMenu(view);
0288             QAction* const downAction    = popMenu.addAction(QIcon::fromTheme(QLatin1String("file-export")), i18n("Download From Camera"));
0289             QAction* const downDelAction = popMenu.addAction(QIcon::fromTheme(QLatin1String("file-export")), i18n("Download && Delete From Camera"));
0290             popMenu.addSeparator();
0291             popMenu.addAction(QIcon::fromTheme(QLatin1String("dialog-cancel")), i18n("C&ancel"));
0292             popMenu.setMouseTracking(true);
0293             QAction* const choice         = popMenu.exec(QCursor::pos());
0294 
0295             if (choice)
0296             {
0297                 if      (choice == downAction)
0298                 {
0299                     ui->slotDownload(true, false, destAlbum);
0300                 }
0301                 else if (choice == downDelAction)
0302                 {
0303                     ui->slotDownload(true, true, destAlbum);
0304                 }
0305             }
0306         }
0307     }
0308 
0309     // -- DnD from an external source ---------------------
0310 
0311     else if (e->mimeData()->hasUrls())
0312     {
0313         QList<QUrl> srcURLs = e->mimeData()->urls();
0314         bool ddMove         = false;
0315         bool ddCopy         = false;
0316 
0317 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0318 
0319         if      (e->modifiers() == Qt::ShiftModifier)
0320 
0321 #else
0322 
0323         if      (e->keyboardModifiers() == Qt::ShiftModifier)
0324 
0325 #endif
0326 
0327         {
0328             // If shift key is pressed while dropping, move the drag object without
0329             // displaying popup menu -> move
0330 
0331             ddMove = true;
0332         }
0333 
0334 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0335 
0336         else if (e->modifiers() == Qt::ControlModifier)
0337 
0338 #else
0339 
0340         else if (e->keyboardModifiers() == Qt::ControlModifier)
0341 
0342 #endif
0343 
0344         {
0345             // If ctrl key is pressed while dropping, copy the drag object without
0346             // displaying popup menu -> copy
0347 
0348             ddCopy = true;
0349         }
0350         else
0351         {
0352             QMenu popMenu(view);
0353             QAction* const moveAction = popMenu.addAction(QIcon::fromTheme(QLatin1String("go-jump")),   i18n("&Move Here"));
0354             QAction* const copyAction = popMenu.addAction(QIcon::fromTheme(QLatin1String("edit-copy")), i18n("&Copy Here"));
0355             popMenu.addSeparator();
0356             popMenu.addAction(QIcon::fromTheme(QLatin1String("dialog-cancel")), i18n("C&ancel"));
0357             popMenu.setMouseTracking(true);
0358             QAction* const choice     = popMenu.exec(QCursor::pos());
0359 
0360             if      (choice == copyAction)
0361             {
0362                 ddCopy = true;
0363             }
0364             else if (choice == moveAction)
0365             {
0366                 ddMove = true;
0367             }
0368         }
0369 
0370         if      (ddMove)
0371         {
0372             DIO::move(srcURLs, destAlbum);
0373         }
0374         else if (ddCopy)
0375         {
0376             DIO::copy(srcURLs, destAlbum);
0377         }
0378 
0379         return true;
0380     }
0381 
0382     return false;
0383 }
0384 
0385 Qt::DropAction AlbumDragDropHandler::accepts(const QDropEvent* e, const QModelIndex& dropIndex)
0386 {
0387     PAlbum* const destAlbum = model()->albumForIndex(dropIndex);
0388 
0389     if (!destAlbum)
0390     {
0391         return Qt::IgnoreAction;
0392     }
0393 
0394     // Dropping on root is not allowed and
0395     // Dropping on trash is not implemented yet
0396 
0397     if (destAlbum->isRoot() || destAlbum->isTrashAlbum())
0398     {
0399         return Qt::IgnoreAction;
0400     }
0401 
0402     if      (DAlbumDrag::canDecode(e->mimeData()))
0403     {
0404         QList<QUrl> urls;
0405         int         albumId = 0;
0406 
0407         if (!DAlbumDrag::decode(e->mimeData(), urls, albumId))
0408         {
0409             return Qt::IgnoreAction;
0410         }
0411 
0412         PAlbum* const droppedAlbum = AlbumManager::instance()->findPAlbum(albumId);
0413 
0414         if (!droppedAlbum)
0415         {
0416             return Qt::IgnoreAction;
0417         }
0418 
0419         // Dragging an item on itself makes no sense
0420 
0421         if (droppedAlbum == destAlbum)
0422         {
0423             return Qt::IgnoreAction;
0424         }
0425 
0426         // Dragging a parent on its child makes no sense
0427 
0428         if (droppedAlbum->isAncestorOf(destAlbum))
0429         {
0430             return Qt::IgnoreAction;
0431         }
0432 
0433         return Qt::MoveAction;
0434     }
0435     else if (DItemDrag::canDecode(e->mimeData())           ||
0436              DCameraItemListDrag::canDecode(e->mimeData()) ||
0437              e->mimeData()->hasUrls())
0438     {
0439         return Qt::MoveAction;
0440     }
0441 
0442     return Qt::IgnoreAction;
0443 }
0444 
0445 QStringList AlbumDragDropHandler::mimeTypes() const
0446 {
0447     QStringList mimeTypes;
0448 
0449     mimeTypes << DAlbumDrag::mimeTypes()
0450               << DItemDrag::mimeTypes()
0451               << DCameraItemListDrag::mimeTypes()
0452               << QLatin1String("text/uri-list");
0453 
0454     return mimeTypes;
0455 }
0456 
0457 QMimeData* AlbumDragDropHandler::createMimeData(const QList<Album*>& albums)
0458 {
0459     if (albums.isEmpty())
0460     {
0461         return nullptr;
0462     }
0463 
0464     if (albums.size() > 1)
0465     {
0466         qCWarning(DIGIKAM_GENERAL_LOG) << "Dragging multiple albums is not implemented";
0467     }
0468 
0469     PAlbum* const palbum = dynamic_cast<PAlbum*>(albums.first());
0470 
0471     // Root or album root and Trash Albums are not draggable
0472 
0473     if (!palbum || palbum->isRoot() || palbum->isAlbumRoot() || palbum->isTrashAlbum())
0474     {
0475         return nullptr;
0476     }
0477 
0478     return (new DAlbumDrag(albums.first()->databaseUrl(), albums.first()->id(), palbum->fileUrl()));
0479 }
0480 
0481 } // namespace Digikam
0482 
0483 #include "moc_albumdragdrop.cpp"