File indexing completed on 2025-01-05 03:59:54

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2012-07-13
0007  * Description : Modified context menu helper for import tool
0008  *
0009  * SPDX-FileCopyrightText: 2012      by Islam Wazery <wazery at ubuntu dot com>
0010  * SPDX-FileCopyrightText: 2012-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0011  *
0012  * SPDX-License-Identifier: GPL-2.0-or-later
0013  *
0014  * ============================================================ */
0015 
0016 #include "importcontextmenu.h"
0017 
0018 // Qt includes
0019 
0020 #include <QAction>
0021 #include <QIcon>
0022 #include <QMimeType>
0023 #include <QMimeDatabase>
0024 
0025 // KDE includes
0026 
0027 // Pragma directives to reduce warnings from KDE header files.
0028 #if defined(Q_CC_GNU)
0029 #   pragma GCC diagnostic push
0030 #   pragma GCC diagnostic ignored "-Wdeprecated-declarations"
0031 #endif
0032 
0033 #if defined(Q_CC_CLANG)
0034 #   pragma clang diagnostic push
0035 #   pragma clang diagnostic ignored "-Wmismatched-tags"
0036 #   pragma clang diagnostic ignored "-Wdeprecated-declarations"
0037 #endif
0038 
0039 #include <kservice_version.h>
0040 
0041 #if KSERVICE_VERSION > QT_VERSION_CHECK(5, 81, 0)
0042 #   include <kapplicationtrader.h>
0043 #else
0044 #   include <kmimetypetrader.h>
0045 #endif
0046 
0047 #include <klocalizedstring.h>
0048 #include <kactioncollection.h>
0049 
0050 #ifdef HAVE_KIO
0051 #   include <kopenwithdialog.h>
0052 #endif
0053 
0054 // Restore warnings
0055 #if defined(Q_CC_GNU)
0056 #   pragma GCC diagnostic pop
0057 #endif
0058 
0059 #if defined(Q_CC_CLANG)
0060 #   pragma clang diagnostic pop
0061 #endif
0062 
0063 // Local includes
0064 
0065 #include "importui.h"
0066 #include "picklabelwidget.h"
0067 #include "colorlabelwidget.h"
0068 #include "ratingwidget.h"
0069 #include "tagmodificationhelper.h"
0070 #include "tagspopupmenu.h"
0071 #include "fileactionmngr.h"
0072 #include "dservicemenu.h"
0073 
0074 namespace Digikam
0075 {
0076 
0077 class Q_DECL_HIDDEN ImportContextMenuHelper::Private
0078 {
0079 public:
0080 
0081     explicit Private(ImportContextMenuHelper* const q)
0082       : importFilterModel  (nullptr),
0083         parent             (nullptr),
0084         ABCmenu            (nullptr),
0085         stdActionCollection(nullptr),
0086         q                  (q)
0087     {
0088     }
0089 
0090     QList<qlonglong>             selectedIds;
0091     QList<QUrl>                  selectedItems;
0092 
0093     QMap<int, QAction*>          queueActions;
0094     QMap<QString, KService::Ptr> servicesMap;
0095 
0096     ImportFilterModel*           importFilterModel;
0097 
0098     QMenu*                       parent;
0099     QMenu*                       ABCmenu;
0100 
0101     KActionCollection*           stdActionCollection;
0102 
0103     ImportContextMenuHelper*     q;
0104 
0105 public:
0106 
0107     QAction* copyFromMainCollection(const QString& name) const
0108     {
0109         QAction* const mainAction = stdActionCollection->action(name);
0110 
0111         if (!mainAction)
0112         {
0113             return nullptr;
0114         }
0115 
0116         QAction* const action = new QAction(mainAction->icon(), mainAction->text(), q);
0117         action->setToolTip(mainAction->toolTip());
0118 
0119         return action;
0120     }
0121 };
0122 
0123 ImportContextMenuHelper::ImportContextMenuHelper(QMenu* const parent, KActionCollection* const actionCollection)
0124     : QObject(parent),
0125       d      (new Private(this))
0126 {
0127     d->parent = parent;
0128 
0129     if (!actionCollection)
0130     {
0131         d->stdActionCollection = ImportUI::instance()->actionCollection();
0132     }
0133     else
0134     {
0135         d->stdActionCollection = actionCollection;
0136     }
0137 }
0138 
0139 ImportContextMenuHelper::~ImportContextMenuHelper()
0140 {
0141     delete d;
0142 }
0143 
0144 void ImportContextMenuHelper::addAction(const QString& name, bool addDisabled)
0145 {
0146     QAction* const action = d->stdActionCollection->action(name);
0147     addAction(action, addDisabled);
0148 }
0149 
0150 void ImportContextMenuHelper::addAction(QAction* action, bool addDisabled)
0151 {
0152     if (!action)
0153     {
0154         return;
0155     }
0156 
0157     if (action->isEnabled() || addDisabled)
0158     {
0159         d->parent->addAction(action);
0160     }
0161 }
0162 
0163 void ImportContextMenuHelper::addSubMenu(QMenu* subMenu)
0164 {
0165     d->parent->addMenu(subMenu);
0166 }
0167 
0168 void ImportContextMenuHelper::addSeparator()
0169 {
0170     d->parent->addSeparator();
0171 }
0172 
0173 void ImportContextMenuHelper::addAction(QAction* action, QObject* recv, const char* slot,
0174                                         bool addDisabled)
0175 {
0176     if (!action)
0177     {
0178         return;
0179     }
0180 
0181     connect(action, SIGNAL(triggered()),
0182             recv, slot);
0183 
0184     addAction(action, addDisabled);
0185 }
0186 
0187 void ImportContextMenuHelper::addServicesMenu(const QList<QUrl>& selectedItems)
0188 {
0189     setSelectedItems(selectedItems);
0190 
0191     // This code is inspired by KonqMenuActions:
0192     // kdebase/apps/lib/konq/konq_menuactions.cpp
0193 
0194     QStringList    mimeTypes;
0195     KService::List offers;
0196 
0197     Q_FOREACH (const QUrl& item, d->selectedItems)
0198     {
0199         const QString mimeType = QMimeDatabase().mimeTypeForFile(item.toLocalFile(), QMimeDatabase::MatchExtension).name();
0200 
0201         if (!mimeTypes.contains(mimeType))
0202         {
0203             mimeTypes << mimeType;
0204         }
0205     }
0206 
0207     if (!mimeTypes.isEmpty())
0208     {
0209         // Query trader
0210 
0211         const QString firstMimeType      = mimeTypes.takeFirst();
0212         const QString constraintTemplate = QString::fromUtf8("'%1' in ServiceTypes");
0213         QStringList   constraints;
0214 
0215         Q_FOREACH (const QString& mimeType, mimeTypes)
0216         {
0217             constraints << constraintTemplate.arg(mimeType);
0218         }
0219 
0220 #if KSERVICE_VERSION > QT_VERSION_CHECK(5, 81, 0)
0221         offers = KApplicationTrader::queryByMimeType(firstMimeType);
0222 #else
0223         offers = KMimeTypeTrader::self()->query(firstMimeType,
0224                                                 QLatin1String("Application"),
0225                                                 constraints.join(QLatin1String(" and ")));
0226 #endif
0227 
0228         // remove duplicate service entries
0229 
0230         QSet<QString> seenApps;
0231 
0232         for (KService::List::iterator it = offers.begin() ; it != offers.end() ; )
0233         {
0234             const QString appName((*it)->name());
0235 
0236             if (!seenApps.contains(appName))
0237             {
0238                 seenApps.insert(appName);
0239                 ++it;
0240             }
0241             else
0242             {
0243                 it = offers.erase(it);
0244             }
0245         }
0246     }
0247 
0248     if      (!offers.isEmpty() && ImportUI::instance()->cameraUseUMSDriver())
0249     {
0250         QMenu* const servicesMenu    = new QMenu(d->parent);
0251         qDeleteAll(servicesMenu->actions());
0252 
0253         QAction* const serviceAction = servicesMenu->menuAction();
0254         serviceAction->setText(i18nc("@title:menu open with desktop application", "Open With"));
0255 
0256         Q_FOREACH (KService::Ptr service, offers)
0257         {
0258             QString name          = service->name().replace(QLatin1Char('&'), QLatin1String("&&"));
0259             QAction* const action = servicesMenu->addAction(name);
0260             action->setIcon(QIcon::fromTheme(service->icon()));
0261             action->setData(service->name());
0262             d->servicesMap[name] = service;
0263         }
0264 
0265 #ifdef HAVE_KIO
0266 
0267         servicesMenu->addSeparator();
0268         servicesMenu->addAction(i18nc("@item:inmenu open with other application", "Other..."));
0269 
0270         addAction(serviceAction);
0271 
0272         connect(servicesMenu, SIGNAL(triggered(QAction*)),
0273                 this, SLOT(slotOpenWith(QAction*)));
0274     }
0275     else if (ImportUI::instance()->cameraUseUMSDriver())
0276     {
0277         QAction* const serviceAction = new QAction(i18nc("@title:menu", "Open With..."), this);
0278         addAction(serviceAction);
0279 
0280         connect(serviceAction, SIGNAL(triggered()),
0281                 this, SLOT(slotOpenWith()));
0282 
0283 #endif // HAVE_KIO
0284 
0285     }
0286 }
0287 
0288 void ImportContextMenuHelper::slotOpenWith()
0289 {
0290     // call the slot with an "empty" action
0291 
0292     slotOpenWith(nullptr);
0293 }
0294 
0295 void ImportContextMenuHelper::slotOpenWith(QAction* action)
0296 {
0297     KService::Ptr service;
0298     QList<QUrl> list = d->selectedItems;
0299 
0300     QString name = action ? action->data().toString() : QString();
0301 
0302 #ifdef HAVE_KIO
0303 
0304     if (name.isEmpty())
0305     {
0306         QPointer<KOpenWithDialog> dlg = new KOpenWithDialog(list);
0307 
0308         if (dlg->exec() != KOpenWithDialog::Accepted)
0309         {
0310             delete dlg;
0311             return;
0312         }
0313 
0314         service = dlg->service();
0315 
0316         if (!service)
0317         {
0318             // User entered a custom command
0319 
0320             if (!dlg->text().isEmpty())
0321             {
0322                 DServiceMenu::runFiles(dlg->text(), list);
0323             }
0324 
0325             delete dlg;
0326             return;
0327         }
0328 
0329         delete dlg;
0330     }
0331     else
0332 
0333 #endif // HAVE_KIO
0334 
0335     {
0336         service = d->servicesMap[name];
0337     }
0338 
0339     DServiceMenu::runFiles(service, list);
0340 }
0341 
0342 void ImportContextMenuHelper::addRotateMenu(itemIds& /*ids*/)
0343 {
0344 /*
0345     setSelectedIds(ids);
0346 
0347     QMenu* const imageRotateMenu = new QMenu(i18n("Rotate"), d->parent);
0348     imageRotateMenu->setIcon(QIcon::fromTheme(QLatin1String("object-rotate-right")));
0349 
0350     QAction* const left = new QAction(this);
0351     left->setObjectName(QLatin1String("rotate_ccw"));
0352     left->setText(i18nc("rotate image left", "Left"));
0353     connect(left, SIGNAL(triggered(bool)),
0354             this, SLOT(slotRotate()));
0355     imageRotateMenu->addAction(left);
0356 
0357     QAction* const right = new QAction(this);
0358     right->setObjectName(QLatin1String("rotate_cw");
0359     right->setText(i18nc("rotate image right", "Right")));
0360     connect(right, SIGNAL(triggered(bool)),
0361             this, SLOT(slotRotate()));
0362     imageRotateMenu->addAction(right);
0363 
0364     d->parent->addMenu(imageRotateMenu);
0365 */
0366 }
0367 
0368 void ImportContextMenuHelper::slotRotate()
0369 {
0370 /*
0371     TODO: Implement rotate in import tool.
0372 
0373     if (sender()->objectName() == "rotate_ccw")
0374     {
0375         FileActionMngr::instance()->transform(CamItemInfoList(d->selectedIds), MetaEngineRotation::Rotate270);
0376     }
0377     else
0378     {
0379         FileActionMngr::instance()->transform(CamItemInfoList(d->selectedIds), MetaEngineRotation::Rotate90);
0380     }
0381 */
0382 }
0383 
0384 void ImportContextMenuHelper::addAssignTagsMenu(itemIds& /*ids*/)
0385 {
0386 /*
0387     setSelectedIds(ids);
0388 
0389     QMenu* const assignTagsPopup = new TagsPopupMenu(ids, TagsPopupMenu::RECENTLYASSIGNED, d->parent);
0390     assignTagsPopup->menuAction()->setText(i18n("Assign Tag"));
0391     assignTagsPopup->menuAction()->setIcon(QIcon::fromTheme(QLatin1String("tag")));
0392     d->parent->addMenu(assignTagsPopup);
0393 
0394     connect(assignTagsPopup, SIGNAL(signalTagActivated(int)),
0395             this, SIGNAL(signalAssignTag(int)));
0396 
0397     connect(assignTagsPopup, SIGNAL(signalPopupTagsView()),
0398             this, SIGNAL(signalPopupTagsView()));
0399 */
0400 }
0401 
0402 void ImportContextMenuHelper::addRemoveTagsMenu(itemIds& /*ids*/)
0403 {
0404 /*
0405     setSelectedIds(ids);
0406 
0407     QMenu* const removeTagsPopup = new TagsPopupMenu(ids, TagsPopupMenu::REMOVE, d->parent);
0408     removeTagsPopup->menuAction()->setText(i18n("Remove Tag"));
0409     removeTagsPopup->menuAction()->setIcon(QIcon::fromTheme(QLatin1String("tag")));
0410     d->parent->addMenu(removeTagsPopup);
0411 
0412     connect(removeTagsPopup, SIGNAL(signalTagActivated(int)),
0413             this, SIGNAL(signalRemoveTag(int)));
0414 */
0415 }
0416 
0417 void ImportContextMenuHelper::addLabelsAction()
0418 {
0419     QMenu* const menuLabels           = new QMenu(i18nc("@title:menu", "Assign Labels"), d->parent);
0420     PickLabelMenuAction* const pmenu  = new PickLabelMenuAction(d->parent);
0421     ColorLabelMenuAction* const cmenu = new ColorLabelMenuAction(d->parent);
0422     RatingMenuAction* const rmenu     = new RatingMenuAction(d->parent);
0423     menuLabels->addAction(pmenu->menuAction());
0424     menuLabels->addAction(cmenu->menuAction());
0425     menuLabels->addAction(rmenu->menuAction());
0426     addSubMenu(menuLabels);
0427 
0428     connect(pmenu, SIGNAL(signalPickLabelChanged(int)),
0429             this, SIGNAL(signalAssignPickLabel(int)));
0430 
0431     connect(cmenu, SIGNAL(signalColorLabelChanged(int)),
0432             this, SIGNAL(signalAssignColorLabel(int)));
0433 
0434     connect(rmenu, SIGNAL(signalRatingChanged(int)),
0435             this, SIGNAL(signalAssignRating(int)));
0436 }
0437 
0438 void ImportContextMenuHelper::slotABCMenuTriggered(QAction* action)
0439 {
0440     QString name = action->iconText();
0441     Q_EMIT signalAddNewTagFromABCMenu(name);
0442 }
0443 
0444 void ImportContextMenuHelper::setImportFilterModel(ImportFilterModel* model)
0445 {
0446     d->importFilterModel = model;
0447 }
0448 
0449 QAction* ImportContextMenuHelper::exec(const QPoint& pos, QAction* at)
0450 {
0451     QAction* const choice = d->parent->exec(pos, at);
0452 
0453     if (choice)
0454     {
0455         // check if a BQM action has been triggered
0456 
0457         for (QMap<int, QAction*>::const_iterator it = d->queueActions.constBegin() ;
0458              it != d->queueActions.constEnd() ; ++it)
0459         {
0460             if (choice == it.value())
0461             {
0462 /*
0463                 Q_EMIT signalAddToExistingQueue(it.key());
0464 */
0465                 return choice;
0466             }
0467         }
0468     }
0469 
0470     return choice;
0471 }
0472 
0473 void ImportContextMenuHelper::setSelectedIds(itemIds& ids)
0474 {
0475     if (d->selectedIds.isEmpty())
0476     {
0477         d->selectedIds = ids;
0478     }
0479 }
0480 
0481 void ImportContextMenuHelper::setSelectedItems(const QList<QUrl>& urls)
0482 {
0483     if (d->selectedItems.isEmpty())
0484     {
0485         d->selectedItems = urls;
0486     }
0487 }
0488 
0489 } // namespace Digikam
0490 
0491 #include "moc_importcontextmenu.cpp"