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

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2002-16-10
0007  * Description : main digiKam interface implementation - Solid API based methods
0008  *
0009  * SPDX-FileCopyrightText: 2002-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0010  *
0011  * SPDX-License-Identifier: GPL-2.0-or-later
0012  *
0013  * ============================================================ */
0014 
0015 #include "digikamapp_p.h"
0016 
0017 // Solid includes
0018 
0019 #if defined(Q_CC_CLANG)
0020 #   pragma clang diagnostic push
0021 #   pragma clang diagnostic ignored "-Wnonportable-include-path"
0022 #endif
0023 
0024 #include <solid/solidnamespace.h>
0025 #include <solid/camera.h>
0026 #include <solid/device.h>
0027 #include <solid/deviceinterface.h>
0028 #include <solid/devicenotifier.h>
0029 #include <solid/predicate.h>
0030 #include <solid/storageaccess.h>
0031 #include <solid/storagedrive.h>
0032 #include <solid/storagevolume.h>
0033 
0034 #if defined(Q_CC_CLANG)
0035 #   pragma clang diagnostic pop
0036 #endif
0037 
0038 namespace Digikam
0039 {
0040 
0041 // NOTE: static methods to not expose whole digiKam to Solid API.
0042 
0043 bool s_checkSolidCamera(const Solid::Device& cameraDevice)
0044 {
0045     const Solid::Camera* const camera = cameraDevice.as<Solid::Camera>();
0046 
0047     if (!camera)
0048     {
0049         qCDebug(DIGIKAM_GENERAL_LOG) << "Solid device" << cameraDevice.description() << "is not a camera";
0050         return false;
0051     }
0052 
0053     QStringList drivers = camera->supportedDrivers();
0054 
0055     qCDebug(DIGIKAM_GENERAL_LOG) << "checkSolidCamera: Found Camera "
0056                                  << QString::fromUtf8("%1 %2").arg(cameraDevice.vendor()).arg(cameraDevice.product())
0057                                  << " protocols " << camera->supportedProtocols()
0058                                  << " drivers " << camera->supportedDrivers(QLatin1String("ptp"));
0059 
0060     // We handle gphoto2 cameras in this loop
0061 
0062     if (!(camera->supportedDrivers().contains(QLatin1String("gphoto")) ||
0063         camera->supportedProtocols().contains(QLatin1String("ptp"))))
0064     {
0065         return false;
0066     }
0067 
0068     QVariant driverHandle = camera->driverHandle(QLatin1String("gphoto"));
0069 
0070 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0071 
0072     if (!driverHandle.canConvert(QMetaType(QMetaType::QVariantList)))
0073 
0074 #else
0075 
0076     if (!driverHandle.canConvert(QMetaType::QVariantList))
0077 
0078 #endif
0079 
0080     {
0081         qCWarning(DIGIKAM_GENERAL_LOG) << "Solid returns unsupported driver handle for gphoto2";
0082         return false;
0083     }
0084 
0085     QList<QVariant> driverHandleList = driverHandle.toList();
0086 
0087     if ((driverHandleList.size() < 3)                                  ||
0088         (driverHandleList.at(0).toString() != QLatin1String("usb"))    ||
0089 
0090 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0091 
0092         !driverHandleList.at(1).canConvert(QMetaType(QMetaType::Int))  ||
0093         !driverHandleList.at(2).canConvert(QMetaType(QMetaType::Int)))
0094 
0095 #else
0096 
0097         !driverHandleList.at(1).canConvert(QMetaType::Int)             ||
0098         !driverHandleList.at(2).canConvert(QMetaType::Int))
0099 
0100 #endif
0101 
0102     {
0103         qCWarning(DIGIKAM_GENERAL_LOG) << "Solid returns unsupported driver handle for gphoto2";
0104         return false;
0105     }
0106 
0107     return true;
0108 }
0109 
0110 QString s_labelForSolidCamera(const Solid::Device& cameraDevice)
0111 {
0112     QString vendor  = cameraDevice.vendor();
0113     QString product = cameraDevice.product();
0114 
0115     if (product == QLatin1String("USB Imaging Interface") ||
0116         product == QLatin1String("USB Vendor Specific Interface"))
0117     {
0118         Solid::Device parentUsbDevice = cameraDevice.parent();
0119 
0120         if (parentUsbDevice.isValid())
0121         {
0122             vendor  = parentUsbDevice.vendor();
0123             product = parentUsbDevice.product();
0124 
0125             if (!vendor.isEmpty() && !product.isEmpty())
0126             {
0127                 if (vendor == QLatin1String("Canon, Inc."))
0128                 {
0129                     vendor = QLatin1String("Canon");
0130 
0131                     if (product.startsWith(QLatin1String("Canon ")))
0132                     {
0133                         product = product.mid(6);    // cut off another "Canon " from product
0134                     }
0135 
0136                     if (product.endsWith(QLatin1String(" (ptp)")))
0137                     {
0138                         product.chop(6);             // cut off " (ptp)"
0139                     }
0140                 }
0141                 else if (vendor == QLatin1String("Fuji Photo Film Co., Ltd"))
0142                 {
0143                     vendor = QLatin1String("Fuji");
0144                 }
0145                 else if (vendor == QLatin1String("Nikon Corp."))
0146                 {
0147                     vendor = QLatin1String("Nikon");
0148 
0149                     if (product.startsWith(QLatin1String("NIKON ")))
0150                     {
0151                         product = product.mid(6);
0152                     }
0153                 }
0154             }
0155         }
0156     }
0157 
0158     return vendor + QLatin1Char(' ') + product;
0159 }
0160 
0161 // --------------------------------------------------------------------------------------------------
0162 
0163 void DigikamApp::fillSolidMenus()
0164 {
0165     QHash<QString, QDateTime> newAppearanceTimes;
0166     d->usbMediaMenu->clear();
0167     d->cardReaderMenu->clear();
0168 
0169     // delete the actionGroups to avoid duplicate menu entries
0170 
0171     delete d->solidUsmActionGroup;
0172     delete d->solidCameraActionGroup;
0173 
0174     d->solidCameraActionGroup = new QActionGroup(this);
0175 
0176     connect(d->solidCameraActionGroup, SIGNAL(triggered(QAction*)),
0177             this, SLOT(slotOpenSolidCamera(QAction*)));
0178 
0179     d->solidUsmActionGroup = new QActionGroup(this);
0180 
0181     connect(d->solidUsmActionGroup, SIGNAL(triggered(QAction*)),
0182             this, SLOT(slotOpenSolidUsmDevice(QAction*)));
0183 
0184     // --------------------------------------------------------
0185 
0186     QList<Solid::Device> cameraDevices = Solid::Device::listFromType(Solid::DeviceInterface::Camera);
0187 
0188     Q_FOREACH (const Solid::Device& cameraDevice, cameraDevices)
0189     {
0190         // USM camera: will be handled below
0191 
0192         if (cameraDevice.is<Solid::StorageAccess>())
0193         {
0194             continue;
0195         }
0196 
0197         if (!s_checkSolidCamera(cameraDevice))
0198         {
0199             continue;
0200         }
0201 
0202         // --------------------------------------------------------
0203 
0204         QString l     = s_labelForSolidCamera(cameraDevice);
0205         QString label = CameraNameHelper::cameraNameAutoDetected(l.trimmed());
0206 
0207         // --------------------------------------------------------
0208 
0209         QString iconName = cameraDevice.icon();
0210 
0211         if (iconName.isEmpty())
0212         {
0213             iconName = QLatin1String("camera-photo");
0214         }
0215 
0216         QAction* const action = new QAction(label, d->solidCameraActionGroup);
0217 
0218         action->setIcon(QIcon::fromTheme(iconName));
0219 
0220         // set data to identify device in action slot slotSolidSetupDevice
0221 
0222         action->setData(cameraDevice.udi());
0223         newAppearanceTimes[cameraDevice.udi()] = d->cameraAppearanceTimes.contains(cameraDevice.udi()) ?
0224                                                  d->cameraAppearanceTimes.value(cameraDevice.udi())    :
0225                                                  QDateTime::currentDateTime();
0226 
0227         d->cameraMenu->addAction(action);
0228     }
0229 
0230     QList<Solid::Device> storageDevices = Solid::Device::listFromType(Solid::DeviceInterface::StorageAccess);
0231 
0232     Q_FOREACH (const Solid::Device& accessDevice, storageDevices)
0233     {
0234         // check for StorageAccess
0235 
0236         if (!accessDevice.is<Solid::StorageAccess>())
0237         {
0238             continue;
0239         }
0240 
0241         // check for StorageDrive
0242 
0243         Solid::Device driveDevice;
0244 
0245         for (Solid::Device currentDevice = accessDevice ;
0246              currentDevice.isValid() ;
0247              currentDevice = currentDevice.parent())
0248         {
0249             if (currentDevice.is<Solid::StorageDrive>())
0250             {
0251                 driveDevice = currentDevice;
0252                 break;
0253             }
0254         }
0255 
0256         if (!driveDevice.isValid())
0257         {
0258             continue;
0259         }
0260 
0261         const Solid::StorageDrive* const drive = driveDevice.as<Solid::StorageDrive>();
0262 
0263         QString driveType;
0264 
0265         bool isHarddisk = false;
0266 
0267         switch (drive->driveType())
0268         {
0269             // skip these
0270 
0271             case Solid::StorageDrive::CdromDrive:
0272             case Solid::StorageDrive::Floppy:
0273             case Solid::StorageDrive::Tape:
0274             default:
0275                 continue;
0276 
0277             // accept card readers
0278 
0279             case Solid::StorageDrive::CompactFlash:
0280                 driveType = i18n("CompactFlash Card Reader");
0281                 break;
0282 
0283             case Solid::StorageDrive::MemoryStick:
0284                 driveType = i18n("Memory Stick Reader");
0285                 break;
0286 
0287             case Solid::StorageDrive::SmartMedia:
0288                 driveType = i18n("SmartMedia Card Reader");
0289                 break;
0290 
0291             case Solid::StorageDrive::SdMmc:
0292                 driveType = i18n("SD / MMC Card Reader");
0293                 break;
0294 
0295             case Solid::StorageDrive::Xd:
0296                 driveType = i18n("xD Card Reader");
0297                 break;
0298 
0299             case Solid::StorageDrive::HardDisk:
0300 
0301                 // We don't want to list HardDisk partitions, but USB Mass Storage devices.
0302                 // Don't know what is the exact difference between removable and hotpluggable.
0303 
0304                 if (drive->isRemovable() || drive->isHotpluggable())
0305                 {
0306                     isHarddisk = true;
0307 
0308                     if (drive->bus() == Solid::StorageDrive::Usb)
0309                     {
0310                         driveType = i18n("USB Disk");
0311                     }
0312                     else
0313                     {
0314                         driveType = i18nc("non-USB removable storage device", "Disk");
0315                     }
0316 
0317                     break;
0318                 }
0319                 else
0320                 {
0321                     continue;
0322                 }
0323         }
0324 
0325         // check for StorageVolume
0326 
0327         Solid::Device volumeDevice;
0328 
0329         for (Solid::Device currentDevice = accessDevice ;
0330              currentDevice.isValid() ;
0331              currentDevice = currentDevice.parent())
0332         {
0333             if (currentDevice.is<Solid::StorageVolume>())
0334             {
0335                 volumeDevice = currentDevice;
0336                 break;
0337             }
0338         }
0339 
0340         if (!volumeDevice.isValid())
0341         {
0342             continue;
0343         }
0344 
0345         bool isCamera                            = accessDevice.is<Solid::Camera>();
0346         const Solid::StorageAccess* const access = accessDevice.as<Solid::StorageAccess>();
0347         const Solid::StorageVolume* const volume = volumeDevice.as<Solid::StorageVolume>();
0348 
0349         if (volume->isIgnored())
0350         {
0351             continue;
0352         }
0353 
0354         QString label;
0355 
0356         if (isCamera)
0357         {
0358             label = accessDevice.vendor() + QLatin1Char(' ') + accessDevice.product();
0359         }
0360         else
0361         {
0362             QString labelOrProduct;
0363 
0364             if      (!volume->label().isEmpty())
0365             {
0366                 labelOrProduct = volume->label();
0367             }
0368             else if (!volumeDevice.product().isEmpty())
0369             {
0370                 labelOrProduct = volumeDevice.product();
0371             }
0372             else if (!volumeDevice.vendor().isEmpty())
0373             {
0374                 labelOrProduct = volumeDevice.vendor();
0375             }
0376             else if (!driveDevice.product().isEmpty())
0377             {
0378                 labelOrProduct = driveDevice.product();
0379             }
0380 
0381             if (!labelOrProduct.isNull())
0382             {
0383                 if (!access->filePath().isEmpty())
0384                 {
0385                     label += i18nc("<drive type> \"<device name or label>\" at <mount path>",
0386                                    "%1 \"%2\" at %3", driveType, labelOrProduct,
0387                                    QDir::toNativeSeparators(access->filePath()));
0388                 }
0389                 else
0390                 {
0391                     label += i18nc("<drive type> \"<device name or label>\"",
0392                                    "%1 \"%2\"", driveType, labelOrProduct);
0393                 }
0394             }
0395             else
0396             {
0397                 if (!access->filePath().isEmpty())
0398                 {
0399                     label += i18nc("<drive type> at <mount path>",
0400                                    "%1 at %2", driveType,
0401                                    QDir::toNativeSeparators(access->filePath()));
0402                 }
0403                 else
0404                 {
0405                     label += driveType;
0406                 }
0407             }
0408 
0409             if (volume->size())
0410             {
0411                 label += i18nc("device label etc... (<formatted byte size>)",
0412                                " (%1)", ItemPropertiesTab::humanReadableBytesCount(volume->size()));
0413             }
0414         }
0415 
0416         QString iconName;
0417 
0418         if      (!driveDevice.icon().isEmpty())
0419         {
0420             iconName = driveDevice.icon();
0421         }
0422         else if (!accessDevice.icon().isEmpty())
0423         {
0424             iconName = accessDevice.icon();
0425         }
0426         else if (!volumeDevice.icon().isEmpty())
0427         {
0428             iconName = volumeDevice.icon();
0429         }
0430 
0431         QAction* const action = new QAction(label, d->solidUsmActionGroup);
0432 
0433         if (!iconName.isEmpty())
0434         {
0435             action->setIcon(QIcon::fromTheme(iconName));
0436         }
0437 
0438         // set data to identify device in action slot slotSolidSetupDevice
0439 
0440         action->setData(accessDevice.udi());
0441         newAppearanceTimes[accessDevice.udi()] = d->cameraAppearanceTimes.contains(accessDevice.udi()) ?
0442                                                  d->cameraAppearanceTimes.value(accessDevice.udi())    :
0443                                                  QDateTime::currentDateTime();
0444 
0445         if (isCamera)
0446         {
0447             d->cameraMenu->addAction(action);
0448         }
0449 
0450         if (isHarddisk)
0451         {
0452             d->usbMediaMenu->addAction(action);
0453         }
0454         else
0455         {
0456             d->cardReaderMenu->addAction(action);
0457         }
0458     }
0459 
0460 /*
0461     //TODO: Find best usable solution when no devices are connected: One entry, hide, or disable?
0462 
0463     // Add one entry telling that no device is available
0464 
0465     if (d->cameraSolidMenu->isEmpty())
0466     {
0467         QAction* const action = d->cameraSolidMenu->addAction(i18n("No Camera Connected"));
0468         action->setEnabled(false);
0469     }
0470 
0471     if (d->usbMediaMenu->isEmpty())
0472     {
0473         QAction* const action = d->usbMediaMenu->addAction(i18n("No Storage Devices Found"));
0474         action->setEnabled(false);
0475     }
0476 
0477     if (d->cardReaderMenu->isEmpty())
0478     {
0479         QAction* const action = d->cardReaderMenu->addAction(i18n("No Card Readers Available"));
0480         action->setEnabled(false);
0481     }
0482 
0483     // hide empty menus
0484 
0485     d->cameraSolidMenu->menuAction()->setVisible(!d->cameraSolidMenu->isEmpty());
0486     d->usbMediaMenu->menuAction()->setVisible(!d->usbMediaMenu->isEmpty());
0487     d->cardReaderMenu->menuAction()->setVisible(!d->cardReaderMenu->isEmpty());
0488 */
0489 
0490     d->cameraAppearanceTimes = newAppearanceTimes;
0491 
0492     // disable empty menus
0493 
0494     d->usbMediaMenu->setEnabled(!d->usbMediaMenu->isEmpty());
0495     d->cardReaderMenu->setEnabled(!d->cardReaderMenu->isEmpty());
0496 
0497     updateCameraMenu();
0498     updateQuickImportAction();
0499 }
0500 
0501 void DigikamApp::connectToSolidNotifiers()
0502 {
0503     connect(Solid::DeviceNotifier::instance(), SIGNAL(deviceAdded(QString)),
0504             this, SLOT(slotSolidDeviceChanged(QString)),
0505             Qt::QueuedConnection);
0506 
0507     connect(Solid::DeviceNotifier::instance(), SIGNAL(deviceRemoved(QString)),
0508             this, SLOT(slotSolidDeviceChanged(QString)),
0509             Qt::QueuedConnection);
0510 
0511     // -- queued connections -------------------------------------------
0512 
0513     connect(this, SIGNAL(queuedOpenCameraUiFromPath(QString)),
0514             this, SLOT(slotOpenCameraUiFromPath(QString)),
0515             Qt::QueuedConnection);
0516 
0517     connect(this, SIGNAL(queuedOpenSolidDevice(QString)),
0518             this, SLOT(slotOpenSolidDevice(QString)),
0519             Qt::QueuedConnection);
0520 }
0521 
0522 void DigikamApp::openSolidCamera(const QString& udi, const QString& cameraLabel)
0523 {
0524     // if there is already an open ImportUI for the device, show and raise it, and be done
0525 
0526     if (d->cameraUIMap.contains(udi))
0527     {
0528         ImportUI* const ui = d->cameraUIMap.value(udi);
0529 
0530         if (ui && !ui->isClosed())
0531         {
0532             ui->unminimizeAndActivateWindow();
0533 
0534             return;
0535         }
0536     }
0537 
0538     // recreate device from unambiguous UDI
0539 
0540     Solid::Device device(udi);
0541 
0542     if (device.isValid())
0543     {
0544         if (cameraLabel.isNull())
0545         {
0546             QString label = s_labelForSolidCamera(device);
0547         }
0548 
0549         Solid::Camera* const camera = device.as<Solid::Camera>();
0550         QList<QVariant> list        = camera->driverHandle(QLatin1String("gphoto")).toList();
0551 
0552         // all sanity checks have already been done when creating the action
0553 
0554         if (list.size() < 3)
0555         {
0556             return;
0557         }
0558 
0559         // NOTE: See bug #262296: With KDE 4.6, Solid API return device vendor id
0560         // and product id in hexadecimal strings.
0561 
0562         bool ok;
0563         int vendorId  = list.at(1).toString().toInt(&ok, 16);
0564         int productId = list.at(2).toString().toInt(&ok, 16);
0565         QString model, port;
0566 
0567         if (CameraList::findConnectedCamera(vendorId, productId, model, port))
0568         {
0569             qCDebug(DIGIKAM_GENERAL_LOG) << "Found camera from ids " << vendorId << " " << productId
0570                                          << " camera is: " << model << " at " << port;
0571 
0572             // the ImportUI will delete itself when it has finished
0573 
0574             ImportUI* const cgui = new ImportUI(cameraLabel, model, port, QLatin1String("/"), 1);
0575             d->cameraUIMap[udi]  = cgui;
0576 
0577             cgui->show();
0578 
0579             connect(cgui, SIGNAL(signalLastDestination(QUrl)),
0580                     d->view, SLOT(slotSelectAlbum(QUrl)));
0581         }
0582         else
0583         {
0584             qCDebug(DIGIKAM_GENERAL_LOG) << "Failed to detect camera with GPhoto2 from Solid information";
0585         }
0586     }
0587 }
0588 
0589 void DigikamApp::openSolidUsmDevice(const QString& udi, const QString& givenLabel)
0590 {
0591     QString mediaLabel = givenLabel;
0592 
0593     // if there is already an open ImportUI for the device, show and raise it
0594 
0595     if (d->cameraUIMap.contains(udi))
0596     {
0597         ImportUI* const ui = d->cameraUIMap.value(udi);
0598 
0599         if (ui && !ui->isClosed())
0600         {
0601             ui->unminimizeAndActivateWindow();
0602 
0603             return;
0604         }
0605     }
0606 
0607     // recreate device from unambiguous UDI
0608 
0609     Solid::Device device(udi);
0610 
0611     if (device.isValid())
0612     {
0613         Solid::StorageAccess* const access = device.as<Solid::StorageAccess>();
0614 
0615         if (!access)
0616         {
0617             return;
0618         }
0619 
0620         if (!access->isAccessible())
0621         {
0622             QApplication::setOverrideCursor(Qt::WaitCursor);
0623 
0624             if (!access->setup())
0625             {
0626                 return;
0627             }
0628 
0629             d->eventLoop = new QEventLoop(this);
0630 
0631             // NOTE: Lambda function to not expose whole digiKam to Solid API.
0632 
0633             connect(access, &Solid::StorageAccess::setupDone,
0634                     this, [=](Solid::ErrorType errorType, QVariant errorData, const QString& /*udi*/)  // clazy:exclude=function-args-by-ref
0635                 {
0636                     if (!d->eventLoop)
0637                     {
0638                         return;
0639                     }
0640 
0641                     if (errorType == Solid::NoError)
0642                     {
0643                         d->eventLoop->exit(0);
0644                     }
0645                     else
0646                     {
0647                         d->solidErrorMessage  = i18n("Cannot access the storage device.\n");
0648                         d->solidErrorMessage += errorData.toString();
0649                         d->eventLoop->exit(1);
0650                     }
0651                 }
0652             );
0653 
0654             int returnCode = d->eventLoop->exec(QEventLoop::ExcludeUserInputEvents);
0655 
0656             delete d->eventLoop;
0657             d->eventLoop = nullptr;
0658             QApplication::restoreOverrideCursor();
0659 
0660             if (returnCode == 1)
0661             {
0662                 QMessageBox::critical(this, qApp->applicationName(), d->solidErrorMessage);
0663                 return;
0664             }
0665         }
0666 
0667         // Create Camera UI
0668 
0669         QString path = QDir::fromNativeSeparators(access->filePath());
0670 
0671         if (mediaLabel.isNull())
0672         {
0673             mediaLabel = path;
0674         }
0675 
0676         // the ImportUI will delete itself when it has finished
0677 
0678         ImportUI* const cgui = new ImportUI(i18n("Images on %1", mediaLabel),
0679                                             QLatin1String("directory browse"),
0680                                             QLatin1String("Fixed"), path, 1);
0681         d->cameraUIMap[udi]  = cgui;
0682 
0683         cgui->show();
0684 
0685         connect(cgui, SIGNAL(signalLastDestination(QUrl)),
0686                 d->view, SLOT(slotSelectAlbum(QUrl)));
0687     }
0688 }
0689 
0690 void DigikamApp::slotOpenSolidCamera(QAction* action)
0691 {
0692     QString udi = action->data().toString();
0693     openSolidCamera(udi, action->iconText());
0694 }
0695 
0696 void DigikamApp::slotOpenSolidUsmDevice(QAction* action)
0697 {
0698     QString udi = action->data().toString();
0699     openSolidUsmDevice(udi, action->iconText());
0700 }
0701 
0702 void DigikamApp::slotOpenSolidDevice(const QString& udi)
0703 {
0704     // Identifies device as either Camera or StorageAccess and calls methods accordingly
0705 
0706     Solid::Device device(udi);
0707 
0708     if (!device.isValid())
0709     {
0710         QMessageBox::critical(this, qApp->applicationName(),
0711                               i18n("The specified device (\"%1\") is not valid.", udi));
0712         return;
0713     }
0714 
0715     if      (device.is<Solid::StorageAccess>())
0716     {
0717         openSolidUsmDevice(udi);
0718     }
0719     else if (device.is<Solid::Camera>())
0720     {
0721         if (!s_checkSolidCamera(device))
0722         {
0723             QMessageBox::critical(this, qApp->applicationName(),
0724                                   i18n("The specified camera (\"%1\") is not supported.", udi));
0725             return;
0726         }
0727 
0728         openSolidCamera(udi);
0729     }
0730 }
0731 
0732 void DigikamApp::slotSolidDeviceChanged(const QString& udi)
0733 {
0734     qCDebug(DIGIKAM_GENERAL_LOG) << "slotSolidDeviceChanged:" << udi;
0735     fillSolidMenus();
0736 }
0737 
0738 } // namespace Digikam