diff --git a/core/libs/database/collection/collectionmanager.cpp b/core/libs/database/collection/collectionmanager.cpp index 46bb787399..945186d3d8 100644 --- a/core/libs/database/collection/collectionmanager.cpp +++ b/core/libs/database/collection/collectionmanager.cpp @@ -1,1803 +1,1803 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2007-04-09 * Description : Collection location management * * Copyright (C) 2007-2009 by Marcel Wiesweg * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "collectionmanager.h" // Qt includes #include #include #include #include #include // KDE includes #include #include #include #include #include #include #include #include #include // Local includes #include "digikam_debug.h" #include "coredbaccess.h" #include "coredbchangesets.h" #include "coredbtransaction.h" #include "coredb.h" #include "collectionscanner.h" #include "collectionlocation.h" namespace Digikam { class AlbumRootLocation : public CollectionLocation { public: AlbumRootLocation() : available(false), hidden(false) { } explicit AlbumRootLocation(const AlbumRootInfo& info) { qCDebug(DIGIKAM_DATABASE_LOG) << "Creating new Location " << info.specificPath << " uuid " << info.identifier; m_id = info.id; m_type = (Type)info.type; specificPath = info.specificPath; identifier = info.identifier; m_label = info.label; m_path.clear(); setStatus((CollectionLocation::Status)info.status); } void setStatusFromFlags() { if (hidden) { m_status = CollectionLocation::LocationHidden; } else { if (available) { m_status = CollectionLocation::LocationAvailable; } else { m_status = CollectionLocation::LocationUnavailable; } } } void setStatus(CollectionLocation::Status s) { m_status = s; // status is exclusive, and Hidden wins // but really both states are independent // - a hidden location might or might not be available if (m_status == CollectionLocation::LocationAvailable) { available = true; hidden = false; } else if (m_status == CollectionLocation::LocationHidden) { available = false; hidden = true; } else // Unavailable, Null, Deleted { available = false; hidden = false; } } void setId(int id) { m_id = id; } void setAbsolutePath(const QString& path) { m_path = path; } void setType(Type type) { m_type = type; } void setLabel(const QString& label) { m_label = label; } public: QString identifier; QString specificPath; bool available; bool hidden; }; // ------------------------------------------------- class SolidVolumeInfo { public: SolidVolumeInfo() : isRemovable(false), isOpticalDisc(false), isMounted(false) { } bool isNull() const { return path.isNull(); } public: QString udi; // Solid device UDI of the StorageAccess device QString path; // mount path of volume, with trailing slash QString uuid; // UUID as from Solid QString label; // volume label (think of CDs) bool isRemovable; // may be removed bool isOpticalDisc; // is an optical disk device as CD/DVD/BR bool isMounted; // is mounted on File System. }; // ------------------------------------------------- class CollectionManagerPrivate { public: explicit CollectionManagerPrivate(CollectionManager* s); // hack for Solid's threading problems QList actuallyListVolumes(); void slotTriggerUpdateVolumesList(); QList volumesListCache; /// Access Solid and return a list of storage volumes QList listVolumes(); /** * Find from a given list (usually the result of listVolumes) the volume * corresponding to the location */ SolidVolumeInfo findVolumeForLocation(const AlbumRootLocation* location, const QList volumes); /** * Find from a given list (usually the result of listVolumes) the volume * on which the file path specified by the url is located. */ SolidVolumeInfo findVolumeForUrl(const QUrl& fileUrl, const QList volumes); /// Create the volume identifier for the given volume info static QString volumeIdentifier(const SolidVolumeInfo& info); /// Create a volume identifier based on the path only QString volumeIdentifier(const QString& path); /// Create a network share identifier based on the mountpath QString networkShareIdentifier(const QString& path); /// Return the path, if location has a path-only identifier. Else returns a null string. QString pathFromIdentifier(const AlbumRootLocation* location); /// Return the path, if location has a path-only identifier. Else returns a null string. QStringList networkShareMountPathsFromIdentifier(const AlbumRootLocation* location); /// Create an MD5 hash of the top-level entries (file names, not file content) of the given path static QString directoryHash(const QString& path); /// Check if a location for specified path exists, assuming the given list of locations was deleted bool checkIfExists(const QString& path, QList assumeDeleted); /// Make a user presentable description, regardless of current location status QString technicalDescription(const AlbumRootLocation* location); public: QMap locations; bool changingDB; QStringList udisToWatch; bool watchEnabled; CollectionManager* s; }; // ------------------------------------------------- class ChangingDB { public: explicit ChangingDB(CollectionManagerPrivate* d) : d(d) { d->changingDB = true; } ~ChangingDB() { d->changingDB = false; } public: CollectionManagerPrivate* const d; }; } // namespace Digikam // This is because of the private slot; we'd want a collectionmanager_p.h #include "collectionmanager.h" // krazy:exclude=includes namespace Digikam { CollectionManagerPrivate::CollectionManagerPrivate(CollectionManager* s) : changingDB(false), watchEnabled(false), s(s) { QObject::connect(s, SIGNAL(triggerUpdateVolumesList()), s, SLOT(slotTriggerUpdateVolumesList()), Qt::BlockingQueuedConnection); } QList CollectionManagerPrivate::listVolumes() { // Move calls to Solid to the main thread. // Solid was meant to be thread-safe, but it is not (KDE4.0), // calling from a non-UI thread leads to a reversible // lock-up of variable length. if (QThread::currentThread() == QCoreApplication::instance()->thread()) { return actuallyListVolumes(); } else { // emit a blocking queued signal to move call to main thread emit(s->triggerUpdateVolumesList()); return volumesListCache; } } void CollectionManagerPrivate::slotTriggerUpdateVolumesList() { volumesListCache = actuallyListVolumes(); } QList CollectionManagerPrivate::actuallyListVolumes() { QList volumes; //qCDebug(DIGIKAM_DATABASE_LOG) << "listFromType"; QList devices = Solid::Device::listFromType(Solid::DeviceInterface::StorageAccess); //qCDebug(DIGIKAM_DATABASE_LOG) << "got listFromType"; udisToWatch.clear(); foreach(const Solid::Device& accessDevice, devices) { // check for StorageAccess if (!accessDevice.is()) { continue; } // mark as a device of principal interest udisToWatch << accessDevice.udi(); const Solid::StorageAccess* access = accessDevice.as(); // watch mount status (remove previous connections) QObject::disconnect(access, SIGNAL(accessibilityChanged(bool,QString)), s, SLOT(accessibilityChanged(bool,QString))); QObject::connect(access, SIGNAL(accessibilityChanged(bool,QString)), s, SLOT(accessibilityChanged(bool,QString))); if (!access->isAccessible()) { continue; } // check for StorageDrive Solid::Device driveDevice; for (Solid::Device currentDevice = accessDevice; currentDevice.isValid(); currentDevice = currentDevice.parent()) { if (currentDevice.is()) { driveDevice = currentDevice; break; } } /* * We cannot require a drive, some logical volumes may not have "one" drive as parent * See bug 273369 if (!driveDevice.isValid()) { continue; } */ Solid::StorageDrive* drive = driveDevice.as(); // check for StorageVolume Solid::Device volumeDevice; for (Solid::Device currentDevice = accessDevice; currentDevice.isValid(); currentDevice = currentDevice.parent()) { if (currentDevice.is()) { volumeDevice = currentDevice; break; } } if (!volumeDevice.isValid()) { continue; } Solid::StorageVolume* const volume = volumeDevice.as(); SolidVolumeInfo info; info.udi = accessDevice.udi(); info.path = QDir::fromNativeSeparators(access->filePath()); info.isMounted = access->isAccessible(); if (!info.path.isEmpty() && !info.path.endsWith(QLatin1Char('/'))) { info.path += QLatin1Char('/'); } info.uuid = volume->uuid(); info.label = volume->label(); if (drive) { info.isRemovable = drive->isHotpluggable() || drive->isRemovable(); } else { // impossible to know, but probably not hotpluggable (see comment above) info.isRemovable = false; } info.isOpticalDisc = volumeDevice.is(); volumes << info; } // This is the central place where the watch is enabled watchEnabled = true; return volumes; } QString CollectionManagerPrivate::volumeIdentifier(const SolidVolumeInfo& volume) { QUrl url; url.setScheme(QLatin1String("volumeid")); // On changing these, please update the checkLocation() code bool identifyByUUID = !volume.uuid.isEmpty(); bool identifyByLabel = !identifyByUUID && !volume.label.isEmpty() && (volume.isOpticalDisc || volume.isRemovable); bool addDirectoryHash = identifyByLabel && volume.isOpticalDisc; bool identifyByMountPath = !identifyByUUID && !identifyByLabel; if (identifyByUUID) { QUrlQuery q(url); q.addQueryItem(QLatin1String("uuid"), volume.uuid); url.setQuery(q); } if (identifyByLabel) { QUrlQuery q(url); q.addQueryItem(QLatin1String("label"), volume.label); url.setQuery(q); } if (addDirectoryHash) { // for CDs, we store a hash of the root directory. May be useful. QString dirHash = directoryHash(volume.path); if (!dirHash.isNull()) { QUrlQuery q(url); q.addQueryItem(QLatin1String("directoryhash"), dirHash); url.setQuery(q); } } if (identifyByMountPath) { QUrlQuery q(url); q.addQueryItem(QLatin1String("mountpath"), volume.path); url.setQuery(q); } return url.url(); } QString CollectionManagerPrivate::volumeIdentifier(const QString& path) { QUrl url; url.setScheme(QLatin1String("volumeid")); QUrlQuery q(url); q.addQueryItem(QLatin1String("path"), path); url.setQuery(q); return url.url(); } QString CollectionManagerPrivate::networkShareIdentifier(const QString& path) { QUrl url; url.setScheme(QLatin1String("networkshareid")); QUrlQuery q(url); q.addQueryItem(QLatin1String("mountpath"), path); url.setQuery(q); return url.url(); } QString CollectionManagerPrivate::pathFromIdentifier(const AlbumRootLocation* location) { QUrl url(location->identifier); if (url.scheme() != QLatin1String("volumeid")) { return QString(); } return QUrlQuery(url).queryItemValue(QLatin1String("path")); } QStringList CollectionManagerPrivate::networkShareMountPathsFromIdentifier(const AlbumRootLocation* location) { // using a QUrl because QUrl cannot handle duplicate query items QUrl url(location->identifier); if (url.scheme() != QLatin1String("networkshareid")) { return QStringList(); } return QUrlQuery(url).allQueryItemValues(QLatin1String("mountpath")); } QString CollectionManagerPrivate::directoryHash(const QString& path) { QDir dir(path); if (dir.isReadable()) { QStringList entries = dir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); QCryptographicHash md5(QCryptographicHash::Md5); foreach(const QString& entry, entries) { md5.addData(entry.toUtf8()); } return QString::fromUtf8(md5.result().toHex()); } return QString(); } SolidVolumeInfo CollectionManagerPrivate::findVolumeForLocation(const AlbumRootLocation* location, const QList volumes) { QUrl url(location->identifier); QString queryItem; if (url.scheme() != QLatin1String("volumeid")) { return SolidVolumeInfo(); } if (!(queryItem = QUrlQuery(url).queryItemValue(QLatin1String("uuid"))).isNull()) { foreach(const SolidVolumeInfo& volume, volumes) { if (volume.uuid.compare(queryItem, Qt::CaseInsensitive) == 0) { return volume; } } return SolidVolumeInfo(); } else if (!(queryItem = QUrlQuery(url).queryItemValue(QLatin1String("label"))).isNull()) { // This one is a bit more difficult, as we take into account the possibility // that the label is not unique, and we take some care to make it work anyway. // find all available volumes with the given label (usually one) QList candidateVolumes; foreach(const SolidVolumeInfo& volume, volumes) { if (volume.label == queryItem) { candidateVolumes << volume; } } if (candidateVolumes.isEmpty()) { return SolidVolumeInfo(); } // find out of there is another location with the same label (usually not) bool hasOtherLocation = false; foreach(AlbumRootLocation* const otherLocation, locations) { if (otherLocation == location) { continue; } QUrl otherUrl(otherLocation->identifier); if (otherUrl.scheme() == QLatin1String("volumeid") && QUrlQuery(otherUrl).queryItemValue(QLatin1String("label")) == queryItem) { hasOtherLocation = true; break; } } // the usual, easy case if (candidateVolumes.size() == 1 && !hasOtherLocation) { return candidateVolumes.first(); } else { // not unique: try to use the directoryhash QString dirHash = QUrlQuery(url).queryItemValue(QLatin1String("directoryhash")); // bail out if not provided if (dirHash.isNull()) { qCDebug(DIGIKAM_DATABASE_LOG) << "No directory hash specified for the non-unique Label" << queryItem << "Resorting to returning the first match."; return candidateVolumes.first(); } // match against directory hash foreach(const SolidVolumeInfo& volume, candidateVolumes) { QString volumeDirHash = directoryHash(volume.path); if (volumeDirHash == dirHash) { return volume; } } } return SolidVolumeInfo(); } else if (!(queryItem = QUrlQuery(url).queryItemValue(QLatin1String("mountpath"))).isNull()) { foreach(const SolidVolumeInfo& volume, volumes) { if (volume.isMounted && volume.path == queryItem) { return volume; } } return SolidVolumeInfo(); } return SolidVolumeInfo(); } QString CollectionManagerPrivate::technicalDescription(const AlbumRootLocation* albumLoc) { QUrl url(albumLoc->identifier); QString queryItem; if (url.scheme() == QLatin1String("volumeid")) { if (!(queryItem = QUrlQuery(url).queryItemValue(QLatin1String("uuid"))).isNull()) { return i18nc("\"relative path\" on harddisk partition with \"UUID\"", "Folder \"%1\" on the volume with the id \"%2\"", QDir::toNativeSeparators(albumLoc->specificPath), queryItem); } else if (!(queryItem = QUrlQuery(url).queryItemValue(QLatin1String("label"))).isNull()) { return i18nc("\"relative path\" on harddisk partition with \"label\"", "Folder \"%1\" on the volume labeled \"%2\"", QDir::toNativeSeparators(albumLoc->specificPath), queryItem); } else if (!(queryItem = QUrlQuery(url).queryItemValue(QLatin1String("mountpath"))).isNull()) { return QString::fromUtf8("\"%1\"").arg(queryItem); } } else if (url.scheme() == QLatin1String("networkshareid")) { if (!(queryItem = QUrlQuery(url).queryItemValue(QLatin1String("mountpath"))).isNull()) { return i18nc("@info", "Shared directory mounted at %1", QDir::toNativeSeparators(queryItem)); } } return QString(); } SolidVolumeInfo CollectionManagerPrivate::findVolumeForUrl(const QUrl& fileUrl, const QList volumes) { SolidVolumeInfo volume; // v.path is specified to have a trailing slash. path needs one as well. - QString path = fileUrl.toLocalFile() + QLatin1String("/"); + QString path = fileUrl.toLocalFile() + QLatin1Char('/'); int volumeMatch = 0; //FIXME: Network shares! Here we get only the volume of the mount path... // This is probably not really clean. But Solid does not help us. foreach(const SolidVolumeInfo& v, volumes) { if (v.isMounted && !v.path.isEmpty() && path.startsWith(v.path)) { int length = v.path.length(); if (length > volumeMatch) { volumeMatch = v.path.length(); volume = v; } } } if (!volumeMatch) { qCDebug(DIGIKAM_DATABASE_LOG) << "Failed to detect a storage volume for path " << path << " with Solid"; } return volume; } bool CollectionManagerPrivate::checkIfExists(const QString& filePath, QList assumeDeleted) { const QUrl filePathUrl = QUrl::fromLocalFile(filePath); CoreDbAccess access; foreach(AlbumRootLocation* const location, locations) { const QUrl locationPathUrl = QUrl::fromLocalFile(location->albumRootPath()); //qCDebug(DIGIKAM_DATABASE_LOG) << filePathUrl << locationPathUrl; // make sure filePathUrl is neither a child nor a parent // of an existing collection if (!locationPathUrl.isEmpty() && (filePathUrl.isParentOf(locationPathUrl) || locationPathUrl.isParentOf(filePathUrl)) ) { bool isDeleted = false; foreach(const CollectionLocation& deletedLoc, assumeDeleted) { if (deletedLoc.id() == location->id()) { isDeleted = true; break; } } if (!isDeleted) { return true; } } } return false; } // ------------------------------------------------- CollectionManager* CollectionManager::m_instance = 0; CollectionManager* CollectionManager::instance() { if (!m_instance) { m_instance = new CollectionManager; } return m_instance; } void CollectionManager::cleanUp() { delete m_instance; m_instance = 0; } CollectionManager::CollectionManager() : d(new CollectionManagerPrivate(this)) { qRegisterMetaType("CollectionLocation"); connect(Solid::DeviceNotifier::instance(), SIGNAL(deviceAdded(QString)), this, SLOT(deviceAdded(QString))); connect(Solid::DeviceNotifier::instance(), SIGNAL(deviceRemoved(QString)), this, SLOT(deviceRemoved(QString))); // CoreDbWatch slot is connected at construction of CoreDbWatch, which may be later. } CollectionManager::~CollectionManager() { qDeleteAll(d->locations.values()); delete d; } void CollectionManager::refresh() { { // if called from the CoreDbAccess constructor itself, it will // hold a flag to prevent endless recursion CoreDbAccess access; clear_locked(); } updateLocations(); } void CollectionManager::setWatchDisabled() { d->watchEnabled = false; } CollectionLocation CollectionManager::addLocation(const QUrl& fileUrl, const QString& label) { qCDebug(DIGIKAM_DATABASE_LOG) << "addLocation " << fileUrl; QString path = fileUrl.adjusted(QUrl::StripTrailingSlash).toLocalFile(); if (!locationForPath(path).isNull()) { return CollectionLocation(); } QList volumes = d->listVolumes(); SolidVolumeInfo volume = d->findVolumeForUrl(fileUrl, volumes); if (!volume.isNull()) { CoreDbAccess access; // volume.path has a trailing slash. We want to split in front of this. QString specificPath = path.mid(volume.path.length() - 1); AlbumRoot::Type type; if (volume.isRemovable) { type = AlbumRoot::VolumeRemovable; } else { type = AlbumRoot::VolumeHardWired; } ChangingDB changing(d); access.db()->addAlbumRoot(type, d->volumeIdentifier(volume), specificPath, label); } else { // Empty volumes indicates that Solid is not working correctly. if (volumes.isEmpty()) { qCDebug(DIGIKAM_DATABASE_LOG) << "Solid did not return any storage volumes on your system."; qCDebug(DIGIKAM_DATABASE_LOG) << "This indicates a missing implementation or a problem with your installation"; qCDebug(DIGIKAM_DATABASE_LOG) << "On Linux, check that Solid and HAL are working correctly." "Problems with RAID partitions have been reported, if you have RAID this error may be normal."; qCDebug(DIGIKAM_DATABASE_LOG) << "On Windows, Solid may not be fully implemented, if you are running Windows this error may be normal."; } // fall back qCWarning(DIGIKAM_DATABASE_LOG) << "Unable to identify a path with Solid. Adding the location with path only."; ChangingDB changing(d); CoreDbAccess().db()->addAlbumRoot(AlbumRoot::VolumeHardWired, d->volumeIdentifier(path), QLatin1String("/"), label); } // Do not emit the locationAdded signal here, it is done in updateLocations() updateLocations(); return locationForPath(path); } CollectionLocation CollectionManager::addNetworkLocation(const QUrl& fileUrl, const QString& label) { qCDebug(DIGIKAM_DATABASE_LOG) << "addLocation " << fileUrl; QString path = fileUrl.adjusted(QUrl::StripTrailingSlash).toLocalFile(); if (!locationForPath(path).isNull()) { return CollectionLocation(); } ChangingDB changing(d); CoreDbAccess().db()->addAlbumRoot(AlbumRoot::Network, d->networkShareIdentifier(path), QLatin1String("/"), label); // Do not emit the locationAdded signal here, it is done in updateLocations() updateLocations(); return locationForPath(path); } CollectionManager::LocationCheckResult CollectionManager::checkLocation(const QUrl& fileUrl, QList assumeDeleted, QString* message, QString* iconName) { if (!fileUrl.isLocalFile()) { if (message) { *message = i18n("Sorry, digiKam does not support remote URLs as collections."); } if (iconName) { *iconName = QLatin1String("dialog-error"); } return LocationNotAllowed; } QString path = fileUrl.adjusted(QUrl::StripTrailingSlash).toLocalFile(); QDir dir(path); if (!dir.isReadable()) { if (message) { *message = i18n("The selected folder does not exist or is not readable"); } if (iconName) { *iconName = QLatin1String("dialog-error"); } return LocationNotAllowed; } if (d->checkIfExists(path, assumeDeleted)) { if (message) { *message = i18n("There is already a collection containing the folder \"%1\"", QDir::toNativeSeparators(path)); } if (iconName) { *iconName = QLatin1String("dialog-error"); } return LocationNotAllowed; } QList volumes = d->listVolumes(); SolidVolumeInfo volume = d->findVolumeForUrl(fileUrl, volumes); if (!volume.isNull()) { if (!volume.uuid.isEmpty()) { if (volume.isRemovable) { if (message) { *message = i18n("The storage media can be uniquely identified."); } if (iconName) { *iconName = QLatin1String("drive-removable-media"); } } else { if (message) { *message = i18n("The collection is located on your harddisk"); } if (iconName) { *iconName = QLatin1String("drive-harddisk"); } } return LocationAllRight; } else if (!volume.label.isEmpty() && (volume.isOpticalDisc || volume.isRemovable)) { if (volume.isOpticalDisc) { bool hasOtherLocation = false; foreach(AlbumRootLocation* const otherLocation, d->locations) { QUrl otherUrl(otherLocation->identifier); if (otherUrl.scheme() == QLatin1String("volumeid") && QUrlQuery(otherUrl).queryItemValue(QLatin1String("label")) == volume.label) { hasOtherLocation = true; break; } } if (iconName) { *iconName = QLatin1String("media-optical"); } if (hasOtherLocation) { if (message) *message = i18n("This is a CD/DVD, which is identified by the label " "that you can set in your CD burning application. " "There is already another entry with the same label. " "The two will be distinguished by the files in the top directory, " "so please do not append files to the CD, or it will not be recognized. " "In the future, please set a unique label on your CDs and DVDs " "if you intend to use them with digiKam."); return LocationHasProblems; } else { if (message) *message = i18n("This is a CD/DVD. It will be identified by the label (\"%1\")" "that you have set in your CD burning application. " "If you create further CDs for use with digikam in the future, " "please remember to give them a unique label as well.", volume.label); return LocationAllRight; } } else { // Which situation? HasProblems or AllRight? if (message) *message = i18n("This is a removable storage medium that will be identified by its label (\"%1\")", volume.label); if (iconName) { *iconName = QLatin1String("drive-removable-media"); } return LocationAllRight; } } else { if (message) *message = i18n("This entry will only be identified by the path where it is found on your system (\"%1\"). " "No more specific means of identification (UUID, label) is available.", QDir::toNativeSeparators(volume.path)); if (iconName) { *iconName = QLatin1String("drive-removale-media"); } return LocationHasProblems; } } else { if (message) *message = i18n("It is not possible on your system to identify the storage medium of this path. " "It will be added using the file path as the only identifier. " "This will work well for your local hard disk."); if (iconName) { *iconName = QLatin1String("folder-important"); } return LocationHasProblems; } } CollectionManager::LocationCheckResult CollectionManager::checkNetworkLocation(const QUrl& fileUrl, QList assumeDeleted, QString* message, QString* iconName) { if (!fileUrl.isLocalFile()) { if (message) { if (fileUrl.scheme() == QLatin1String("smb")) *message = i18n("You need to locally mount your Samba share. " "Sorry, digiKam does currently not support smb:// URLs. "); else *message = i18n("Your network storage must be set up to be accessible " "as files and folders through the operating system. " "DigiKam does not support remote URLs."); } if (iconName) { *iconName = QLatin1String("dialog-error"); } return LocationNotAllowed; } QString path = fileUrl.adjusted(QUrl::StripTrailingSlash).toLocalFile(); QDir dir(path); if (!dir.isReadable()) { if (message) { *message = i18n("The selected folder does not exist or is not readable"); } if (iconName) { *iconName = QLatin1String("dialog-error"); } return LocationNotAllowed; } if (d->checkIfExists(path, assumeDeleted)) { if (message) { *message = i18n("There is already a collection for a network share with the same path."); } if (iconName) { *iconName = QLatin1String("dialog-error"); } return LocationNotAllowed; } if (message) *message = i18n("The network share will be identified by the path you selected. " "If the path is empty, the share will be considered unavailable."); if (iconName) { *iconName = QLatin1String("network-wired-activated"); } return LocationAllRight; } void CollectionManager::removeLocation(const CollectionLocation& location) { { CoreDbAccess access; AlbumRootLocation* const albumLoc = d->locations.value(location.id()); if (!albumLoc) { return; } // Ensure that all albums are set to orphan and no images will be permanently deleted, // as would do only calling deleteAlbumRoot by a Trigger QList albumIds = access.db()->getAlbumsOnAlbumRoot(albumLoc->id()); ChangingDB changing(d); CollectionScanner scanner; CoreDbTransaction transaction(&access); scanner.safelyRemoveAlbums(albumIds); access.db()->deleteAlbumRoot(albumLoc->id()); } // Do not emit the locationRemoved signal here, it is done in updateLocations() updateLocations(); } QList CollectionManager::checkHardWiredLocations() { QList disappearedLocations; QList volumes = d->listVolumes(); CoreDbAccess access; foreach(AlbumRootLocation* const location, d->locations) { // Hardwired and unavailable? if (location->type() == CollectionLocation::TypeVolumeHardWired && location->status() == CollectionLocation::LocationUnavailable) { disappearedLocations << *location; } } return disappearedLocations; } void CollectionManager::migrationCandidates(const CollectionLocation& location, QString* const description, QStringList* const candidateIdentifiers, QStringList* const candidateDescriptions) { description->clear(); candidateIdentifiers->clear(); candidateDescriptions->clear(); QList volumes = d->listVolumes(); CoreDbAccess access; AlbumRootLocation* const albumLoc = d->locations.value(location.id()); if (!albumLoc) { return; } *description = d->technicalDescription(albumLoc); // Find possible new volumes where the specific path is found. foreach(const SolidVolumeInfo& info, volumes) { if (info.isMounted && !info.path.isEmpty()) { QDir dir(info.path + albumLoc->specificPath); if (dir.exists()) { *candidateIdentifiers << d->volumeIdentifier(info); *candidateDescriptions << dir.absolutePath(); } } } } void CollectionManager::migrateToVolume(const CollectionLocation& location, const QString& identifier) { CoreDbAccess access; AlbumRootLocation* const albumLoc = d->locations.value(location.id()); if (!albumLoc) { return; } // update db ChangingDB db(d); access.db()->migrateAlbumRoot(albumLoc->id(), identifier); albumLoc->identifier = identifier; updateLocations(); } void CollectionManager::setLabel(const CollectionLocation& location, const QString& label) { CoreDbAccess access; AlbumRootLocation* const albumLoc = d->locations.value(location.id()); if (!albumLoc) { return; } // update db ChangingDB db(d); access.db()->setAlbumRootLabel(albumLoc->id(), label); // update local structure albumLoc->setLabel(label); emit locationPropertiesChanged(*albumLoc); } void CollectionManager::changeType(const CollectionLocation& location, int type) { CoreDbAccess access; AlbumRootLocation* const albumLoc = d->locations.value(location.id()); if (!albumLoc) { return; } // update db ChangingDB db(d); access.db()->changeAlbumRootType(albumLoc->id(), (AlbumRoot::Type)type); // update local structure albumLoc->setType((CollectionLocation::Type)type); emit locationPropertiesChanged(*albumLoc); } QList CollectionManager::allLocations() { CoreDbAccess access; QList list; foreach(AlbumRootLocation* const location, d->locations) { list << *location; } return list; } QList CollectionManager::allAvailableLocations() { CoreDbAccess access; QList list; foreach(AlbumRootLocation* const location, d->locations) { if (location->status() == CollectionLocation::LocationAvailable) { list << *location; } } return list; } QStringList CollectionManager::allAvailableAlbumRootPaths() { CoreDbAccess access; QStringList list; foreach(AlbumRootLocation* const location, d->locations) { if (location->status() == CollectionLocation::LocationAvailable) { list << location->albumRootPath(); } } return list; } CollectionLocation CollectionManager::locationForAlbumRootId(int id) { CoreDbAccess access; AlbumRootLocation* const location = d->locations.value(id); if (location) { return *location; } else { return CollectionLocation(); } } CollectionLocation CollectionManager::locationForAlbumRoot(const QUrl& fileUrl) { return locationForAlbumRootPath(fileUrl.adjusted(QUrl::StripTrailingSlash).toLocalFile()); } CollectionLocation CollectionManager::locationForAlbumRootPath(const QString& albumRootPath) { if (!QFileInfo::exists(albumRootPath)) { qCWarning(DIGIKAM_DATABASE_LOG) << "Album root path not exist" << albumRootPath; qCWarning(DIGIKAM_DATABASE_LOG) << "Drive or network connection broken?"; updateLocations(); } CoreDbAccess access; foreach(AlbumRootLocation* const location, d->locations) { if (location->albumRootPath() == albumRootPath) { return *location; } } return CollectionLocation(); } CollectionLocation CollectionManager::locationForUrl(const QUrl& fileUrl) { return locationForPath(fileUrl.adjusted(QUrl::StripTrailingSlash).toLocalFile()); } CollectionLocation CollectionManager::locationForPath(const QString& givenPath) { CoreDbAccess access; foreach(AlbumRootLocation* const location, d->locations) { QString rootPath = location->albumRootPath(); QString filePath = QDir::fromNativeSeparators(givenPath); if (!rootPath.isEmpty() && filePath.startsWith(rootPath)) { // see also bug #221155 for extra checks if (filePath == rootPath || filePath.startsWith(rootPath + QLatin1Char('/'))) { return *location; } } } return CollectionLocation(); } QString CollectionManager::albumRootPath(int id) { CoreDbAccess access; CollectionLocation* const location = d->locations.value(id); if (location && location->status() == CollectionLocation::LocationAvailable) { return location->albumRootPath(); } return QString(); } QString CollectionManager::albumRootLabel(int id) { CoreDbAccess access; CollectionLocation* const location = d->locations.value(id); if (location && location->status() == CollectionLocation::LocationAvailable) { return location->label(); } return QString(); } QUrl CollectionManager::albumRoot(const QUrl& fileUrl) { return QUrl::fromLocalFile(albumRootPath(fileUrl.adjusted(QUrl::StripTrailingSlash).toLocalFile())); } QString CollectionManager::albumRootPath(const QUrl& fileUrl) { return albumRootPath(fileUrl.adjusted(QUrl::StripTrailingSlash).toLocalFile()); } QString CollectionManager::albumRootPath(const QString& givenPath) { CoreDbAccess access; foreach(AlbumRootLocation* const location, d->locations) { QString rootPath = location->albumRootPath(); QString filePath = QDir::fromNativeSeparators(givenPath); if (!rootPath.isEmpty() && filePath.startsWith(rootPath)) { // see also bug #221155 for extra checks if (filePath == rootPath || filePath.startsWith(rootPath + QLatin1Char('/'))) { return location->albumRootPath(); } } } return QString(); } bool CollectionManager::isAlbumRoot(const QUrl& fileUrl) { return isAlbumRoot(fileUrl.adjusted(QUrl::StripTrailingSlash).toLocalFile()); } bool CollectionManager::isAlbumRoot(const QString& filePath) { CoreDbAccess access; foreach(AlbumRootLocation* const location, d->locations) { if (filePath == location->albumRootPath()) { return true; } } return false; } QString CollectionManager::album(const QUrl& fileUrl) { return album(fileUrl.adjusted(QUrl::StripTrailingSlash).toLocalFile()); } QString CollectionManager::album(const QString& filePath) { CoreDbAccess access; foreach(AlbumRootLocation* const location, d->locations) { QString absolutePath = location->albumRootPath(); if (absolutePath.isEmpty()) { continue; } QString firstPart = filePath.left(absolutePath.length()); if (firstPart == absolutePath) { if (filePath == absolutePath || (filePath.length() == absolutePath.length() + 1 && filePath.right(1) == QLatin1String("/"))) { return QLatin1String("/"); } else { QString album = filePath.mid(absolutePath.length()); if (album.endsWith(QLatin1Char('/'))) { album.chop(1); } return album; } } } return QString(); } QString CollectionManager::album(const CollectionLocation& location, const QUrl& fileUrl) { return album(location, fileUrl.adjusted(QUrl::StripTrailingSlash).toLocalFile()); } QString CollectionManager::album(const CollectionLocation& location, const QString& filePath) { if (location.isNull()) { return QString(); } QString absolutePath = location.albumRootPath(); if (filePath == absolutePath) { return QLatin1String("/"); } else { QString album = filePath.mid(absolutePath.length()); if (album.endsWith(QLatin1Char('/'))) { album.chop(1); } return album; } } QUrl CollectionManager::oneAlbumRoot() { return QUrl::fromLocalFile(oneAlbumRootPath()); } QString CollectionManager::oneAlbumRootPath() { CoreDbAccess access; foreach(AlbumRootLocation* const location, d->locations) { if (location->status() == CollectionLocation::LocationAvailable) { return location->albumRootPath(); } } return QString(); } void CollectionManager::deviceAdded(const QString& udi) { if (!d->watchEnabled) { return; } Solid::Device device(udi); if (device.is()) { updateLocations(); } } void CollectionManager::deviceRemoved(const QString& udi) { if (!d->watchEnabled) { return; } // we can't access the Solid::Device to check because it is removed CoreDbAccess access; if (!d->udisToWatch.contains(udi)) { return; } updateLocations(); } void CollectionManager::accessibilityChanged(bool accessible, const QString& udi) { Q_UNUSED(accessible); Q_UNUSED(udi); updateLocations(); } void CollectionManager::updateLocations() { // get information from Solid QList volumes; { // Absolutely ensure that the db mutex is not held when emitting the blocking queued signal! Deadlock! CoreDbAccessUnlock unlock; volumes = d->listVolumes(); } { CoreDbAccess access; // read information from database QList infos = access.db()->getAlbumRoots(); // synchronize map with database QMap locs = d->locations; d->locations.clear(); foreach(const AlbumRootInfo& info, infos) { if (locs.contains(info.id)) { d->locations[info.id] = locs.value(info.id); locs.remove(info.id); } else { d->locations[info.id] = new AlbumRootLocation(info); } } // delete old locations foreach(AlbumRootLocation* const location, locs) { CollectionLocation::Status oldStatus = location->status(); location->setStatus(CollectionLocation::LocationDeleted); emit locationStatusChanged(*location, oldStatus); delete location; } // update status with current access state, store old status in list QList oldStatus; foreach(AlbumRootLocation* const location, d->locations) { oldStatus << location->status(); bool available = false; QString absolutePath; if (location->type() == CollectionLocation::TypeNetwork) { foreach(const QString& path, d->networkShareMountPathsFromIdentifier(location)) { QDir dir(path); available = dir.isReadable() && dir.entryList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot).count() > 0; absolutePath = path; if (available) { break; } } } else { SolidVolumeInfo info = d->findVolumeForLocation(location, volumes); if (!info.isNull()) { available = info.isMounted; QString volumePath = info.path; // volume.path has a trailing slash (and this is good) // but specific path has a leading slash, so remove it volumePath.chop(1); // volumePath is the mount point of the volume; // specific path is the path on the file system of the volume. absolutePath = volumePath + location->specificPath; } else { QString path = d->pathFromIdentifier(location); if (!path.isNull()) { available = true; // Here we have the absolute path as definition of the volume. // specificPath is "/" as per convention, but ignored, // absolute path shall not have a trailing slash. absolutePath = path; } } } // set values in location // Don't touch location->status, do not interfere with "hidden" setting location->available = available; location->setAbsolutePath(absolutePath); qCDebug(DIGIKAM_DATABASE_LOG) << "location for " << absolutePath << " is available " << available; // set the status depending on "hidden" and "available" location->setStatusFromFlags(); } // emit status changes (and new locations) int i = 0; foreach(AlbumRootLocation* const location, d->locations) { if (oldStatus.at(i) != location->status()) { emit locationStatusChanged(*location, oldStatus.at(i)); } ++i; } } } void CollectionManager::clear_locked() { // Internal method: Called with mutex locked // Cave: Difficult recursions with CoreDbAccess constructor and setParameters foreach(AlbumRootLocation* const location, d->locations) { CollectionLocation::Status oldStatus = location->status(); location->setStatus(CollectionLocation::LocationDeleted); emit locationStatusChanged(*location, oldStatus); delete location; } d->locations.clear(); } void CollectionManager::slotAlbumRootChange(const AlbumRootChangeset& changeset) { if (d->changingDB) { return; } switch (changeset.operation()) { case AlbumRootChangeset::Added: case AlbumRootChangeset::Deleted: updateLocations(); break; case AlbumRootChangeset::PropertiesChanged: // label has changed { CollectionLocation toBeEmitted; { CoreDbAccess access; AlbumRootLocation* const location = d->locations.value(changeset.albumRootId()); if (location) { QList infos = access.db()->getAlbumRoots(); foreach(const AlbumRootInfo& info, infos) { if (info.id == location->id()) { location->setLabel(info.label); toBeEmitted = *location; break; } } } } if (!toBeEmitted.isNull()) { emit locationPropertiesChanged(toBeEmitted); } } break; case AlbumRootChangeset::Unknown: break; } } } // namespace Digikam #include "moc_collectionmanager.cpp" diff --git a/core/libs/dtrash/dtrash.cpp b/core/libs/dtrash/dtrash.cpp index 6b66a31655..cf2e3f5a29 100644 --- a/core/libs/dtrash/dtrash.cpp +++ b/core/libs/dtrash/dtrash.cpp @@ -1,240 +1,240 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2015-07-27 * Description : Special digiKam trash implementation * * Copyright (C) 2015 by Mohamed_Anwer * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "dtrash.h" // Qt includes #include #include #include #include #include #include #include #include // Local includes #include "digikam_debug.h" #include "collectionmanager.h" #include "albummanager.h" namespace Digikam { const QString DTrash::TRASH_FOLDER = QLatin1String(".dtrash"); const QString DTrash::FILES_FOLDER = QLatin1String("files"); const QString DTrash::INFO_FOLDER = QLatin1String("info"); const QString DTrash::INFO_FILE_EXTENSION = QLatin1String(".dtrashinfo"); const QString DTrash::PATH_JSON_KEY = QLatin1String("path"); const QString DTrash::DELETIONTIMESTAMP_JSON_KEY = QLatin1String("deletiontimestamp"); const QString DTrash::IMAGEID_JSON_KEY = QLatin1String("imageid"); // ---------------------------------------------- DTrash::DTrash() { } bool DTrash::deleteImage(const QString& imageToDelete) { QString collection = CollectionManager::instance()->albumRootPath(imageToDelete); qCDebug(DIGIKAM_IOJOB_LOG) << "DTrash: Image album root path:" << collection; if (!prepareCollectionTrash(collection)) { return false; } QFileInfo imageFileInfo(imageToDelete); QFile imageFile(imageToDelete); QString fileName = imageFileInfo.fileName(); // Get the album path, i.e. collection + album. For this, // get the n leftmost characters where n is the complete path without the size of the filename QString completePath = imageFileInfo.path(); qlonglong imageId = -1; // Get the album and with this the image id of the image to trash. PAlbum* pAlbum = AlbumManager::instance()->findPAlbum(QUrl::fromLocalFile(completePath)); if (pAlbum) { imageId = AlbumManager::instance()->getItemFromAlbum(pAlbum, fileName); } QString baseNameForMovingIntoTrash = createJsonRecordForFile(collection, imageToDelete, imageId); QString destinationInTrash = collection + QLatin1Char('/') + TRASH_FOLDER + QLatin1Char('/') + FILES_FOLDER + QLatin1Char('/') + - baseNameForMovingIntoTrash + QLatin1String(".") + + baseNameForMovingIntoTrash + QLatin1Char('.') + imageFileInfo.completeSuffix(); if (!imageFile.rename(destinationInTrash)) { return false; } return true; } bool DTrash::deleteDirRecursivley(const QString& dirToDelete) { QDir srcDir(dirToDelete); foreach (const QFileInfo& fileInfo, srcDir.entryInfoList(QDir::Files)) { if (!deleteImage(fileInfo.filePath())) { return false; } } foreach (const QFileInfo& fileInfo, srcDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) { deleteDirRecursivley(fileInfo.filePath()); } return srcDir.removeRecursively(); } void DTrash::extractJsonForItem(const QString& collPath, const QString& baseName, DTrashItemInfo& itemInfo) { QString jsonFilePath = collPath + QLatin1Char('/') + TRASH_FOLDER + QLatin1Char('/') + INFO_FOLDER + QLatin1Char('/') + baseName + INFO_FILE_EXTENSION; QFile jsonFile(jsonFilePath); if (!jsonFile.open(QIODevice::ReadOnly | QIODevice::Text)) return; QJsonDocument doc = QJsonDocument::fromJson(jsonFile.readAll()); jsonFile.close(); QJsonObject fileInfoObj = doc.object(); itemInfo.jsonFilePath = jsonFilePath; itemInfo.collectionPath = fileInfoObj.value(PATH_JSON_KEY).toString(); itemInfo.collectionRelativePath = fileInfoObj.value(PATH_JSON_KEY).toString() .replace(collPath, QLatin1String("")); itemInfo.deletionTimestamp = QDateTime::fromString( fileInfoObj.value(DELETIONTIMESTAMP_JSON_KEY).toString()); QJsonValue imageIdValue = fileInfoObj.value(IMAGEID_JSON_KEY); if (!imageIdValue.isUndefined()) { itemInfo.imageId = imageIdValue.toString().toLongLong(); } else { itemInfo.imageId = -1; } } bool DTrash::prepareCollectionTrash(const QString& collectionPath) { QString trashFolder = collectionPath + QLatin1Char('/') + TRASH_FOLDER; QDir trashDir(trashFolder); if (!trashDir.exists()) { bool isCreated = true; isCreated &= trashDir.mkpath(trashFolder); isCreated &= trashDir.mkpath(trashFolder + QLatin1Char('/') + FILES_FOLDER); isCreated &= trashDir.mkpath(trashFolder + QLatin1Char('/') + INFO_FOLDER); if (!isCreated) { qCDebug(DIGIKAM_IOJOB_LOG) << "DTrash: could not create trash folder for collection"; return false; } } qCDebug(DIGIKAM_IOJOB_LOG) << "Trash folder for collection: " << trashFolder; return true; } QString DTrash::createJsonRecordForFile(const QString& collectionPath, const QString& imagePath, qlonglong imageId) { QJsonObject jsonObjForImg; QJsonValue pathJsonVal(imagePath); QJsonValue timestampJsonVal(QDateTime::currentDateTime().toString()); QJsonValue imageIdJsonVal(QString::number(imageId)); jsonObjForImg.insert(PATH_JSON_KEY, pathJsonVal); jsonObjForImg.insert(DELETIONTIMESTAMP_JSON_KEY, timestampJsonVal); jsonObjForImg.insert(IMAGEID_JSON_KEY, imageIdJsonVal); QJsonDocument jsonDocForImg(jsonObjForImg); QFileInfo imgFileInfo(imagePath); QString jsonFileName = getAvialableJsonFilePathInTrash(collectionPath, imgFileInfo.baseName()); QFile jsonFileForImg(jsonFileName); QFileInfo jsonFileInfo(jsonFileName); if (!jsonFileForImg.open(QFile::WriteOnly)) return jsonFileInfo.baseName(); jsonFileForImg.write(jsonDocForImg.toJson()); jsonFileForImg.close(); return jsonFileInfo.baseName(); } QString DTrash::getAvialableJsonFilePathInTrash(const QString& collectionPath, const QString& baseName, int version) { QString pathToCreateJsonFile = collectionPath + QLatin1Char('/') + TRASH_FOLDER + QLatin1Char('/') + INFO_FOLDER + QLatin1Char('/') + baseName + QLatin1Char('-') + QUuid::createUuid().toString().mid(1, 8) + (version ? QString::number(version) : QLatin1String("")) + INFO_FILE_EXTENSION; QFileInfo jsonFileInfo(pathToCreateJsonFile); if (jsonFileInfo.exists()) { return getAvialableJsonFilePathInTrash(collectionPath, baseName, ++version); } else { return pathToCreateJsonFile; } } } // namespace Digikam diff --git a/core/showfoto/thumbbar/showfotothumbnailmodel.cpp b/core/showfoto/thumbbar/showfotothumbnailmodel.cpp index 50bd528b9d..004f4e5acd 100644 --- a/core/showfoto/thumbbar/showfotothumbnailmodel.cpp +++ b/core/showfoto/thumbbar/showfotothumbnailmodel.cpp @@ -1,371 +1,371 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2013-07-22 * Description : Qt item model for Showfoto thumbnails entries * * Copyright (C) 2013 by Mohamed_Anwer * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "showfotothumbnailmodel.h" // Local includes #include "drawdecoder.h" #include "digikam_debug.h" #include "dmetadata.h" #include "imagescanner.h" #include "thumbnailsize.h" #include "thumbnailloadthread.h" #include "loadingdescription.h" using namespace Digikam; namespace ShowFoto { class ShowfotoThumbnailModel::Private { public: explicit Private() : thread(0), preloadThread(0), thumbSize(0), lastGlobalThumbSize(0), preloadThumbSize(0), emitDataChanged(true) { maxThumbSize = ThumbnailSize::Huge; } int preloadThumbnailSize() const { if (preloadThumbSize.size()) { return preloadThumbSize.size(); } return thumbSize.size(); } public: ThumbnailLoadThread* thread; ThumbnailLoadThread* preloadThread; ThumbnailSize thumbSize; ThumbnailSize lastGlobalThumbSize; ThumbnailSize preloadThumbSize; QRect detailRect; int maxThumbSize; bool emitDataChanged; }; ShowfotoThumbnailModel::ShowfotoThumbnailModel(QObject* const parent) : ShowfotoImageModel(parent), d(new Private) { connect(this, &ShowfotoThumbnailModel::signalThumbInfo, this, &ShowfotoThumbnailModel::slotThumbInfoLoaded); } ShowfotoThumbnailModel::~ShowfotoThumbnailModel() { delete d->preloadThread; delete d; } void ShowfotoThumbnailModel::setThumbnailLoadThread(ThumbnailLoadThread* thread) { d->thread = thread; connect(d->thread, &ThumbnailLoadThread::signalThumbnailLoaded, this, &ShowfotoThumbnailModel::slotThumbnailLoaded); } ThumbnailLoadThread* ShowfotoThumbnailModel::thumbnailLoadThread() const { return d->thread; } ThumbnailSize ShowfotoThumbnailModel::thumbnailSize() const { return d->thumbSize; } void ShowfotoThumbnailModel::setThumbnailSize(const ThumbnailSize& size) { d->lastGlobalThumbSize = size; d->thumbSize = size; } void ShowfotoThumbnailModel::setPreloadThumbnailSize(const ThumbnailSize& size) { d->preloadThumbSize = size; } void ShowfotoThumbnailModel::setEmitDataChanged(bool emitSignal) { d->emitDataChanged = emitSignal; } void ShowfotoThumbnailModel::showfotoItemInfosCleared() { if (d->preloadThread) { d->preloadThread->stopAllTasks(); } } QVariant ShowfotoThumbnailModel::data(const QModelIndex& index, int role) const { if (role == ThumbnailRole && d->thread && index.isValid()) { QImage thumbnailImage; QPixmap pixmap; ShowfotoItemInfo info = showfotoItemInfo(index); QString url = info.url.toDisplayString(); - QString path = info.folder + QLatin1String("/") + info.name; + QString path = info.folder + QLatin1Char('/') + info.name; if (info.isNull() || url.isEmpty()) { return QVariant(QVariant::Pixmap); } if(pixmapForItem(path,pixmap)) { return pixmap; } //if pixmapForItem Failed if(getThumbnail(info,thumbnailImage)) { thumbnailImage = thumbnailImage.scaled(d->thumbSize.size(),d->thumbSize.size(),Qt::KeepAspectRatio); emit signalThumbInfo(info,thumbnailImage); return thumbnailImage; } return QVariant(QVariant::Pixmap); } return ShowfotoImageModel::data(index, role); } bool ShowfotoThumbnailModel::setData(const QModelIndex& index, const QVariant& value, int role) { if (role == ThumbnailRole) { switch (value.type()) { case QVariant::Invalid: d->thumbSize = d->lastGlobalThumbSize; d->detailRect = QRect(); break; case QVariant::Int: if (value.isNull()) { d->thumbSize = ThumbnailSize(d->lastGlobalThumbSize); } else { d->thumbSize = ThumbnailSize(value.toInt()); } break; case QVariant::Rect: if (value.isNull()) { d->detailRect = QRect(); } else { d->detailRect = value.toRect(); } break; default: break; } } return ShowfotoImageModel::setData(index, value, role); } void ShowfotoThumbnailModel::slotThumbnailLoaded(const LoadingDescription& loadingDescription, const QPixmap& thumb) { if (thumb.isNull()) { return; } // In case of multiple occurrence, we currently do not know which thumbnail is this. Signal change on all. foreach(const QModelIndex& index, indexesForUrl(QUrl::fromLocalFile(loadingDescription.filePath))) { if (thumb.isNull()) { emit thumbnailFailed(index, loadingDescription.previewParameters.size); } else { emit thumbnailAvailable(index, loadingDescription.previewParameters.size); if (d->emitDataChanged) { emit dataChanged(index, index); } } } } bool ShowfotoThumbnailModel::getThumbnail(const ShowfotoItemInfo& itemInfo, QImage& thumbnail) const { - QString path = itemInfo.folder + QLatin1String("/") + itemInfo.name; + QString path = itemInfo.folder + QLatin1Char('/') + itemInfo.name; // Try to get preview from Exif data (good quality). Can work with Raw files DMetadata metadata(path); metadata.getImagePreview(thumbnail); if (!thumbnail.isNull()) { return true; } // RAW files : try to extract embedded thumbnail using RawEngine DRawDecoder::loadRawPreview(thumbnail, path); if (!thumbnail.isNull()) { return true; } KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(QLatin1String("Camera Settings")); bool turnHighQualityThumbs = group.readEntry(QLatin1String("TurnHighQualityThumbs"), false); // Try to get thumbnail from Exif data (poor quality). if (!turnHighQualityThumbs) { thumbnail = metadata.getExifThumbnail(true); if (!thumbnail.isNull()) { return true; } } // THM files: try to get thumbnail from '.thm' files if we didn't manage to get // thumbnail from Exif. Any cameras provides *.thm files like JPEG files with RAW files. // Using this way is always speed up than ultimate loading using DImg. // Note: the thumbnail extracted with this method can be in poor quality. // 2006/27/01 - Gilles - Tested with my Minolta Dynax 5D USM camera. QFileInfo fi(path); - if (thumbnail.load(itemInfo.folder + QLatin1String("/") + fi.baseName() + QLatin1String(".thm"))) // Lowercase + if (thumbnail.load(itemInfo.folder + QLatin1Char('/') + fi.baseName() + QLatin1String(".thm"))) // Lowercase { if (!thumbnail.isNull()) { return true; } } - else if (thumbnail.load(itemInfo.folder + QLatin1String("/") + fi.baseName() + QLatin1String(".THM"))) // Uppercase + else if (thumbnail.load(itemInfo.folder + QLatin1Char('/') + fi.baseName() + QLatin1String(".THM"))) // Uppercase { if (!thumbnail.isNull()) { return true; } } // Finally, we trying to get thumbnail using DImg API (slow). // qCDebug(DIGIKAM_SHOWFOTO_LOG) << "Use DImg loader to get thumbnail from : " << path; // DImg dimgThumb(path); // if (!dimgThumb.isNull()) // { // thumbnail = dimgThumb.copyQImage(); // return true; // } return false; } bool ShowfotoThumbnailModel::pixmapForItem(QString url, QPixmap& pix) const { if (d->thumbSize.size() > d->maxThumbSize) { //TODO: Install a widget maximum size to prevent this situation bool hasPixmap = d->thread->find(ThumbnailIdentifier(url), pix, d->maxThumbSize); if (hasPixmap) { qCWarning(DIGIKAM_GENERAL_LOG) << "Thumbbar: Requested thumbnail size" << d->thumbSize.size() << "is larger than the maximum thumbnail size" << d->maxThumbSize << ". Returning a scaled-up image."; pix = pix.scaled(d->thumbSize.size(), d->thumbSize.size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); return true; } else { return false; } } else { return d->thread->find(ThumbnailIdentifier(url), pix, d->thumbSize.size()); } } void ShowfotoThumbnailModel::slotThumbInfoLoaded(const ShowfotoItemInfo& info, const QImage& thumbnailImage) { QImage thumbnail = thumbnailImage; if (thumbnail.isNull()) { thumbnail = QImage(); } foreach(const QModelIndex& index, indexesForUrl(info.url)) { if (thumbnail.isNull()) { emit thumbnailFailed(index, d->thumbSize.size()); } else { emit thumbnailAvailable(index, d->thumbSize.size()); if (d->emitDataChanged) { emit dataChanged(index, index); } } } } } // namespace ShowFoto diff --git a/core/tests/albummodel/albummodeltest.cpp b/core/tests/albummodel/albummodeltest.cpp index 7fe9cfa43f..ae82257b71 100644 --- a/core/tests/albummodel/albummodeltest.cpp +++ b/core/tests/albummodel/albummodeltest.cpp @@ -1,811 +1,811 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2009-12-11 * Description : test cases for the various album models * * Copyright (C) 2009 by Johannes Wienke * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "albummodeltest.h" // Qt includes #include #include #include #include // Local includes #include "albumfiltermodel.h" #include "albummanager.h" #include "albummodel.h" #include "applicationsettings.h" #include "albumthumbnailloader.h" #include "collectionlocation.h" #include "collectionmanager.h" #include "loadingcacheinterface.h" #include "scancontroller.h" #include "thumbnailloadthread.h" #include "../modeltest/modeltest.h" using namespace Digikam; const QString IMAGE_PATH(QFINDTESTDATA("data/")); QTEST_MAIN(AlbumModelTest) AlbumModelTest::AlbumModelTest() : albumCategory(QLatin1String("DummyCategory")), palbumRoot0(0), palbumRoot1(0), palbumRoot2(0), palbumChild0Root0(0), palbumChild1Root0(0), palbumChild2Root0(0), palbumChild0Root1(0), rootTag(0), talbumRoot0(0), talbumRoot1(0), talbumChild0Root0(0), talbumChild1Root0(0), talbumChild0Child1Root0(0), talbumChild0Root1(0), startModel(0) { } AlbumModelTest::~AlbumModelTest() { } /** * TODO: this test case needs to be removed, since it depends on database, * threading etc, therefore it is not predictable and very hard to fix */ void AlbumModelTest::initTestCase() { tempSuffix = QLatin1String("albummodeltest-") + QTime::currentTime().toString(); - dbPath = QDir::temp().absolutePath() + QLatin1String("/") + tempSuffix; + dbPath = QDir::temp().absolutePath() + QLatin1Char('/') + tempSuffix; if (QDir::temp().exists(tempSuffix)) { QString msg = QLatin1String("Error creating temp path") + dbPath; QVERIFY2(false, msg.toLatin1().constData()); } QDir::temp().mkdir(tempSuffix); qDebug() << "Using database path for test: " << dbPath; ApplicationSettings::instance()->setShowFolderTreeViewItemsCount(true); // use a testing database AlbumManager::instance(); // catch palbum counts for waiting connect(AlbumManager::instance(), SIGNAL(signalPAlbumsDirty(QMap)), this, SLOT(setLastPAlbumCountMap(QMap))); AlbumManager::checkDatabaseDirsAfterFirstRun(QDir::temp().absoluteFilePath( tempSuffix), QDir::temp().absoluteFilePath(tempSuffix)); DbEngineParameters params(QLatin1String("QSQLITE"), QDir::temp().absoluteFilePath(tempSuffix + QLatin1String("/digikam4.db")), QString(), QString(), -1, false, QString(), QString()); bool dbChangeGood = AlbumManager::instance()->setDatabase(params, false, QDir::temp().absoluteFilePath(tempSuffix)); QVERIFY2(dbChangeGood, "Could not set temp album db"); QList locs = CollectionManager::instance()->allAvailableLocations(); QVERIFY2(locs.size(), "Failed to auto-create one collection in setDatabase"); ScanController::instance()->completeCollectionScan(); AlbumManager::instance()->startScan(); AlbumList all = AlbumManager::instance()->allPAlbums(); qDebug() << "PAlbum registered : " << all.size(); foreach(Album* const a, all) { if (a) { qDebug() << " ==> Id : " << a->id() << " , is root : " << a->isRoot() << " , title : " << a->title(); } } QVERIFY2( all.size() == 3, "Failed to scan empty directory. We must have one root album, one album, and one trash."); } void AlbumModelTest::cleanupTestCase() { qDebug() << "Start AlbumModelTest::cleanupTestCase()"; ScanController::instance()->shutDown(); AlbumManager::instance()->cleanUp(); ThumbnailLoadThread::cleanUp(); AlbumThumbnailLoader::instance()->cleanUp(); LoadingCacheInterface::cleanUp(); QDir dir(dbPath); dir.removeRecursively(); qDebug() << "deleted test folder " << dbPath; } // Qt test doesn't use exceptions, so using assertion macros in methods called // from a test slot doesn't stop the test method and may result in inconsistent // data or segfaults. Therefore use macros for these functions. #define safeCreatePAlbum(parent, name, result) \ { \ QString error; \ result = AlbumManager::instance()->createPAlbum(parent, name, name, \ QDate::currentDate(), albumCategory, error); \ QVERIFY2(result, QString::fromUtf8("Error creating PAlbum for test: %1").arg(error).toLatin1().constData()); \ } #define safeCreateTAlbum(parent, name, result) \ { \ QString error; \ result = AlbumManager::instance()->createTAlbum(parent, name, QLatin1String(""), error); \ QVERIFY2(result, QString::fromUtf8("Error creating TAlbum for test: %1").arg(error).toLatin1().constData()); \ } void AlbumModelTest::init() { qDebug() << "Start AlbumModelTest::init()"; palbumCountMap.clear(); // create a model to check that model work is done correctly while scanning addedIds.clear(); startModel = new AlbumModel; startModel->setShowCount(true); connect(startModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(slotStartModelRowsInserted(QModelIndex,int,int))); connect(startModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(slotStartModelDataChanged(QModelIndex,QModelIndex))); qDebug() << "Created startModel" << startModel; // ensure that this model is empty in the beginning except for the root // album and the collection that include trash QCOMPARE(startModel->rowCount(), 1); QModelIndex rootIndex = startModel->index(0, 0); QCOMPARE(startModel->rowCount(rootIndex), 1); QModelIndex collectionIndex = startModel->index(0, 0, rootIndex); QCOMPARE(startModel->rowCount(collectionIndex), 1); // insert some test data // physical albums // create two of them by creating directories and scanning QDir dir(dbPath); dir.mkdir(QLatin1String("root0")); dir.mkdir(QLatin1String("root1")); ScanController::instance()->completeCollectionScan(); AlbumManager::instance()->refresh(); QCOMPARE(AlbumManager::instance()->allPAlbums().size(), 5); QString error; palbumRoot0 = AlbumManager::instance()->findPAlbum(QUrl::fromLocalFile(dbPath + QLatin1String("/root0"))); QVERIFY2(palbumRoot0, "Error having PAlbum root0 in AlbumManager"); palbumRoot1 = AlbumManager::instance()->findPAlbum(QUrl::fromLocalFile(dbPath + QLatin1String("/root1"))); QVERIFY2(palbumRoot1, "Error having PAlbum root1 in AlbumManager"); // Create some more through AlbumManager palbumRoot2 = AlbumManager::instance()->createPAlbum(dbPath, QLatin1String("root2"), QLatin1String("root album 2"), QDate::currentDate(), albumCategory, error); QVERIFY2(palbumRoot2, QString::fromUtf8("Error creating PAlbum for test: %1").arg(error).toLatin1().constData()); safeCreatePAlbum(palbumRoot0, QLatin1String("root0child0"), palbumChild0Root0); safeCreatePAlbum(palbumRoot0, QLatin1String("root0child1"), palbumChild1Root0); const QString sameName = QLatin1String("sameName Album"); safeCreatePAlbum(palbumRoot0, sameName, palbumChild2Root0); safeCreatePAlbum(palbumRoot1, sameName, palbumChild0Root1); qDebug() << "AlbumManager now knows these PAlbums:"; foreach(Album* const a, AlbumManager::instance()->allPAlbums()) { qDebug() << "\t" << a->title(); } // tags rootTag = AlbumManager::instance()->findTAlbum(0); QVERIFY(rootTag); safeCreateTAlbum(rootTag, QLatin1String("root0"), talbumRoot0); safeCreateTAlbum(rootTag, QLatin1String("root1"), talbumRoot1); safeCreateTAlbum(talbumRoot0, QLatin1String("child0 root 0"), talbumChild0Root0); safeCreateTAlbum(talbumRoot0, QLatin1String("child1 root 0"), talbumChild1Root0); safeCreateTAlbum(talbumChild1Root0, sameName, talbumChild0Child1Root0); safeCreateTAlbum(talbumRoot1, sameName, talbumChild0Root1); qDebug() << "created tags"; // add some images for having date albums QDir imageDir(IMAGE_PATH); imageDir.setNameFilters(QStringList() << QLatin1String("*.jpg")); QStringList imageFiles = imageDir.entryList(); qDebug() << "copying images " << imageFiles << " to " << palbumChild0Root0->fileUrl(); foreach(const QString& imageFile, imageFiles) { QString src = IMAGE_PATH + QLatin1Char('/') + imageFile; QString dst = palbumChild0Root0->fileUrl().toLocalFile() + QLatin1Char('/') + imageFile; bool copied = QFile::copy(src, dst); QVERIFY2(copied, "Test images must be copied"); } ScanController::instance()->completeCollectionScan(); if (AlbumManager::instance()->allDAlbums().count() <= 1) { ensureItemCounts(); } qDebug() << "date albums: " << AlbumManager::instance()->allDAlbums(); // root + 2 years + 2 and 3 months per year + (1997 as test year for date ordering with 12 months) = 21 QCOMPARE(AlbumManager::instance()->allDAlbums().size(), 21); // ensure that there is a root date album DAlbum* const rootFromAlbumManager = AlbumManager::instance()->findDAlbum(0); QVERIFY(rootFromAlbumManager); DAlbum* rootFromList = 0; foreach(Album* const album, AlbumManager::instance()->allDAlbums()) { DAlbum* const dAlbum = dynamic_cast (album); QVERIFY(dAlbum); if (dAlbum->isRoot()) { rootFromList = dAlbum; } } QVERIFY(rootFromList); QVERIFY(rootFromList == rootFromAlbumManager); } void AlbumModelTest::testStartAlbumModel() { qDebug() << "Start AlbumModelTest::testStartAlbumModel()"; // verify that the start album model got all these changes // one root QCOMPARE(startModel->rowCount(), 1); // one collection QModelIndex rootIndex = startModel->index(0, 0); QCOMPARE(startModel->rowCount(rootIndex), 1); // two albums in the collection QModelIndex collectionIndex = startModel->index(0, 0, rootIndex); QCOMPARE(startModel->rowCount(collectionIndex), 3); // this is should be enough for now // We must have received an added notation for everything except album root // and collection QCOMPARE(addedIds.size(), 7); } void AlbumModelTest::ensureItemCounts() { // trigger listing job QEventLoop dAlbumLoop; connect(AlbumManager::instance(), SIGNAL(signalAllDAlbumsLoaded()), &dAlbumLoop, SLOT(quit())); AlbumManager::instance()->prepareItemCounts(); qDebug() << "Waiting for AlbumManager and the IOSlave to create DAlbums..."; dAlbumLoop.exec(); qDebug() << "DAlbums were created"; while (palbumCountMap.size() < 8) { QEventLoop pAlbumLoop; connect(AlbumManager::instance(), SIGNAL(signalPAlbumsDirty(QMap)), &pAlbumLoop, SLOT(quit())); qDebug() << "Waiting for first PAlbum count map"; pAlbumLoop.exec(); qDebug() << "Got new PAlbum count map"; } } void AlbumModelTest::slotStartModelRowsInserted(const QModelIndex& parent, int start, int end) { qDebug() << "called, parent:" << parent << ", start:" << start << ", end:" << end; for (int row = start; row <= end; ++row) { QModelIndex child = startModel->index(row, 0, parent); QVERIFY(child.isValid()); Album* album = startModel->albumForIndex(child); const int id = child.data(AbstractAlbumModel::AlbumIdRole).toInt(); QVERIFY(album); qDebug() << "added album with id" << id << "and name" << album->title(); addedIds << id; } } void AlbumModelTest::slotStartModelDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) { for (int row = topLeft.row(); row <= bottomRight.row(); ++row) { QModelIndex index = startModel->index(row, topLeft.column(), topLeft.parent()); if (!index.isValid()) { qDebug() << "Illegal index received"; continue; } int albumId = index.data(AbstractAlbumModel::AlbumIdRole).toInt(); if (!addedIds.contains(albumId)) { QString message = QLatin1String("Album id ") + QString::number(albumId) + QLatin1String(" was changed before adding signal was received"); QFAIL(message.toLatin1().constData()); qDebug() << message; } } } void AlbumModelTest::deletePAlbum(PAlbum* album) { QDir dir(album->folderPath()); dir.removeRecursively(); } void AlbumModelTest::setLastPAlbumCountMap(const QMap &map) { qDebug() << "Receiving new count map "<< map; palbumCountMap = map; } void AlbumModelTest::cleanup() { if (startModel) { disconnect(startModel); } delete startModel; addedIds.clear(); // remove all test data AlbumManager::instance()->refresh(); // remove all palbums' directories deletePAlbum(palbumRoot0); deletePAlbum(palbumRoot1); deletePAlbum(palbumRoot2); // take over changes to database ScanController::instance()->completeCollectionScan(); // reread from database AlbumManager::instance()->refresh(); // root + one collection QCOMPARE(AlbumManager::instance()->allPAlbums().size(), 2); // remove all tags QString error; bool removed = AlbumManager::instance()->deleteTAlbum(talbumRoot0, error, false); QVERIFY2(removed, QString::fromUtf8("Error removing a tag: %1").arg(error).toLatin1().constData()); removed = AlbumManager::instance()->deleteTAlbum(talbumRoot1, error, false); QVERIFY2(removed, QString::fromUtf8("Error removing a tag: %1").arg(error).toLatin1().constData()); QCOMPARE(AlbumManager::instance()->allTAlbums().size(), 1); } void AlbumModelTest::testPAlbumModel() { qDebug() << "Start AlbumModelTest::testPAlbumModel()"; AlbumModel* albumModel = new AlbumModel(); ModelTest* test = new ModelTest(albumModel, 0); delete test; delete albumModel; albumModel = new AlbumModel(AbstractAlbumModel::IgnoreRootAlbum); test = new ModelTest(albumModel, 0); delete test; delete albumModel; } void AlbumModelTest::testDisablePAlbumCount() { qDebug() << "Start AlbumModelTest::testDisablePAlbumCount()"; AlbumModel albumModel; albumModel.setCountMap(palbumCountMap); albumModel.setShowCount(true); QRegExp countRegEx(QLatin1String(".+ \\(\\d+\\)")); countRegEx.setMinimal(true); QVERIFY(countRegEx.exactMatch(QLatin1String("test (10)"))); QVERIFY(countRegEx.exactMatch(QLatin1String("te st (10)"))); QVERIFY(countRegEx.exactMatch(QLatin1String("te st (0)"))); QVERIFY(!countRegEx.exactMatch(QLatin1String("te st ()"))); QVERIFY(!countRegEx.exactMatch(QLatin1String("te st"))); QVERIFY(!countRegEx.exactMatch(QLatin1String("te st (10) bla"))); // ensure that all albums except the root album have a count attached QModelIndex rootIndex = albumModel.index(0, 0, QModelIndex()); QString rootTitle = albumModel.data(rootIndex, Qt::DisplayRole).toString(); QVERIFY(!countRegEx.exactMatch(rootTitle)); for (int collectionRow = 0; collectionRow < albumModel.rowCount(rootIndex); ++collectionRow) { QModelIndex collectionIndex = albumModel.index(collectionRow, 0, rootIndex); QString collectionTitle = albumModel.data(collectionIndex, Qt::DisplayRole).toString(); QVERIFY2(countRegEx.exactMatch(collectionTitle), QString::fromUtf8("%1 matching error").arg(collectionTitle).toLatin1().constData()); for (int albumRow = 0; albumRow < albumModel.rowCount(collectionIndex); ++albumRow) { QModelIndex albumIndex = albumModel.index(albumRow, 0, collectionIndex); QString albumTitle = albumModel.data(albumIndex, Qt::DisplayRole).toString(); QVERIFY2(countRegEx.exactMatch(albumTitle), QString::fromUtf8("%1 matching error").arg(albumTitle).toLatin1().constData()); } } // now disable showing the count albumModel.setShowCount(false); // ensure that no album has a count attached rootTitle = albumModel.data(rootIndex, Qt::DisplayRole).toString(); QVERIFY(!countRegEx.exactMatch(rootTitle)); for (int collectionRow = 0; collectionRow < albumModel.rowCount(rootIndex); ++collectionRow) { QModelIndex collectionIndex = albumModel.index(collectionRow, 0, rootIndex); QString collectionTitle = albumModel.data(collectionIndex, Qt::DisplayRole).toString(); QVERIFY2(!countRegEx.exactMatch(collectionTitle), QString::fromUtf8("%1 matching error").arg(collectionTitle).toLatin1().constData()); for (int albumRow = 0; albumRow < albumModel.rowCount(collectionIndex); ++albumRow) { QModelIndex albumIndex = albumModel.index(albumRow, 0, collectionIndex); QString albumTitle = albumModel.data(albumIndex, Qt::DisplayRole).toString(); QVERIFY2(!countRegEx.exactMatch(albumTitle), QString::fromUtf8("%1 matching error").arg(albumTitle).toLatin1().constData()); } } } void AlbumModelTest::testDAlbumModel() { qDebug() << "Start AlbumModelTest::testDAlbumModel()"; DateAlbumModel* const albumModel = new DateAlbumModel(); ensureItemCounts(); ModelTest* const test = new ModelTest(albumModel, 0); delete test; delete albumModel; } void AlbumModelTest::testDAlbumContainsAlbums() { qDebug() << "Start AlbumModelTest::testDAlbumContainsAlbums()"; DateAlbumModel* const albumModel = new DateAlbumModel(); ensureItemCounts(); QVERIFY(albumModel->rootAlbum()); foreach(Album* const album, AlbumManager::instance()->allDAlbums()) { DAlbum* const dAlbum = dynamic_cast (album); QVERIFY(dAlbum); qDebug() << "checking album for date " << dAlbum->date() << ", range = " << dAlbum->range(); QModelIndex index = albumModel->indexForAlbum(dAlbum); if (!dAlbum->isRoot()) { QVERIFY(index.isValid()); } if (dAlbum->isRoot()) { // root album QVERIFY(dAlbum->isRoot()); QCOMPARE(albumModel->rowCount(index), 3); QCOMPARE(index, albumModel->rootAlbumIndex()); } else if (dAlbum->range() == DAlbum::Year && dAlbum->date().year() == 2007) { QCOMPARE(albumModel->rowCount(index), 2); } else if (dAlbum->range() == DAlbum::Year && dAlbum->date().year() == 2009) { QCOMPARE(albumModel->rowCount(index), 3); } else if (dAlbum->range() == DAlbum::Month && dAlbum->date().year() == 2007 && dAlbum->date().month() == 3) { QCOMPARE(albumModel->rowCount(index), 0); } else if (dAlbum->range() == DAlbum::Month && dAlbum->date().year() == 2007 && dAlbum->date().month() == 4) { QCOMPARE(albumModel->rowCount(index), 0); } else if (dAlbum->range() == DAlbum::Month && dAlbum->date().year() == 2009 && dAlbum->date().month() == 3) { QCOMPARE(albumModel->rowCount(index), 0); } else if (dAlbum->range() == DAlbum::Month && dAlbum->date().year() == 2009 && dAlbum->date().month() == 4) { QCOMPARE(albumModel->rowCount(index), 0); } else if (dAlbum->range() == DAlbum::Month && dAlbum->date().year() == 2009 && dAlbum->date().month() == 5) { QCOMPARE(albumModel->rowCount(index), 0); } else if (dAlbum->date().year() == 1997) { // Ignore these albums for order testing } else { qDebug() << "Unexpected album: " << dAlbum->title(); QFAIL("Unexpected album returned from model"); } } delete albumModel; } void AlbumModelTest::testDAlbumSorting() { qDebug() << "Start AlbumModelTest::testDAlbumSorting()"; DateAlbumModel dateAlbumModel; AlbumFilterModel albumModel; albumModel.setSourceAlbumModel(&dateAlbumModel); // first check ascending order albumModel.sort(0, Qt::AscendingOrder); int previousYear = 0; for (int yearRow = 0; yearRow < albumModel.rowCount(); ++yearRow) { QModelIndex yearIndex = albumModel.index(yearRow, 0); DAlbum* const yearAlbum = dynamic_cast (albumModel.albumForIndex(yearIndex)); QVERIFY(yearAlbum); QVERIFY(yearAlbum->date().year() > previousYear); previousYear = yearAlbum->date().year(); int previousMonth = 0; for (int monthRow = 0; monthRow < albumModel.rowCount(yearIndex); ++monthRow) { QModelIndex monthIndex = albumModel.index(monthRow, 0, yearIndex); DAlbum* const monthAlbum = dynamic_cast (albumModel.albumForIndex(monthIndex)); QVERIFY(monthAlbum); QVERIFY(monthAlbum->date().month() > previousMonth); previousMonth = monthAlbum->date().month(); } } // then check descending order albumModel.sort(0, Qt::DescendingOrder); previousYear = 1000000; for (int yearRow = 0; yearRow < albumModel.rowCount(); ++yearRow) { QModelIndex yearIndex = albumModel.index(yearRow, 0); DAlbum* const yearAlbum = dynamic_cast (albumModel.albumForIndex(yearIndex)); QVERIFY(yearAlbum); QVERIFY(yearAlbum->date().year() < previousYear); previousYear = yearAlbum->date().year(); int previousMonth = 13; for (int monthRow = 0; monthRow < albumModel.rowCount(yearIndex); ++monthRow) { QModelIndex monthIndex = albumModel.index(monthRow, 0, yearIndex); DAlbum* const monthAlbum = dynamic_cast (albumModel.albumForIndex(monthIndex)); QVERIFY(monthAlbum); QVERIFY(monthAlbum->date().month() < previousMonth); previousMonth = monthAlbum->date().month(); } } } void AlbumModelTest::testDAlbumCount() { qDebug() << "Start AlbumModelTest::testDAlbumCount()"; DateAlbumModel* const albumModel = new DateAlbumModel(); albumModel->setShowCount(true); ensureItemCounts(); qDebug() << "iterating over root indices"; // check year albums for (int yearRow = 0; yearRow < albumModel->rowCount(albumModel->rootAlbumIndex()); ++yearRow) { QModelIndex yearIndex = albumModel->index(yearRow, 0); DAlbum* const yearDAlbum = albumModel->albumForIndex(yearIndex); QVERIFY(yearDAlbum); QVERIFY(yearDAlbum->range() == DAlbum::Year); if (yearDAlbum->date().year() == 2007) { const int imagesInYear = 7; albumModel->includeChildrenCount(yearIndex); QCOMPARE(albumModel->albumCount(yearDAlbum), imagesInYear); albumModel->excludeChildrenCount(yearIndex); QCOMPARE(albumModel->albumCount(yearDAlbum), 0); albumModel->includeChildrenCount(yearIndex); QCOMPARE(albumModel->albumCount(yearDAlbum), imagesInYear); for (int monthRow = 0; monthRow < albumModel->rowCount(yearIndex); ++monthRow) { QModelIndex monthIndex = albumModel->index(monthRow, 0, yearIndex); DAlbum* monthDAlbum = albumModel->albumForIndex(monthIndex); QVERIFY(monthDAlbum); QVERIFY(monthDAlbum->range() == DAlbum::Month); QVERIFY(monthDAlbum->date().year() == 2007); if (monthDAlbum->date().month() == 3) { const int imagesInMonth = 3; albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->excludeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); } else if (monthDAlbum->date().month() == 4) { const int imagesInMonth = 4; albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->excludeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); } else { QFAIL("unexpected month album in 2007"); } } } else if (yearDAlbum->date().year() == 2009) { const int imagesInYear = 5; albumModel->includeChildrenCount(yearIndex); QCOMPARE(albumModel->albumCount(yearDAlbum), imagesInYear); albumModel->excludeChildrenCount(yearIndex); QCOMPARE(albumModel->albumCount(yearDAlbum), 0); albumModel->includeChildrenCount(yearIndex); QCOMPARE(albumModel->albumCount(yearDAlbum), imagesInYear); for (int monthRow = 0; monthRow < albumModel->rowCount(yearIndex); ++monthRow) { QModelIndex monthIndex = albumModel->index(monthRow, 0, yearIndex); DAlbum* monthDAlbum = albumModel->albumForIndex(monthIndex); QVERIFY(monthDAlbum); QVERIFY(monthDAlbum->range() == DAlbum::Month); QVERIFY(monthDAlbum->date().year() == 2009); if (monthDAlbum->date().month() == 3) { const int imagesInMonth = 2; albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->excludeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); } else if (monthDAlbum->date().month() == 4) { const int imagesInMonth = 2; albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->excludeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); } else if (monthDAlbum->date().month() == 5) { const int imagesInMonth = 1; albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->excludeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); } else { QFAIL("unexpected month album in 2009"); } } } else if (yearDAlbum->date().year() == 1997) { // Nothing to do here, ignore the albums for ordering tests } else { QFAIL("Received unexpected album from model"); } } delete albumModel; } void AlbumModelTest::testTAlbumModel() { qDebug() << "Start AlbumModelTest::testTAlbumModel()"; TagModel* albumModel = new TagModel(); ModelTest* test = new ModelTest(albumModel, 0); delete test; delete albumModel; albumModel = new TagModel(AbstractAlbumModel::IgnoreRootAlbum); test = new ModelTest(albumModel, 0); delete test; delete albumModel; } void AlbumModelTest::testSAlbumModel() { qDebug() << "Start AlbumModelTest::testSAlbumModel()"; SearchModel* const albumModel = new SearchModel(); ModelTest* const test = new ModelTest(albumModel, 0); delete test; delete albumModel; } diff --git a/core/tests/database/databasetagstest.cpp b/core/tests/database/databasetagstest.cpp index 37e5d99991..0aeb0f40aa 100644 --- a/core/tests/database/databasetagstest.cpp +++ b/core/tests/database/databasetagstest.cpp @@ -1,805 +1,805 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2015-12-13 * Description : test cases for tags tree manipulation in database * * Copyright (C) 2015-2018 by Gilles Caulier, * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "databasetagstest.h" // Qt includes #include #include #include #include // Local includes #include "albummanager.h" #include "albummodel.h" #include "albumthumbnailloader.h" #include "collectionlocation.h" #include "collectionmanager.h" #include "loadingcacheinterface.h" #include "scancontroller.h" #include "thumbnailloadthread.h" using namespace Digikam; const QString IMAGE_PATH(QFINDTESTDATA("data/")); QTEST_MAIN(DatabaseTagsTest) DatabaseTagsTest::DatabaseTagsTest() : palbumRoot0(0), palbumRoot1(0), palbumRoot2(0), palbumChild0Root0(0), palbumChild1Root0(0), palbumChild2Root0(0), palbumChild0Root1(0), rootTag(0), talbumRoot0(0), talbumRoot1(0), talbumChild0Root0(0), talbumChild1Root0(0), talbumChild0Child1Root0(0), talbumChild0Root1(0) { } DatabaseTagsTest::~DatabaseTagsTest() { } void DatabaseTagsTest::initTestCase() { tempSuffix = QLatin1String("databasetagstest-") + QTime::currentTime().toString(); - dbPath = QDir::temp().absolutePath() + QLatin1String("/") + tempSuffix; + dbPath = QDir::temp().absolutePath() + QLatin1Char('/') + tempSuffix; if (QDir::temp().exists(tempSuffix)) { QString msg = QLatin1String("Error creating temp path") + dbPath; QVERIFY2(false, msg.toLatin1().constData()); } QDir::temp().mkdir(tempSuffix); qDebug() << "Using database path for test: " << dbPath; // use a testing database AlbumManager::instance(); AlbumManager::checkDatabaseDirsAfterFirstRun(QDir::temp().absoluteFilePath( tempSuffix), QDir::temp().absoluteFilePath(tempSuffix)); DbEngineParameters params = DbEngineParameters::defaultParameters(QLatin1String("QMYSQL")); params.setInternalServerPath(dbPath); DatabaseServerStarter::startServerManagerProcess(); bool dbChangeGood = AlbumManager::instance()->setDatabase(params, false, QDir::temp().absoluteFilePath(tempSuffix)); QVERIFY2(dbChangeGood, "Could not set temp album db"); QList locs = CollectionManager::instance()->allAvailableLocations(); QVERIFY2(locs.size(), "Failed to auto-create one collection in setDatabase"); ScanController::instance()->completeCollectionScan(); AlbumManager::instance()->startScan(); AlbumList all = AlbumManager::instance()->allPAlbums(); qDebug() << "PAlbum registered : " << all.size(); foreach(Album* const a, all) { if (a) { qDebug() << " ==> Id : " << a->id() << " , is root : " << a->isRoot() << " , title : " << a->title(); } } QVERIFY2( all.size() == 3, "Failed to scan empty directory. We must have one root album, one album, and one trash."); } void DatabaseTagsTest::cleanupTestCase() { qDebug() << "Start DatabaseTagsTest::cleanupTestCase()"; ScanController::instance()->shutDown(); AlbumManager::instance()->cleanUp(); ThumbnailLoadThread::cleanUp(); AlbumThumbnailLoader::instance()->cleanUp(); LoadingCacheInterface::cleanUp(); QDir dir(dbPath); dir.removeRecursively(); qDebug() << "deleted test folder " << dbPath; } // Qt test doesn't use exceptions, so using assertion macros in methods called // from a test slot doesn't stop the test method and may result in inconsistent // data or segfaults. Therefore use macros for these functions. #define safeCreatePAlbum(parent, name, result) \ { \ QString error; \ result = AlbumManager::instance()->createPAlbum(parent, name, name, \ QDate::currentDate(), albumCategory, error); \ QVERIFY2(result, QString::fromUtf8("Error creating PAlbum for test: %1").arg(error).toLatin1().constData()); \ } #define safeCreateTAlbum(parent, name, result) \ { \ QString error; \ result = AlbumManager::instance()->createTAlbum(parent, name, QLatin1String(""), error); \ QVERIFY2(result, QString::fromUtf8("Error creating TAlbum for test: %1").arg(error).toLatin1().constData()); \ } void DatabaseTagsTest::init() { qDebug() << "Start DatabaseTagsTest::init()"; /* palbumCountMap.clear(); // create a model to check that model work is done correctly while scanning addedIds.clear(); startModel = new AlbumModel; startModel->setShowCount(true); connect(startModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(slotStartModelRowsInserted(QModelIndex,int,int))); connect(startModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(slotStartModelDataChanged(QModelIndex,QModelIndex))); qDebug() << "Created startModel" << startModel; // ensure that this model is empty in the beginning except for the root // album and the collection that include trash QCOMPARE(startModel->rowCount(), 1); QModelIndex rootIndex = startModel->index(0, 0); QCOMPARE(startModel->rowCount(rootIndex), 1); QModelIndex collectionIndex = startModel->index(0, 0, rootIndex); QCOMPARE(startModel->rowCount(collectionIndex), 1); // insert some test data // physical albums // create two of them by creating directories and scanning QDir dir(dbPath); dir.mkdir(QLatin1String("root0")); dir.mkdir(QLatin1String("root1")); ScanController::instance()->completeCollectionScan(); AlbumManager::instance()->refresh(); QCOMPARE(AlbumManager::instance()->allPAlbums().size(), 5); QString error; palbumRoot0 = AlbumManager::instance()->findPAlbum(QUrl::fromLocalFile(dbPath + QLatin1String("/root0"))); QVERIFY2(palbumRoot0, "Error having PAlbum root0 in AlbumManager"); palbumRoot1 = AlbumManager::instance()->findPAlbum(QUrl::fromLocalFile(dbPath + QLatin1String("/root1"))); QVERIFY2(palbumRoot1, "Error having PAlbum root1 in AlbumManager"); // Create some more through AlbumManager palbumRoot2 = AlbumManager::instance()->createPAlbum(dbPath, QLatin1String("root2"), QLatin1String("root album 2"), QDate::currentDate(), albumCategory, error); QVERIFY2(palbumRoot2, QString::fromUtf8("Error creating PAlbum for test: %1").arg(error).toLatin1().constData()); safeCreatePAlbum(palbumRoot0, QLatin1String("root0child0"), palbumChild0Root0); safeCreatePAlbum(palbumRoot0, QLatin1String("root0child1"), palbumChild1Root0); const QString sameName = QLatin1String("sameName Album"); safeCreatePAlbum(palbumRoot0, sameName, palbumChild2Root0); safeCreatePAlbum(palbumRoot1, sameName, palbumChild0Root1); qDebug() << "AlbumManager now knows these PAlbums:"; foreach(Album* const a, AlbumManager::instance()->allPAlbums()) { qDebug() << "\t" << a->title(); } // tags rootTag = AlbumManager::instance()->findTAlbum(0); QVERIFY(rootTag); safeCreateTAlbum(rootTag, QLatin1String("root0"), talbumRoot0); safeCreateTAlbum(rootTag, QLatin1String("root1"), talbumRoot1); safeCreateTAlbum(talbumRoot0, QLatin1String("child0 root 0"), talbumChild0Root0); safeCreateTAlbum(talbumRoot0, QLatin1String("child1 root 0"), talbumChild1Root0); safeCreateTAlbum(talbumChild1Root0, sameName, talbumChild0Child1Root0); safeCreateTAlbum(talbumRoot1, sameName, talbumChild0Root1); qDebug() << "created tags"; // add some images for having date albums QDir imageDir(IMAGE_PATH); imageDir.setNameFilters(QStringList() << QLatin1String("*.jpg")); QStringList imageFiles = imageDir.entryList(); qDebug() << "copying images " << imageFiles << " to " << palbumChild0Root0->fileUrl(); foreach(const QString& imageFile, imageFiles) { QString src = IMAGE_PATH + QLatin1Char('/') + imageFile; QString dst = palbumChild0Root0->fileUrl().toLocalFile() + QLatin1Char('/') + imageFile; bool copied = QFile::copy(src, dst); QVERIFY2(copied, "Test images must be copied"); } ScanController::instance()->completeCollectionScan(); if (AlbumManager::instance()->allDAlbums().count() <= 1) { ensureItemCounts(); } qDebug() << "date albums: " << AlbumManager::instance()->allDAlbums(); // root + 2 years + 2 and 3 months per year + (1997 as test year for date ordering with 12 months) = 21 QCOMPARE(AlbumManager::instance()->allDAlbums().size(), 21); // ensure that there is a root date album DAlbum* const rootFromAlbumManager = AlbumManager::instance()->findDAlbum(0); QVERIFY(rootFromAlbumManager); DAlbum* rootFromList = 0; foreach(Album* const album, AlbumManager::instance()->allDAlbums()) { DAlbum* const dAlbum = dynamic_cast (album); QVERIFY(dAlbum); if (dAlbum->isRoot()) { rootFromList = dAlbum; } } QVERIFY(rootFromList); QVERIFY(rootFromList == rootFromAlbumManager); */ } void DatabaseTagsTest::cleanup() { /* if (startModel) { disconnect(startModel); } delete startModel; addedIds.clear(); // remove all test data AlbumManager::instance()->refresh(); // remove all palbums' directories deletePAlbum(palbumRoot0); deletePAlbum(palbumRoot1); deletePAlbum(palbumRoot2); // take over changes to database ScanController::instance()->completeCollectionScan(); // reread from database AlbumManager::instance()->refresh(); // root + one collection QCOMPARE(AlbumManager::instance()->allPAlbums().size(), 2); // remove all tags QString error; bool removed = AlbumManager::instance()->deleteTAlbum(talbumRoot0, error); QVERIFY2(removed, QString::fromUtf8("Error removing a tag: %1").arg(error).toLatin1().constData()); removed = AlbumManager::instance()->deleteTAlbum(talbumRoot1, error); QVERIFY2(removed, QString::fromUtf8("Error removing a tag: %1").arg(error).toLatin1().constData()); QCOMPARE(AlbumManager::instance()->allTAlbums().size(), 1); */ } /* void DatabaseTagsTest::testStartAlbumModel() { qDebug() << "Start DatabaseTagsTest::testStartAlbumModel()"; // verify that the start album model got all these changes // one root QCOMPARE(startModel->rowCount(), 1); // one collection QModelIndex rootIndex = startModel->index(0, 0); QCOMPARE(startModel->rowCount(rootIndex), 1); // two albums in the collection QModelIndex collectionIndex = startModel->index(0, 0, rootIndex); QCOMPARE(startModel->rowCount(collectionIndex), 3); // this is should be enough for now // We must have received an added notation for everything except album root // and collection QCOMPARE(addedIds.size(), 7); } void DatabaseTagsTest::ensureItemCounts() { // trigger listing job QEventLoop dAlbumLoop; connect(AlbumManager::instance(), SIGNAL(signalAllDAlbumsLoaded()), &dAlbumLoop, SLOT(quit())); AlbumManager::instance()->prepareItemCounts(); qDebug() << "Waiting for AlbumManager and the IOSlave to create DAlbums..."; dAlbumLoop.exec(); qDebug() << "DAlbums were created"; while (palbumCountMap.size() < 8) { QEventLoop pAlbumLoop; connect(AlbumManager::instance(), SIGNAL(signalPAlbumsDirty(QMap)), &pAlbumLoop, SLOT(quit())); qDebug() << "Waiting for first PAlbum count map"; pAlbumLoop.exec(); qDebug() << "Got new PAlbum count map"; } } void DatabaseTagsTest::slotStartModelRowsInserted(const QModelIndex& parent, int start, int end) { qDebug() << "called, parent:" << parent << ", start:" << start << ", end:" << end; for (int row = start; row <= end; ++row) { QModelIndex child = startModel->index(row, 0, parent); QVERIFY(child.isValid()); Album* album = startModel->albumForIndex(child); const int id = child.data(AbstractAlbumModel::AlbumIdRole).toInt(); QVERIFY(album); qDebug() << "added album with id" << id << "and name" << album->title(); addedIds << id; } } void DatabaseTagsTest::slotStartModelDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) { for (int row = topLeft.row(); row <= bottomRight.row(); ++row) { QModelIndex index = startModel->index(row, topLeft.column(), topLeft.parent()); if (!index.isValid()) { qDebug() << "Illegal index received"; continue; } int albumId = index.data(AbstractAlbumModel::AlbumIdRole).toInt(); if (!addedIds.contains(albumId)) { QString message = QLatin1String("Album id ") + QString::number(albumId) + QLatin1String(" was changed before adding signal was received"); QFAIL(message.toLatin1().constData()); qDebug() << message; } } } void DatabaseTagsTest::deletePAlbum(PAlbum* album) { QDir dir(album->folderPath()); dir.removeRecursively(); } void DatabaseTagsTest::setLastPAlbumCountMap(const QMap &map) { qDebug() << "Receiving new count map "<< map; palbumCountMap = map; } void DatabaseTagsTest::testPAlbumModel() { qDebug() << "Start DatabaseTagsTest::testPAlbumModel()"; AlbumModel* albumModel = new AlbumModel(); ModelTest* test = new ModelTest(albumModel, 0); delete test; delete albumModel; albumModel = new AlbumModel(AbstractAlbumModel::IgnoreRootAlbum); test = new ModelTest(albumModel, 0); delete test; delete albumModel; } void DatabaseTagsTest::testDisablePAlbumCount() { qDebug() << "Start DatabaseTagsTest::testDisablePAlbumCount()"; AlbumModel albumModel; albumModel.setCountMap(palbumCountMap); albumModel.setShowCount(true); QRegExp countRegEx(QLatin1String(".+ \\(\\d+\\)")); countRegEx.setMinimal(true); QVERIFY(countRegEx.exactMatch(QLatin1String("test (10)"))); QVERIFY(countRegEx.exactMatch(QLatin1String("te st (10)"))); QVERIFY(countRegEx.exactMatch(QLatin1String("te st (0)"))); QVERIFY(!countRegEx.exactMatch(QLatin1String("te st ()"))); QVERIFY(!countRegEx.exactMatch(QLatin1String("te st"))); QVERIFY(!countRegEx.exactMatch(QLatin1String("te st (10) bla"))); // ensure that all albums except the root album have a count attached QModelIndex rootIndex = albumModel.index(0, 0, QModelIndex()); QString rootTitle = albumModel.data(rootIndex, Qt::DisplayRole).toString(); QVERIFY(!countRegEx.exactMatch(rootTitle)); for (int collectionRow = 0; collectionRow < albumModel.rowCount(rootIndex); ++collectionRow) { QModelIndex collectionIndex = albumModel.index(collectionRow, 0, rootIndex); QString collectionTitle = albumModel.data(collectionIndex, Qt::DisplayRole).toString(); QVERIFY2(countRegEx.exactMatch(collectionTitle), QString::fromUtf8("%1 matching error").arg(collectionTitle).toLatin1().constData()); for (int albumRow = 0; albumRow < albumModel.rowCount(collectionIndex); ++albumRow) { QModelIndex albumIndex = albumModel.index(albumRow, 0, collectionIndex); QString albumTitle = albumModel.data(albumIndex, Qt::DisplayRole).toString(); QVERIFY2(countRegEx.exactMatch(albumTitle), QString::fromUtf8("%1 matching error").arg(albumTitle).toLatin1().constData()); } } // now disable showing the count albumModel.setShowCount(false); // ensure that no album has a count attached rootTitle = albumModel.data(rootIndex, Qt::DisplayRole).toString(); QVERIFY(!countRegEx.exactMatch(rootTitle)); for (int collectionRow = 0; collectionRow < albumModel.rowCount(rootIndex); ++collectionRow) { QModelIndex collectionIndex = albumModel.index(collectionRow, 0, rootIndex); QString collectionTitle = albumModel.data(collectionIndex, Qt::DisplayRole).toString(); QVERIFY2(!countRegEx.exactMatch(collectionTitle), QString::fromUtf8("%1 matching error").arg(collectionTitle).toLatin1().constData()); for (int albumRow = 0; albumRow < albumModel.rowCount(collectionIndex); ++albumRow) { QModelIndex albumIndex = albumModel.index(albumRow, 0, collectionIndex); QString albumTitle = albumModel.data(albumIndex, Qt::DisplayRole).toString(); QVERIFY2(!countRegEx.exactMatch(albumTitle), QString::fromUtf8("%1 matching error").arg(albumTitle).toLatin1().constData()); } } } void DatabaseTagsTest::testDAlbumModel() { qDebug() << "Start DatabaseTagsTest::testDAlbumModel()"; DateAlbumModel* const albumModel = new DateAlbumModel(); ensureItemCounts(); ModelTest* const test = new ModelTest(albumModel, 0); delete test; delete albumModel; } void DatabaseTagsTest::testDAlbumContainsAlbums() { qDebug() << "Start DatabaseTagsTest::testDAlbumContainsAlbums()"; DateAlbumModel* const albumModel = new DateAlbumModel(); ensureItemCounts(); QVERIFY(albumModel->rootAlbum()); foreach(Album* const album, AlbumManager::instance()->allDAlbums()) { DAlbum* const dAlbum = dynamic_cast (album); QVERIFY(dAlbum); qDebug() << "checking album for date " << dAlbum->date() << ", range = " << dAlbum->range(); QModelIndex index = albumModel->indexForAlbum(dAlbum); if (!dAlbum->isRoot()) { QVERIFY(index.isValid()); } if (dAlbum->isRoot()) { // root album QVERIFY(dAlbum->isRoot()); QCOMPARE(albumModel->rowCount(index), 3); QCOMPARE(index, albumModel->rootAlbumIndex()); } else if (dAlbum->range() == DAlbum::Year && dAlbum->date().year() == 2007) { QCOMPARE(albumModel->rowCount(index), 2); } else if (dAlbum->range() == DAlbum::Year && dAlbum->date().year() == 2009) { QCOMPARE(albumModel->rowCount(index), 3); } else if (dAlbum->range() == DAlbum::Month && dAlbum->date().year() == 2007 && dAlbum->date().month() == 3) { QCOMPARE(albumModel->rowCount(index), 0); } else if (dAlbum->range() == DAlbum::Month && dAlbum->date().year() == 2007 && dAlbum->date().month() == 4) { QCOMPARE(albumModel->rowCount(index), 0); } else if (dAlbum->range() == DAlbum::Month && dAlbum->date().year() == 2009 && dAlbum->date().month() == 3) { QCOMPARE(albumModel->rowCount(index), 0); } else if (dAlbum->range() == DAlbum::Month && dAlbum->date().year() == 2009 && dAlbum->date().month() == 4) { QCOMPARE(albumModel->rowCount(index), 0); } else if (dAlbum->range() == DAlbum::Month && dAlbum->date().year() == 2009 && dAlbum->date().month() == 5) { QCOMPARE(albumModel->rowCount(index), 0); } else if (dAlbum->date().year() == 1997) { // Ignore these albums for order testing } else { qDebug() << "Unexpected album: " << dAlbum->title(); QFAIL("Unexpected album returned from model"); } } delete albumModel; } void DatabaseTagsTest::testDAlbumSorting() { qDebug() << "Start DatabaseTagsTest::testDAlbumSorting()"; DateAlbumModel dateAlbumModel; AlbumFilterModel albumModel; albumModel.setSourceAlbumModel(&dateAlbumModel); // first check ascending order albumModel.sort(0, Qt::AscendingOrder); int previousYear = 0; for (int yearRow = 0; yearRow < albumModel.rowCount(); ++yearRow) { QModelIndex yearIndex = albumModel.index(yearRow, 0); DAlbum* const yearAlbum = dynamic_cast (albumModel.albumForIndex(yearIndex)); QVERIFY(yearAlbum); QVERIFY(yearAlbum->date().year() > previousYear); previousYear = yearAlbum->date().year(); int previousMonth = 0; for (int monthRow = 0; monthRow < albumModel.rowCount(yearIndex); ++monthRow) { QModelIndex monthIndex = albumModel.index(monthRow, 0, yearIndex); DAlbum* const monthAlbum = dynamic_cast (albumModel.albumForIndex(monthIndex)); QVERIFY(monthAlbum); QVERIFY(monthAlbum->date().month() > previousMonth); previousMonth = monthAlbum->date().month(); } } // then check descending order albumModel.sort(0, Qt::DescendingOrder); previousYear = 1000000; for (int yearRow = 0; yearRow < albumModel.rowCount(); ++yearRow) { QModelIndex yearIndex = albumModel.index(yearRow, 0); DAlbum* const yearAlbum = dynamic_cast (albumModel.albumForIndex(yearIndex)); QVERIFY(yearAlbum); QVERIFY(yearAlbum->date().year() < previousYear); previousYear = yearAlbum->date().year(); int previousMonth = 13; for (int monthRow = 0; monthRow < albumModel.rowCount(yearIndex); ++monthRow) { QModelIndex monthIndex = albumModel.index(monthRow, 0, yearIndex); DAlbum* const monthAlbum = dynamic_cast (albumModel.albumForIndex(monthIndex)); QVERIFY(monthAlbum); QVERIFY(monthAlbum->date().month() < previousMonth); previousMonth = monthAlbum->date().month(); } } } void DatabaseTagsTest::testDAlbumCount() { qDebug() << "Start DatabaseTagsTest::testDAlbumCount()"; DateAlbumModel* const albumModel = new DateAlbumModel(); albumModel->setShowCount(true); ensureItemCounts(); qDebug() << "iterating over root indices"; // check year albums for (int yearRow = 0; yearRow < albumModel->rowCount(albumModel->rootAlbumIndex()); ++yearRow) { QModelIndex yearIndex = albumModel->index(yearRow, 0); DAlbum* const yearDAlbum = albumModel->albumForIndex(yearIndex); QVERIFY(yearDAlbum); QVERIFY(yearDAlbum->range() == DAlbum::Year); if (yearDAlbum->date().year() == 2007) { const int imagesInYear = 7; albumModel->includeChildrenCount(yearIndex); QCOMPARE(albumModel->albumCount(yearDAlbum), imagesInYear); albumModel->excludeChildrenCount(yearIndex); QCOMPARE(albumModel->albumCount(yearDAlbum), 0); albumModel->includeChildrenCount(yearIndex); QCOMPARE(albumModel->albumCount(yearDAlbum), imagesInYear); for (int monthRow = 0; monthRow < albumModel->rowCount(yearIndex); ++monthRow) { QModelIndex monthIndex = albumModel->index(monthRow, 0, yearIndex); DAlbum* monthDAlbum = albumModel->albumForIndex(monthIndex); QVERIFY(monthDAlbum); QVERIFY(monthDAlbum->range() == DAlbum::Month); QVERIFY(monthDAlbum->date().year() == 2007); if (monthDAlbum->date().month() == 3) { const int imagesInMonth = 3; albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->excludeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); } else if (monthDAlbum->date().month() == 4) { const int imagesInMonth = 4; albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->excludeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); } else { QFAIL("unexpected month album in 2007"); } } } else if (yearDAlbum->date().year() == 2009) { const int imagesInYear = 5; albumModel->includeChildrenCount(yearIndex); QCOMPARE(albumModel->albumCount(yearDAlbum), imagesInYear); albumModel->excludeChildrenCount(yearIndex); QCOMPARE(albumModel->albumCount(yearDAlbum), 0); albumModel->includeChildrenCount(yearIndex); QCOMPARE(albumModel->albumCount(yearDAlbum), imagesInYear); for (int monthRow = 0; monthRow < albumModel->rowCount(yearIndex); ++monthRow) { QModelIndex monthIndex = albumModel->index(monthRow, 0, yearIndex); DAlbum* monthDAlbum = albumModel->albumForIndex(monthIndex); QVERIFY(monthDAlbum); QVERIFY(monthDAlbum->range() == DAlbum::Month); QVERIFY(monthDAlbum->date().year() == 2009); if (monthDAlbum->date().month() == 3) { const int imagesInMonth = 2; albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->excludeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); } else if (monthDAlbum->date().month() == 4) { const int imagesInMonth = 2; albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->excludeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); } else if (monthDAlbum->date().month() == 5) { const int imagesInMonth = 1; albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->excludeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); } else { QFAIL("unexpected month album in 2009"); } } } else if (yearDAlbum->date().year() == 1997) { // Nothing to do here, ignore the albums for ordering tests } else { QFAIL("Received unexpected album from model"); } } delete albumModel; } void DatabaseTagsTest::testTAlbumModel() { qDebug() << "Start DatabaseTagsTest::testTAlbumModel()"; TagModel* albumModel = new TagModel(); ModelTest* test = new ModelTest(albumModel, 0); delete test; delete albumModel; albumModel = new TagModel(AbstractAlbumModel::IgnoreRootAlbum); test = new ModelTest(albumModel, 0); delete test; delete albumModel; } void DatabaseTagsTest::testSAlbumModel() { qDebug() << "Start DatabaseTagsTest::testSAlbumModel()"; SearchModel* const albumModel = new SearchModel(); ModelTest* const test = new ModelTest(albumModel, 0); delete test; delete albumModel; } */ diff --git a/core/utilities/assistants/panorama/tasks/panotask.cpp b/core/utilities/assistants/panorama/tasks/panotask.cpp index 7009882594..780090cc9b 100644 --- a/core/utilities/assistants/panorama/tasks/panotask.cpp +++ b/core/utilities/assistants/panorama/tasks/panotask.cpp @@ -1,54 +1,54 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2012-03-15 * Description : a tool to create panorama by fusion of several images. * * Copyright (C) 2012-2016 by Benjamin Girault * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "panotask.h" // Qt includes #include namespace Digikam { PanoTask::PanoTask(PanoAction action, const QString& workDirPath) : action(action), isAbortedFlag(false), successFlag(false), - tmpDir(QUrl::fromLocalFile(workDirPath + QLatin1String("/"))) + tmpDir(QUrl::fromLocalFile(workDirPath + QLatin1Char('/'))) { } PanoTask::~PanoTask() { } bool PanoTask::success() const { return successFlag; } void PanoTask::requestAbort() { isAbortedFlag = true; } } // namespace Digikam diff --git a/core/utilities/assistants/printcreator/manager/advprinttask.cpp b/core/utilities/assistants/printcreator/manager/advprinttask.cpp index 552bd704fc..6955807bc7 100644 --- a/core/utilities/assistants/printcreator/manager/advprinttask.cpp +++ b/core/utilities/assistants/printcreator/manager/advprinttask.cpp @@ -1,705 +1,705 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2007-11-07 * Description : a tool to print images * * Copyright (C) 2017-2018 by Gilles Caulier * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "advprinttask.h" // C++ includes #include // Qt includes #include #include #include #include // KDE includes #include // Local includes #include "advprintwizard.h" #include "advprintphoto.h" #include "advprintcaptionpage.h" #include "dmetadata.h" #include "dfileoperations.h" #include "dimg.h" #include "digikam_debug.h" #include "digikam_config.h" namespace Digikam { class AdvPrintTask::Private { public: explicit Private() : settings(0), mode(AdvPrintTask::PRINT), sizeIndex(0) { } public: AdvPrintSettings* settings; PrintMode mode; QSize size; int sizeIndex; }; // ------------------------------------------------------- AdvPrintTask::AdvPrintTask(AdvPrintSettings* const settings, PrintMode mode, const QSize& size, int sizeIndex) : ActionJob(), d(new Private) { d->settings = settings; d->mode = mode; d->size = size; d->sizeIndex = sizeIndex; } AdvPrintTask::~AdvPrintTask() { cancel(); delete d; } void AdvPrintTask::run() { switch (d->mode) { case PREPAREPRINT: qCDebug(DIGIKAM_GENERAL_LOG) << "Start prepare to print"; preparePrint(); emit signalDone(!m_cancel); qCDebug(DIGIKAM_GENERAL_LOG) << "Prepare to print is done"; break; case PRINT: qCDebug(DIGIKAM_GENERAL_LOG) << "Start to print"; if (d->settings->printerName != d->settings->outputName(AdvPrintSettings::FILES) && d->settings->printerName != d->settings->outputName(AdvPrintSettings::GIMP)) { printPhotos(); emit signalDone(!m_cancel); } else { QStringList files = printPhotosToFile(); if (d->settings->printerName == d->settings->outputName(AdvPrintSettings::GIMP)) { d->settings->gimpFiles << files; } emit signalDone(!m_cancel && !files.isEmpty()); } qCDebug(DIGIKAM_GENERAL_LOG) << "Print is done"; break; default: // PREVIEW qCDebug(DIGIKAM_GENERAL_LOG) << "Start to compute preview"; QImage img(d->size, QImage::Format_ARGB32_Premultiplied); QPainter p(&img); p.setCompositionMode(QPainter::CompositionMode_Clear); p.fillRect(img.rect(), Qt::color0); p.setCompositionMode(QPainter::CompositionMode_SourceOver); paintOnePage(p, d->settings->photos, d->settings->outputLayouts->m_layouts, d->settings->currentPreviewPage, d->settings->disableCrop, true); p.end(); if (!m_cancel) emit signalPreview(img); qCDebug(DIGIKAM_GENERAL_LOG) << "Preview computation is done"; break; } } void AdvPrintTask::preparePrint() { int photoIndex = 0; for (QList::iterator it = d->settings->photos.begin() ; it != d->settings->photos.end() ; ++it) { AdvPrintPhoto* const photo = static_cast(*it); if (photo && photo->m_cropRegion == QRect(-1, -1, -1, -1)) { QRect* const curr = d->settings->getLayout(photoIndex, d->sizeIndex); photo->updateCropRegion(curr->width(), curr->height(), d->settings->outputLayouts->m_autoRotate); } photoIndex++; emit signalProgress(photoIndex); if (m_cancel) { signalMessage(i18n("Printing canceled"), true); return; } } } void AdvPrintTask::printPhotos() { AdvPrintPhotoSize* const layouts = d->settings->outputLayouts; QPrinter* const printer = d->settings->outputPrinter; Q_ASSERT(layouts); Q_ASSERT(printer); Q_ASSERT(layouts->m_layouts.count() > 1); QList photos = d->settings->photos; QPainter p; p.begin(printer); int current = 0; int pageCount = 1; bool printing = true; while (printing) { signalMessage(i18n("Processing page %1", pageCount), false); printing = paintOnePage(p, photos, layouts->m_layouts, current, d->settings->disableCrop); if (printing) { printer->newPage(); } pageCount++; emit signalProgress(current); if (m_cancel) { printer->abort(); signalMessage(i18n("Printing canceled"), true); return; } } p.end(); } QStringList AdvPrintTask::printPhotosToFile() { AdvPrintPhotoSize* const layouts = d->settings->outputLayouts; QString dir = d->settings->outputPath; Q_ASSERT(layouts); Q_ASSERT(!dir.isEmpty()); Q_ASSERT(layouts->m_layouts.count() > 1); QList photos = d->settings->photos; QStringList files; int current = 0; int pageCount = 1; bool printing = true; QRect* const srcPage = layouts->m_layouts.at(0); while (printing) { // make a pixmap to save to file. Make it just big enough to show the // highest-dpi image on the page without losing data. double dpi = layouts->m_dpi; if (dpi == 0.0) { dpi = getMaxDPI(photos, layouts->m_layouts, current) * 1.1; } int w = AdvPrintWizard::normalizedInt(srcPage->width()); int h = AdvPrintWizard::normalizedInt(srcPage->height()); QImage image(w, h, QImage::Format_ARGB32_Premultiplied); QPainter painter; painter.begin(&image); QString ext = d->settings->format(); QString name = QLatin1String("output"); - QString filename = dir + QLatin1String("/") + - name + QLatin1String("_") + + QString filename = dir + QLatin1Char('/') + + name + QLatin1Char('_') + QString::number(pageCount) + - QLatin1String(".") + ext; + QLatin1Char('.') + ext; if (QFile::exists(filename) && d->settings->conflictRule != FileSaveConflictBox::OVERWRITE) { filename = DFileOperations::getUniqueFileUrl(QUrl::fromLocalFile(filename)).toLocalFile(); } signalMessage(i18n("Processing page %1", pageCount), false); printing = paintOnePage(painter, photos, layouts->m_layouts, current, d->settings->disableCrop); painter.end(); if (!image.save(filename, 0, 100)) { signalMessage(i18n("Could not save file %1", filename), true); break; } else { files.append(filename); signalMessage(i18n("Page %1 saved as %2", pageCount, filename), false); } pageCount++; emit signalProgress(current); if (m_cancel) { signalMessage(i18n("Printing canceled"), true); break; } } return files; } bool AdvPrintTask::paintOnePage(QPainter& p, const QList& photos, const QList& layouts, int& current, bool cropDisabled, bool useThumbnails) { if (layouts.isEmpty()) { qCWarning(DIGIKAM_GENERAL_LOG) << "Invalid layout content"; return true; } if (photos.count() == 0) { qCWarning(DIGIKAM_GENERAL_LOG) << "no photo to print"; // no photos => last photo return true; } QList::const_iterator it = layouts.begin(); QRect* const srcPage = static_cast(*it); ++it; QRect* layout = static_cast(*it); // scale the page size to best fit the painter // size the rectangle based on the minimum image dimension int destW = p.window().width(); int destH = p.window().height(); int srcW = srcPage->width(); int srcH = srcPage->height(); if (destW < destH) { destH = AdvPrintWizard::normalizedInt((double) destW * ((double) srcH / (double) srcW)); if (destH > p.window().height()) { destH = p.window().height(); destW = AdvPrintWizard::normalizedInt((double) destH * ((double) srcW / (double) srcH)); } } else { destW = AdvPrintWizard::normalizedInt((double) destH * ((double) srcW / (double) srcH)); if (destW > p.window().width()) { destW = p.window().width(); destH = AdvPrintWizard::normalizedInt((double) destW * ((double) srcH / (double) srcW)); } } double xRatio = (double) destW / (double) srcPage->width(); double yRatio = (double) destH / (double) srcPage->height(); int left = (p.window().width() - destW) / 2; int top = (p.window().height() - destH) / 2; // FIXME: may not want to erase the background page p.eraseRect(left, top, AdvPrintWizard::normalizedInt((double) srcPage->width() * xRatio), AdvPrintWizard::normalizedInt((double) srcPage->height() * yRatio)); for (; (current < photos.count()) && !m_cancel ; ++current) { AdvPrintPhoto* const photo = photos.at(current); // crop QImage img; if (useThumbnails) { img = photo->thumbnail().copyQImage(); } else { img = photo->loadPhoto().copyQImage(); } // next, do we rotate? if (photo->m_rotation != 0) { // rotate QMatrix matrix; matrix.rotate(photo->m_rotation); img = img.transformed(matrix); } if (useThumbnails) { // scale the crop region to thumbnail coords double xRatio = 0.0; double yRatio = 0.0; if (photo->thumbnail().width() != 0) { xRatio = (double)photo->thumbnail().width() / (double)photo->width(); } if (photo->thumbnail().height() != 0) { yRatio = (double)photo->thumbnail().height() / (double)photo->height(); } int x1 = AdvPrintWizard::normalizedInt((double)photo->m_cropRegion.left() * xRatio); int y1 = AdvPrintWizard::normalizedInt((double)photo->m_cropRegion.top() * yRatio); int w = AdvPrintWizard::normalizedInt((double)photo->m_cropRegion.width() * xRatio); int h = AdvPrintWizard::normalizedInt((double)photo->m_cropRegion.height() * yRatio); img = img.copy(QRect(x1, y1, w, h)); } else if (!cropDisabled) { img = img.copy(photo->m_cropRegion); } int x1 = AdvPrintWizard::normalizedInt((double) layout->left() * xRatio); int y1 = AdvPrintWizard::normalizedInt((double) layout->top() * yRatio); int w = AdvPrintWizard::normalizedInt((double) layout->width() * xRatio); int h = AdvPrintWizard::normalizedInt((double) layout->height() * yRatio); QRect rectViewPort = p.viewport(); QRect newRectViewPort = QRect(x1 + left, y1 + top, w, h); QSize imageSize = img.size(); /* qCDebug(DIGIKAM_GENERAL_LOG) << "Image " << photo->filename << " size " << imageSize; qCDebug(DIGIKAM_GENERAL_LOG) << "viewport size " << newRectViewPort.size(); */ QPoint point; if (cropDisabled) { imageSize.scale(newRectViewPort.size(), Qt::KeepAspectRatio); int spaceLeft = (newRectViewPort.width() - imageSize.width()) / 2; int spaceTop = (newRectViewPort.height() - imageSize.height()) / 2; p.setViewport(spaceLeft + newRectViewPort.x(), spaceTop + newRectViewPort.y(), imageSize.width(), imageSize.height()); point = QPoint(newRectViewPort.x() + spaceLeft + imageSize.width(), newRectViewPort.y() + spaceTop + imageSize.height()); } else { p.setViewport(newRectViewPort); point = QPoint(x1 + left + w, y1 + top + w); } QRect rectWindow = p.window(); p.setWindow(img.rect()); p.drawImage(0, 0, img); p.setViewport(rectViewPort); p.setWindow(rectWindow); p.setBrushOrigin(point); if (photo->m_pAdvPrintCaptionInfo && photo->m_pAdvPrintCaptionInfo->m_captionType != AdvPrintSettings::NONE) { p.save(); QString caption = AdvPrintCaptionPage::captionFormatter(photo); qCDebug(DIGIKAM_GENERAL_LOG) << "Caption for" << photo->m_url << ":" << caption; // draw the text at (0,0), but we will translate and rotate the world // before drawing so the text will be in the correct location // next, do we rotate? int captionW = w - 2; double ratio = photo->m_pAdvPrintCaptionInfo->m_captionSize * 0.01; int captionH = (int)(qMin(w, h) * ratio); int exifOrientation = DMetadata::ORIENTATION_NORMAL; int orientatation = photo->m_rotation; if (photo->m_iface) { DItemInfo info(photo->m_iface->itemInfo(photo->m_url)); exifOrientation = info.orientation(); } else { DMetadata meta(photo->m_url.toLocalFile()); exifOrientation = meta.getImageOrientation(); } // ROT_90_HFLIP .. ROT_270 if (exifOrientation == DMetadata::ORIENTATION_ROT_90_HFLIP || exifOrientation == DMetadata::ORIENTATION_ROT_90 || exifOrientation == DMetadata::ORIENTATION_ROT_90_VFLIP || exifOrientation == DMetadata::ORIENTATION_ROT_270) { orientatation = (photo->m_rotation + 270) % 360; // -90 degrees } if (orientatation == 90 || orientatation == 270) { captionW = h; } p.rotate(orientatation); qCDebug(DIGIKAM_GENERAL_LOG) << "rotation " << photo->m_rotation << " orientation " << orientatation; int tx = left; int ty = top; switch (orientatation) { case 0 : { tx += x1 + 1; ty += y1 + (h - captionH - 1); break; } case 90 : { tx = top + y1 + 1; ty = -left - x1 - captionH - 1; break; } case 180 : { tx = -left - x1 - w + 1; ty = -top - y1 - (captionH + 1); break; } case 270 : { tx = -top - y1 - h + 1; ty = left + x1 + (w - captionH) - 1; break; } } p.translate(tx, ty); printCaption(p, photo, captionW, captionH, caption); p.restore(); } // iterate to the next position ++it; layout = (it == layouts.end()) ? 0 : static_cast(*it); if (layout == 0) { current++; break; } } // did we print the last photo? return (current < photos.count()); } double AdvPrintTask::getMaxDPI(const QList& photos, const QList& layouts, int current) { Q_ASSERT(layouts.count() > 1); QList::const_iterator it = layouts.begin(); QRect* layout = static_cast(*it); double maxDPI = 0.0; for (; current < photos.count(); ++current) { AdvPrintPhoto* const photo = photos.at(current); double dpi = ((double) photo->m_cropRegion.width() + (double) photo->m_cropRegion.height()) / (((double) layout->width() / 1000.0) + ((double) layout->height() / 1000.0)); if (dpi > maxDPI) maxDPI = dpi; // iterate to the next position ++it; layout = (it == layouts.end()) ? 0 : static_cast(*it); if (layout == 0) { break; } } return maxDPI; } void AdvPrintTask::printCaption(QPainter& p, AdvPrintPhoto* const photo, int captionW, int captionH, const QString& caption) { QStringList captionByLines; int captionIndex = 0; while (captionIndex < caption.length()) { QString newLine; bool breakLine = false; // End Of Line found int currIndex; // Caption QString current index // Check minimal lines dimension // TODO: fix length, maybe useless int captionLineLocalLength = 40; for (currIndex = captionIndex ; currIndex < caption.length() && !breakLine ; ++currIndex) { if (caption[currIndex] == QLatin1Char('\n') || caption[currIndex].isSpace()) { breakLine = true; } } if (captionLineLocalLength <= (currIndex - captionIndex)) { captionLineLocalLength = (currIndex - captionIndex); } breakLine = false; for (currIndex = captionIndex; (currIndex <= captionIndex + captionLineLocalLength) && (currIndex < caption.length()) && !breakLine; ++currIndex) { breakLine = (caption[currIndex] == QLatin1Char('\n')) ? true : false; if (breakLine) newLine.append(QLatin1Char(' ')); else newLine.append(caption[currIndex]); } captionIndex = currIndex; // The line is ended if (captionIndex != caption.length()) { while (!newLine.endsWith(QLatin1Char(' '))) { newLine.truncate(newLine.length() - 1); captionIndex--; } } captionByLines.prepend(newLine.trimmed()); } QFont font(photo->m_pAdvPrintCaptionInfo->m_captionFont); font.setStyleHint(QFont::SansSerif); font.setPixelSize((int)(captionH * 0.8F)); // Font height ratio font.setWeight(QFont::Normal); QFontMetrics fm(font); int pixelsHigh = fm.height(); p.setFont(font); p.setPen(photo->m_pAdvPrintCaptionInfo->m_captionColor); qCDebug(DIGIKAM_GENERAL_LOG) << "Number of lines " << (int) captionByLines.count() ; // Now draw the caption // TODO allow printing captions per photo and on top, bottom and vertically for (int lineNumber = 0 ; lineNumber < (int) captionByLines.count() ; ++lineNumber) { if (lineNumber > 0) { p.translate(0, - (int)(pixelsHigh)); } QRect r(0, 0, captionW, captionH); p.drawText(r, Qt::AlignLeft, captionByLines[lineNumber], &r); } } } // namespace Digikam diff --git a/core/utilities/assistants/printcreator/wizard/advprintphotopage.cpp b/core/utilities/assistants/printcreator/wizard/advprintphotopage.cpp index 0d310b0994..4041ef1b22 100644 --- a/core/utilities/assistants/printcreator/wizard/advprintphotopage.cpp +++ b/core/utilities/assistants/printcreator/wizard/advprintphotopage.cpp @@ -1,1516 +1,1516 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2017-05-25 * Description : a tool to print images * * Copyright (C) 2017-2018 by Gilles Caulier * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "advprintphotopage.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include #include // Local includes #include "digikam_debug.h" #include "advprintwizard.h" #include "advprintcustomdlg.h" #include "templateicon.h" namespace Digikam { static const char* const CUSTOM_PAGE_LAYOUT_NAME = I18N_NOOP("Custom"); class AdvPrintPhotoPage::Private { public: template class WizardUI : public QWidget, public Ui_Class { public: explicit WizardUI(QWidget* const parent) : QWidget(parent) { this->setupUi(this); } }; typedef WizardUI PhotoUI; public: explicit Private(QWizard* const dialog) : pageSetupDlg(0), printer(0), wizard(0), settings(0), iface(0) { photoUi = new PhotoUI(dialog); wizard = dynamic_cast(dialog); if (wizard) { settings = wizard->settings(); iface = wizard->iface(); } } PhotoUI* photoUi; QPageSetupDialog* pageSetupDlg; QPrinter* printer; QList printerList; AdvPrintWizard* wizard; AdvPrintSettings* settings; DInfoInterface* iface; }; AdvPrintPhotoPage::AdvPrintPhotoPage(QWizard* const wizard, const QString& title) : DWizardPage(wizard, title), d(new Private(wizard)) { d->photoUi->BtnPreviewPageUp->setIcon(QIcon::fromTheme(QLatin1String("go-next")) .pixmap(16, 16)); d->photoUi->BtnPreviewPageDown->setIcon(QIcon::fromTheme(QLatin1String("go-previous")) .pixmap(16, 16)); // ---------------------- d->photoUi->m_printer_choice->setEditable(false); d->photoUi->m_printer_choice->setWhatsThis(i18n("Select your preferred print output.")); // Populate hardcoded printers QMap map2 = AdvPrintSettings::outputNames(); QMap::const_iterator it2 = map2.constBegin(); while (it2 != map2.constEnd()) { d->photoUi->m_printer_choice->addSqueezedItem(it2.value(), (int)it2.key()); ++it2; } // Populate real printers d->printerList = QPrinterInfo::availablePrinters(); for (QList::iterator it = d->printerList.begin() ; it != d->printerList.end() ; ++it) { d->photoUi->m_printer_choice->addSqueezedItem(it->printerName()); } connect(d->photoUi->m_printer_choice, SIGNAL(activated(QString)), this, SLOT(slotOutputChanged(QString))); connect(d->photoUi->BtnPreviewPageUp, SIGNAL(clicked()), this, SLOT(slotBtnPreviewPageUpClicked())); connect(d->photoUi->BtnPreviewPageDown, SIGNAL(clicked()), this, SLOT(slotBtnPreviewPageDownClicked())); connect(d->photoUi->ListPhotoSizes, SIGNAL(currentRowChanged(int)), this, SLOT(slotListPhotoSizesSelected())); connect(d->photoUi->m_pagesetup, SIGNAL(clicked()), this, SLOT(slotPageSetup())); if (d->photoUi->mPrintList->layout()) { delete d->photoUi->mPrintList->layout(); } // ----------------------------------- d->photoUi->mPrintList->setIface(d->iface); d->photoUi->mPrintList->setAllowDuplicate(true); d->photoUi->mPrintList->setControlButtons(DImagesList::Add | DImagesList::Remove | DImagesList::MoveUp | DImagesList::MoveDown | DImagesList::Clear | DImagesList::Save | DImagesList::Load); d->photoUi->mPrintList->setControlButtonsPlacement(DImagesList::ControlButtonsAbove); d->photoUi->mPrintList->enableDragAndDrop(false); d->photoUi->BmpFirstPagePreview->setAlignment(Qt::AlignHCenter); connect(d->photoUi->mPrintList, SIGNAL(signalMoveDownItem()), this, SLOT(slotBtnPrintOrderDownClicked())); connect(d->photoUi->mPrintList, SIGNAL(signalMoveUpItem()), this, SLOT(slotBtnPrintOrderUpClicked())); connect(d->photoUi->mPrintList, SIGNAL(signalAddItems(QList)), this, SLOT(slotAddItems(QList))); connect(d->photoUi->mPrintList, SIGNAL(signalRemovedItems(QList)), this, SLOT(slotRemovingItems(QList))); connect(d->photoUi->mPrintList, SIGNAL(signalContextMenuRequested()), this, SLOT(slotContextMenuRequested())); connect(d->photoUi->mPrintList, SIGNAL(signalXMLSaveItem(QXmlStreamWriter&,int)), this, SLOT(slotXMLSaveItem(QXmlStreamWriter&,int))); connect(d->photoUi->mPrintList, SIGNAL(signalXMLCustomElements(QXmlStreamWriter&)), this, SLOT(slotXMLCustomElement(QXmlStreamWriter&))); connect(d->photoUi->mPrintList, SIGNAL(signalXMLLoadImageElement(QXmlStreamReader&)), this, SLOT(slotXMLLoadElement(QXmlStreamReader&))); connect(d->photoUi->mPrintList, SIGNAL(signalXMLCustomElements(QXmlStreamReader&)), this, SLOT(slotXMLCustomElement(QXmlStreamReader&))); // ----------------------------------- setPageWidget(d->photoUi); setLeftBottomPix(QIcon::fromTheme(QLatin1String("image-stack"))); slotOutputChanged(d->photoUi->m_printer_choice->itemHighlighted()); } AdvPrintPhotoPage::~AdvPrintPhotoPage() { delete d->printer; delete d->pageSetupDlg; delete d; } void AdvPrintPhotoPage::initializePage() { d->photoUi->mPrintList->listView()->clear(); if (d->settings->selMode == AdvPrintSettings::IMAGES) { d->photoUi->mPrintList->loadImagesFromCurrentSelection(); } else { d->wizard->setItemsList(d->settings->inputImages); } initPhotoSizes(d->printer->paperSize(QPrinter::Millimeter)); // restore photoSize if (d->settings->savedPhotoSize == i18n(CUSTOM_PAGE_LAYOUT_NAME)) { d->photoUi->ListPhotoSizes->setCurrentRow(0); } else { QList list = d->photoUi->ListPhotoSizes->findItems(d->settings->savedPhotoSize, Qt::MatchExactly); if (list.count()) d->photoUi->ListPhotoSizes->setCurrentItem(list[0]); else d->photoUi->ListPhotoSizes->setCurrentRow(0); } // reset preview page number d->settings->currentPreviewPage = 0; // create our photo sizes list d->wizard->previewPhotos(); int gid = d->photoUi->m_printer_choice->findText(d->settings->outputName(AdvPrintSettings::GIMP)); if (d->settings->gimpPath.isEmpty()) { // Gimp is not available : we disable the option. d->photoUi->m_printer_choice->setItemData(gid, false, Qt::UserRole-1); } int index = d->photoUi->m_printer_choice->findText(d->settings->printerName); if (index != -1) { d->photoUi->m_printer_choice->setCurrentIndex(index); } slotOutputChanged(d->photoUi->m_printer_choice->itemHighlighted()); d->photoUi->ListPhotoSizes->setIconSize(QSize(32, 32)); initPhotoSizes(d->printer->paperSize(QPrinter::Millimeter)); } bool AdvPrintPhotoPage::validatePage() { d->settings->inputImages = d->photoUi->mPrintList->imageUrls(); d->settings->printerName = d->photoUi->m_printer_choice->itemHighlighted(); if (d->photoUi->ListPhotoSizes->currentItem()) { d->settings->savedPhotoSize = d->photoUi->ListPhotoSizes->currentItem()->text(); } return true; } bool AdvPrintPhotoPage::isComplete() const { return (!d->photoUi->mPrintList->imageUrls().isEmpty() || !d->wizard->itemsList().isEmpty()); } QPrinter* AdvPrintPhotoPage::printer() const { return d->printer; } DImagesList* AdvPrintPhotoPage::imagesList() const { return d->photoUi->mPrintList; } Ui_AdvPrintPhotoPage* AdvPrintPhotoPage::ui() const { return d->photoUi; } void AdvPrintPhotoPage::slotOutputChanged(const QString& text) { if (AdvPrintSettings::outputNames().values().contains(text)) { delete d->printer; d->printer = new QPrinter(); d->printer->setOutputFormat(QPrinter::PdfFormat); } else // real printer { for (QList::iterator it = d->printerList.begin() ; it != d->printerList.end() ; ++it) { if (it->printerName() == text) { qCDebug(DIGIKAM_GENERAL_LOG) << "Chosen printer: " << it->printerName(); delete d->printer; d->printer = new QPrinter(*it); } } d->printer->setOutputFormat(QPrinter::NativeFormat); } // default no margins d->printer->setFullPage(true); d->printer->setPageMargins(0, 0, 0, 0, QPrinter::Millimeter); } void AdvPrintPhotoPage::slotXMLLoadElement(QXmlStreamReader& xmlReader) { if (d->settings->photos.size()) { // read image is the last. AdvPrintPhoto* const pPhoto = d->settings->photos[d->settings->photos.size()-1]; qCDebug(DIGIKAM_GENERAL_LOG) << " invoked " << xmlReader.name(); while (xmlReader.readNextStartElement()) { qCDebug(DIGIKAM_GENERAL_LOG) << pPhoto->m_url << " " << xmlReader.name(); if (xmlReader.name() == QLatin1String("pa_caption")) { //useless this item has been added now if (pPhoto->m_pAdvPrintCaptionInfo) delete pPhoto->m_pAdvPrintCaptionInfo; pPhoto->m_pAdvPrintCaptionInfo = new AdvPrintCaptionInfo(); // get all attributes and its value of a tag in attrs variable. QXmlStreamAttributes attrs = xmlReader.attributes(); // get value of each attribute from QXmlStreamAttributes QStringRef attr = attrs.value(QLatin1String("type")); bool ok; if (!attr.isEmpty()) { qCDebug(DIGIKAM_GENERAL_LOG) << " found " << attr.toString(); pPhoto->m_pAdvPrintCaptionInfo->m_captionType = (AdvPrintSettings::CaptionType)attr.toString().toInt(&ok); } attr = attrs.value(QLatin1String("font")); if (!attr.isEmpty()) { qCDebug(DIGIKAM_GENERAL_LOG) << " found " << attr.toString(); pPhoto->m_pAdvPrintCaptionInfo->m_captionFont.fromString(attr.toString()); } attr = attrs.value(QLatin1String("color")); if (!attr.isEmpty()) { qCDebug(DIGIKAM_GENERAL_LOG) << " found " << attr.toString(); pPhoto->m_pAdvPrintCaptionInfo->m_captionColor.setNamedColor(attr.toString()); } attr = attrs.value(QLatin1String("size")); if (!attr.isEmpty()) { qCDebug(DIGIKAM_GENERAL_LOG) << " found " << attr.toString(); pPhoto->m_pAdvPrintCaptionInfo->m_captionSize = attr.toString().toInt(&ok); } attr = attrs.value(QLatin1String("text")); if (!attr.isEmpty()) { qCDebug(DIGIKAM_GENERAL_LOG) << " found " << attr.toString(); pPhoto->m_pAdvPrintCaptionInfo->m_captionText = attr.toString(); } } } } } void AdvPrintPhotoPage::slotXMLSaveItem(QXmlStreamWriter& xmlWriter, int itemIndex) { if (d->settings->photos.size()) { AdvPrintPhoto* const pPhoto = d->settings->photos[itemIndex]; // TODO: first and copies could be removed since they are not useful any more xmlWriter.writeAttribute(QLatin1String("first"), QString::fromUtf8("%1") .arg(pPhoto->m_first)); xmlWriter.writeAttribute(QLatin1String("copies"), QString::fromUtf8("%1") .arg(pPhoto->m_first ? pPhoto->m_copies : 0)); // additional info (caption... etc) if (pPhoto->m_pAdvPrintCaptionInfo) { xmlWriter.writeStartElement(QLatin1String("pa_caption")); xmlWriter.writeAttribute(QLatin1String("type"), QString::fromUtf8("%1").arg(pPhoto->m_pAdvPrintCaptionInfo->m_captionType)); xmlWriter.writeAttribute(QLatin1String("font"), pPhoto->m_pAdvPrintCaptionInfo->m_captionFont.toString()); xmlWriter.writeAttribute(QLatin1String("size"), QString::fromUtf8("%1").arg(pPhoto->m_pAdvPrintCaptionInfo->m_captionSize)); xmlWriter.writeAttribute(QLatin1String("color"), pPhoto->m_pAdvPrintCaptionInfo->m_captionColor.name()); xmlWriter.writeAttribute(QLatin1String("text"), pPhoto->m_pAdvPrintCaptionInfo->m_captionText); xmlWriter.writeEndElement(); // pa_caption } } } void AdvPrintPhotoPage::slotXMLCustomElement(QXmlStreamWriter& xmlWriter) { xmlWriter.writeStartElement(QLatin1String("pa_layout")); xmlWriter.writeAttribute(QLatin1String("Printer"), d->photoUi->m_printer_choice->itemHighlighted()); xmlWriter.writeAttribute(QLatin1String("PageSize"), QString::fromUtf8("%1").arg(d->printer->paperSize())); xmlWriter.writeAttribute(QLatin1String("PhotoSize"), d->photoUi->ListPhotoSizes->currentItem()->text()); xmlWriter.writeEndElement(); // pa_layout } void AdvPrintPhotoPage::slotContextMenuRequested() { if (d->settings->photos.size()) { int itemIndex = d->photoUi->mPrintList->listView()->currentIndex().row(); d->photoUi->mPrintList->listView()->blockSignals(true); QMenu menu(d->photoUi->mPrintList->listView()); QAction* const action = menu.addAction(i18n("Add again")); connect(action, SIGNAL(triggered()), this , SLOT(slotIncreaseCopies())); AdvPrintPhoto* const pPhoto = d->settings->photos[itemIndex]; qCDebug(DIGIKAM_GENERAL_LOG) << " copies " << pPhoto->m_copies << " first " << pPhoto->m_first; if (pPhoto->m_copies > 1 || !pPhoto->m_first) { QAction* const actionr = menu.addAction(i18n("Remove")); connect(actionr, SIGNAL(triggered()), this, SLOT(slotDecreaseCopies())); } menu.exec(QCursor::pos()); d->photoUi->mPrintList->listView()->blockSignals(false); } } void AdvPrintPhotoPage::slotIncreaseCopies() { if (d->settings->photos.size()) { QList list; DImagesListViewItem* const item = dynamic_cast(d->photoUi->mPrintList->listView()->currentItem()); if (!item) return; list.append(item->url()); qCDebug(DIGIKAM_GENERAL_LOG) << " Adding a copy of " << item->url(); d->photoUi->mPrintList->slotAddImages(list); } } void AdvPrintPhotoPage::slotDecreaseCopies() { if (d->settings->photos.size()) { DImagesListViewItem* const item = dynamic_cast (d->photoUi->mPrintList->listView()->currentItem()); if (!item) return; qCDebug(DIGIKAM_GENERAL_LOG) << " Removing a copy of " << item->url(); d->photoUi->mPrintList->slotRemoveItems(); } } void AdvPrintPhotoPage::slotAddItems(const QList& list) { if (list.count() == 0) { return; } QList urls; d->photoUi->mPrintList->blockSignals(true); for (QList::ConstIterator it = list.constBegin() ; it != list.constEnd() ; ++it) { QUrl imageUrl = *it; // Check if the new item already exist in the list. bool found = false; for (int i = 0 ; i < d->settings->photos.count() && !found ; ++i) { AdvPrintPhoto* const pCurrentPhoto = d->settings->photos.at(i); if (pCurrentPhoto && (pCurrentPhoto->m_url == imageUrl) && pCurrentPhoto->m_first) { pCurrentPhoto->m_copies++; found = true; AdvPrintPhoto* const pPhoto = new AdvPrintPhoto(*pCurrentPhoto); pPhoto->m_first = false; d->settings->photos.append(pPhoto); qCDebug(DIGIKAM_GENERAL_LOG) << "Added fileName: " << pPhoto->m_url.fileName() << " copy number " << pCurrentPhoto->m_copies; } } if (!found) { AdvPrintPhoto* const pPhoto = new AdvPrintPhoto(150, d->iface); pPhoto->m_url = *it; pPhoto->m_first = true; d->settings->photos.append(pPhoto); qCDebug(DIGIKAM_GENERAL_LOG) << "Added new fileName: " << pPhoto->m_url.fileName(); } } d->photoUi->mPrintList->blockSignals(false); d->photoUi->LblPhotoCount->setText(QString::number(d->settings->photos.count())); if (d->settings->photos.size()) { setComplete(true); } } void AdvPrintPhotoPage::slotRemovingItems(const QList& list) { if (list.count() == 0) { return; } d->photoUi->mPrintList->blockSignals(true); foreach (int itemIndex, list) { if (d->settings->photos.size() && itemIndex >= 0) { /// Debug data: found and copies bool found = false; int copies = 0; AdvPrintPhoto* const pPhotoToRemove = d->settings->photos.at(itemIndex); // photo to be removed could be: // 1) unique => just remove it // 2) first of n, => // search another with the same url // and set it a first and with a count to n-1 then remove it // 3) one of n, search the first one and set count to n-1 then remove it if (pPhotoToRemove && pPhotoToRemove->m_first) { if (pPhotoToRemove->m_copies > 0) { for (int i = 0 ; i < d->settings->photos.count() && !found ; ++i) { AdvPrintPhoto* const pCurrentPhoto = d->settings->photos.at(i); if (pCurrentPhoto && pCurrentPhoto->m_url == pPhotoToRemove->m_url) { pCurrentPhoto->m_copies = pPhotoToRemove->m_copies - 1; copies = pCurrentPhoto->m_copies; pCurrentPhoto->m_first = true; found = true; } } } // otherwise it's unique } else if (pPhotoToRemove) { for (int i = 0 ; i < d->settings->photos.count() && !found ; ++i) { AdvPrintPhoto* const pCurrentPhoto = d->settings->photos.at(i); if (pCurrentPhoto && pCurrentPhoto->m_url == pPhotoToRemove->m_url && pCurrentPhoto->m_first) { pCurrentPhoto->m_copies--; copies = pCurrentPhoto->m_copies; found = true; } } } else { qCDebug(DIGIKAM_GENERAL_LOG) << " NULL AdvPrintPhoto object "; return; } if (pPhotoToRemove) { qCDebug(DIGIKAM_GENERAL_LOG) << "Removed fileName: " << pPhotoToRemove->m_url.fileName() << " copy number " << copies; } d->settings->photos.removeAt(itemIndex); delete pPhotoToRemove; } } d->wizard->previewPhotos(); d->photoUi->mPrintList->blockSignals(false); d->photoUi->LblPhotoCount->setText(QString::number(d->settings->photos.count())); if (d->settings->photos.isEmpty()) { // No photos => disabling next button (e.g. crop page) setComplete(false); } } void AdvPrintPhotoPage::slotBtnPrintOrderDownClicked() { d->photoUi->mPrintList->blockSignals(true); int currentIndex = d->photoUi->mPrintList->listView()->currentIndex().row(); qCDebug(DIGIKAM_GENERAL_LOG) << "Moved photo " << currentIndex - 1 << " to " << currentIndex; d->settings->photos.swap(currentIndex, currentIndex - 1); d->photoUi->mPrintList->blockSignals(false); d->wizard->previewPhotos(); } void AdvPrintPhotoPage::slotBtnPrintOrderUpClicked() { d->photoUi->mPrintList->blockSignals(true); int currentIndex = d->photoUi->mPrintList->listView()->currentIndex().row(); qCDebug(DIGIKAM_GENERAL_LOG) << "Moved photo " << currentIndex << " to " << currentIndex + 1; d->settings->photos.swap(currentIndex, currentIndex + 1); d->photoUi->mPrintList->blockSignals(false); d->wizard->previewPhotos(); } void AdvPrintPhotoPage::slotXMLCustomElement(QXmlStreamReader& xmlReader) { qCDebug(DIGIKAM_GENERAL_LOG) << " invoked " << xmlReader.name(); while (!xmlReader.atEnd()) { if (xmlReader.isStartElement() && xmlReader.name() == QLatin1String("pa_layout")) { bool ok; QXmlStreamAttributes attrs = xmlReader.attributes(); // get value of each attribute from QXmlStreamAttributes QStringRef attr = attrs.value(QLatin1String("Printer")); if (!attr.isEmpty()) { qCDebug(DIGIKAM_GENERAL_LOG) << " found " << attr.toString(); int index = d->photoUi->m_printer_choice->findText(attr.toString()); if (index != -1) { d->photoUi->m_printer_choice->setCurrentIndex(index); } slotOutputChanged(d->photoUi->m_printer_choice->itemHighlighted()); } attr = attrs.value(QLatin1String("PageSize")); if (!attr.isEmpty()) { qCDebug(DIGIKAM_GENERAL_LOG) << " found " << attr.toString(); QPrinter::PaperSize paperSize = (QPrinter::PaperSize)attr.toString().toInt(&ok); d->printer->setPaperSize(paperSize); } attr = attrs.value(QLatin1String("PhotoSize")); if (!attr.isEmpty()) { qCDebug(DIGIKAM_GENERAL_LOG) << " found " << attr.toString(); d->settings->savedPhotoSize = attr.toString(); } } xmlReader.readNext(); } // reset preview page number d->settings->currentPreviewPage = 0; initPhotoSizes(d->printer->paperSize(QPrinter::Millimeter)); QList list = d->photoUi->ListPhotoSizes->findItems(d->settings->savedPhotoSize, Qt::MatchExactly); if (list.count()) { qCDebug(DIGIKAM_GENERAL_LOG) << " PhotoSize " << list[0]->text(); d->photoUi->ListPhotoSizes->setCurrentItem(list[0]); } else { d->photoUi->ListPhotoSizes->setCurrentRow(0); } d->wizard->previewPhotos(); } void AdvPrintPhotoPage::slotBtnPreviewPageDownClicked() { if (d->settings->currentPreviewPage == 0) return; d->settings->currentPreviewPage--; d->wizard->previewPhotos(); } void AdvPrintPhotoPage::slotBtnPreviewPageUpClicked() { if (d->settings->currentPreviewPage == getPageCount() - 1) return; d->settings->currentPreviewPage++; d->wizard->previewPhotos(); } int AdvPrintPhotoPage::getPageCount() const { int pageCount = 0; int photoCount = d->settings->photos.count(); if (photoCount > 0) { // get the selected layout AdvPrintPhotoSize* const s = d->settings->photosizes.at(d->photoUi->ListPhotoSizes->currentRow()); // how many pages? Recall that the first layout item is the paper size int photosPerPage = s->m_layouts.count() - 1; int remainder = photoCount % photosPerPage; int emptySlots = 0; if (remainder > 0) emptySlots = photosPerPage - remainder; pageCount = photoCount / photosPerPage; if (emptySlots > 0) pageCount++; } return pageCount; } void AdvPrintPhotoPage::createPhotoGrid(AdvPrintPhotoSize* const p, int pageWidth, int pageHeight, int rows, int columns, TemplateIcon* const iconpreview) { int MARGIN = (int)(((double)pageWidth + (double)pageHeight) / 2.0 * 0.04 + 0.5); int GAP = MARGIN / 4; int photoWidth = (pageWidth - (MARGIN * 2) - ((columns - 1) * GAP)) / columns; int photoHeight = (pageHeight - (MARGIN * 2) - ((rows - 1) * GAP)) / rows; int row = 0; for (int y = MARGIN ; (row < rows) && (y < (pageHeight - MARGIN)) ; y += photoHeight + GAP) { int col = 0; for (int x = MARGIN ; (col < columns) && (x < (pageWidth - MARGIN)) ; x += photoWidth + GAP) { p->m_layouts.append(new QRect(x, y, photoWidth, photoHeight)); iconpreview->fillRect(x, y, photoWidth, photoHeight, Qt::color1); col++; } row++; } } void AdvPrintPhotoPage::slotListPhotoSizesSelected() { AdvPrintPhotoSize* s = 0; QSizeF size, sizeManaged; // TODO FREE STYLE // check if layout is managed by templates or free one // get the selected layout int curr = d->photoUi->ListPhotoSizes->currentRow(); QListWidgetItem* item = d->photoUi->ListPhotoSizes->item(curr); // if custom page layout we launch a dialog to choose what kind if (item->text() == i18n(CUSTOM_PAGE_LAYOUT_NAME)) { // check if a custom layout has already been added if (curr >= 0 && curr < d->settings->photosizes.size()) { s = d->settings->photosizes.at(curr); d->settings->photosizes.removeAt(curr); delete s; s = NULL; } QPointer custDlg = new AdvPrintCustomLayoutDlg(this); custDlg->readSettings(); custDlg->exec(); custDlg->saveSettings(); // get parameters from dialog size = d->settings->pageSize; int scaleValue = 10; // 0.1 mm // convert to mm if (custDlg->m_photoUnits->currentText() == i18n("inches")) { size /= 25.4; scaleValue = 1000; } else if (custDlg->m_photoUnits->currentText() == i18n("cm")) { size /= 10; scaleValue = 100; } sizeManaged = size * scaleValue; s = new AdvPrintPhotoSize; TemplateIcon iconpreview(80, sizeManaged.toSize()); iconpreview.begin(); if (custDlg->m_photoGridCheck->isChecked()) { // custom photo grid int rows = custDlg->m_gridRows->value(); int columns = custDlg->m_gridColumns->value(); s->m_layouts.append(new QRect(0, 0, (int)sizeManaged.width(), (int)sizeManaged.height())); s->m_autoRotate = custDlg->m_autorotate->isChecked(); s->m_label = item->text(); s->m_dpi = 0; int pageWidth = (int)(size.width()) * scaleValue; int pageHeight = (int)(size.height()) * scaleValue; createPhotoGrid(s, pageWidth, pageHeight, rows, columns, &iconpreview); } else if (custDlg->m_fitAsManyCheck->isChecked()) { int width = custDlg->m_photoWidth->value(); int height = custDlg->m_photoHeight->value(); //photo size must be less than page size static const float round_value = 0.01F; if ((height > (size.height() + round_value) || width > (size.width() + round_value))) { qCDebug(DIGIKAM_GENERAL_LOG) << "photo size " << QSize(width, height) << "> page size " << size; delete s; s = NULL; } else { // fit as many photos of given size as possible s->m_layouts.append(new QRect(0, 0, (int)sizeManaged.width(), (int)sizeManaged.height())); s->m_autoRotate = custDlg->m_autorotate->isChecked(); s->m_label = item->text(); s->m_dpi = 0; int nColumns = int(size.width() / width); int nRows = int(size.height() / height); int spareWidth = int(size.width()) % width; // check if there's no room left to separate photos if (nColumns > 1 && spareWidth == 0) { nColumns -= 1; spareWidth = width; } int spareHeight = int(size.height()) % height; // check if there's no room left to separate photos if (nRows > 1 && spareHeight == 0) { nRows -= 1; spareHeight = height; } if (nRows > 0 && nColumns > 0) { // n photos => dx1, photo1, dx2, photo2,... photoN, dxN+1 int dx = spareWidth * scaleValue / (nColumns + 1); int dy = spareHeight * scaleValue / (nRows + 1); int photoX = 0; int photoY = 0; width *= scaleValue; height *= scaleValue; for (int row = 0 ; row < nRows ; ++row) { photoY = dy * (row + 1) + (row * height); for (int col = 0 ; col < nColumns ; ++col) { photoX = dx * (col + 1) + (col * width); qCDebug(DIGIKAM_GENERAL_LOG) << "photo at P(" << photoX << ", " << photoY << ") size(" << width << ", " << height; s->m_layouts.append(new QRect(photoX, photoY, width, height)); iconpreview.fillRect(photoX, photoY, width, height, Qt::color1); } } } else { qCDebug(DIGIKAM_GENERAL_LOG) << "I cannot go on, rows " << nRows << "> columns " << nColumns; delete s; s = NULL; } } } else { // Atckin's layout } // TODO not for Atckin's layout iconpreview.end(); if (s) { s->m_icon = iconpreview.getIcon(); d->settings->photosizes.append(s); } delete custDlg; } else { s = d->settings->photosizes.at(curr); } if (!s) { // change position to top d->photoUi->ListPhotoSizes->blockSignals(true); d->photoUi->ListPhotoSizes->setCurrentRow(0, QItemSelectionModel::Select); d->photoUi->ListPhotoSizes->blockSignals(false); } // reset preview page number d->settings->currentPreviewPage = 0; d->wizard->previewPhotos(); } void AdvPrintPhotoPage::slotPageSetup() { delete d->pageSetupDlg; QString lastSize = d->photoUi->ListPhotoSizes->currentItem()->text(); d->pageSetupDlg = new QPageSetupDialog(d->printer, this); int ret = d->pageSetupDlg->exec(); if (ret == QDialog::Accepted) { QPrinter* const printer = d->pageSetupDlg->printer(); qCDebug(DIGIKAM_GENERAL_LOG) << "Dialog exit, new size " << printer->paperSize(QPrinter::Millimeter) << " internal size " << d->printer->paperSize(QPrinter::Millimeter); qreal left, top, right, bottom; d->printer->getPageMargins(&left, &top, &right, &bottom, QPrinter::Millimeter); qCDebug(DIGIKAM_GENERAL_LOG) << "Dialog exit, new margins: left " << left << " right " << right << " top " << top << " bottom " << bottom; // next should be useless invoke once changing wizard page //d->wizard->initPhotoSizes(d->printer.paperSize(QPrinter::Millimeter)); //d->settings->pageSize = d->printer.paperSize(QPrinter::Millimeter); #ifdef DEBUG qCDebug(DIGIKAM_GENERAL_LOG) << " dialog exited num of copies: " << printer->numCopies() << " inside: " << d->printer->numCopies(); qCDebug(DIGIKAM_GENERAL_LOG) << " dialog exited from : " << printer->fromPage() << " to: " << d->printer->toPage(); #endif } // Fix the page size dialog and preview PhotoPage initPhotoSizes(d->printer->paperSize(QPrinter::Millimeter)); // restore photoSize if (d->settings->savedPhotoSize == i18n(CUSTOM_PAGE_LAYOUT_NAME)) { d->photoUi->ListPhotoSizes->setCurrentRow(0); } else { QList list = d->photoUi->ListPhotoSizes->findItems(lastSize, Qt::MatchExactly); if (list.count()) d->photoUi->ListPhotoSizes->setCurrentItem(list[0]); else d->photoUi->ListPhotoSizes->setCurrentRow(0); } // create our photo sizes list d->wizard->previewPhotos(); } void AdvPrintPhotoPage::manageBtnPreviewPage() { if (d->settings->photos.isEmpty()) { d->photoUi->BtnPreviewPageDown->setEnabled(false); d->photoUi->BtnPreviewPageUp->setEnabled(false); } else { d->photoUi->BtnPreviewPageDown->setEnabled(true); d->photoUi->BtnPreviewPageUp->setEnabled(true); if (d->settings->currentPreviewPage == 0) { d->photoUi->BtnPreviewPageDown->setEnabled(false); } if ((d->settings->currentPreviewPage + 1) == getPageCount()) { d->photoUi->BtnPreviewPageUp->setEnabled(false); } } } void AdvPrintPhotoPage::initPhotoSizes(const QSizeF& pageSize) { qCDebug(DIGIKAM_GENERAL_LOG) << "New page size " << pageSize << ", old page size " << d->settings->pageSize; // don't refresh anything if we haven't changed page sizes. if (pageSize == d->settings->pageSize) return; d->settings->pageSize = pageSize; // cleaning pageSize memory before invoking clear() for (int i = 0 ; i < d->settings->photosizes.count() ; ++i) delete d->settings->photosizes.at(i); d->settings->photosizes.clear(); // get template-files and parse them QDir dir(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("digikam/templates"), QStandardPaths::LocateDirectory)); const QStringList list = dir.entryList(QStringList() << QLatin1String("*.xml")); qCDebug(DIGIKAM_GENERAL_LOG) << "Template XML files list: " << list; foreach(const QString& fn, list) { - parseTemplateFile(dir.absolutePath() + QLatin1String("/") + fn, pageSize); + parseTemplateFile(dir.absolutePath() + QLatin1Char('/') + fn, pageSize); } qCDebug(DIGIKAM_GENERAL_LOG) << "photosizes count() =" << d->settings->photosizes.count(); qCDebug(DIGIKAM_GENERAL_LOG) << "photosizes isEmpty() =" << d->settings->photosizes.isEmpty(); if (d->settings->photosizes.isEmpty()) { qCDebug(DIGIKAM_GENERAL_LOG) << "Empty photoSize-list, create default size"; // There is no valid page size yet. Create a default page (B10) to prevent crashes. AdvPrintPhotoSize* const p = new AdvPrintPhotoSize; // page size: B10 (32 x 45 mm) p->m_layouts.append(new QRect(0, 0, 3200, 4500)); p->m_layouts.append(new QRect(0, 0, 3200, 4500)); // add to the list d->settings->photosizes.append(p); } // load the photo sizes into the listbox d->photoUi->ListPhotoSizes->blockSignals(true); d->photoUi->ListPhotoSizes->clear(); QList::iterator it; for (it = d->settings->photosizes.begin() ; it != d->settings->photosizes.end() ; ++it) { AdvPrintPhotoSize* const s = static_cast(*it); if (s) { QListWidgetItem* const pWItem = new QListWidgetItem(s->m_label); pWItem->setIcon(s->m_icon); d->photoUi->ListPhotoSizes->addItem(pWItem); } } // Adding custom choice QListWidgetItem* const pWItem = new QListWidgetItem(i18n(CUSTOM_PAGE_LAYOUT_NAME)); TemplateIcon ti(80, pageSize.toSize()); ti.begin(); QPainter& painter = ti.getPainter(); painter.setPen(Qt::color1); painter.drawText(painter.viewport(), Qt::AlignCenter, i18n("Custom\nlayout")); ti.end(); pWItem->setIcon(ti.getIcon()); d->photoUi->ListPhotoSizes->addItem(pWItem); d->photoUi->ListPhotoSizes->blockSignals(false); d->photoUi->ListPhotoSizes->setCurrentRow(0, QItemSelectionModel::Select); } void AdvPrintPhotoPage::parseTemplateFile(const QString& fn, const QSizeF& pageSize) { QDomDocument doc(QLatin1String("mydocument")); qCDebug(DIGIKAM_GENERAL_LOG) << " XXX: " << fn; if (fn.isEmpty()) { return; } QFile file(fn); if (!file.open(QIODevice::ReadOnly)) return; if (!doc.setContent(&file)) { file.close(); return; } file.close(); AdvPrintPhotoSize* p = 0; // print out the element names of all elements that are direct children // of the outermost element. QDomElement docElem = doc.documentElement(); qCDebug(DIGIKAM_GENERAL_LOG) << docElem.tagName(); // the node really is an element. QSizeF size; QString unit; int scaleValue; QDomNode n = docElem.firstChild(); while (!n.isNull()) { size = QSizeF(0, 0); scaleValue = 10; // 0.1 mm QDomElement e = n.toElement(); // try to convert the node to an element. if (!e.isNull()) { if (e.tagName() == QLatin1String("paper")) { size = QSizeF(e.attribute(QLatin1String("width"), QLatin1String("0")).toFloat(), e.attribute(QLatin1String("height"), QLatin1String("0")).toFloat()); unit = e.attribute(QLatin1String("unit"), QLatin1String("mm")); qCDebug(DIGIKAM_GENERAL_LOG) << e.tagName() << QLatin1String(" name=") << e.attribute(QLatin1String("name"), QLatin1String("??")) << " size= " << size << " unit= " << unit; if (size == QSizeF(0.0, 0.0) && size == pageSize) { // skipping templates without page size since pageSize is not set n = n.nextSibling(); continue; } else if (unit != QLatin1String("mm") && size != QSizeF(0.0, 0.0)) // "cm", "inches" or "inch" { // convert to mm if (unit == QLatin1String("inches") || unit == QLatin1String("inch")) { size *= 25.4; scaleValue = 1000; qCDebug(DIGIKAM_GENERAL_LOG) << "template size " << size << " page size " << pageSize; } else if (unit == QLatin1String("cm")) { size *= 10; scaleValue = 100; qCDebug(DIGIKAM_GENERAL_LOG) << "template size " << size << " page size " << pageSize; } else { qCWarning(DIGIKAM_GENERAL_LOG) << "Wrong unit " << unit << " skipping layout"; n = n.nextSibling(); continue; } } static const float round_value = 0.01F; if (size == QSizeF(0, 0)) { size = pageSize; unit = QLatin1String("mm"); } else if (pageSize != QSizeF(0, 0) && (size.height() > (pageSize.height() + round_value) || size.width() > (pageSize.width() + round_value))) { qCDebug(DIGIKAM_GENERAL_LOG) << "skipping size " << size << " page size " << pageSize; // skipping layout it can't fit n = n.nextSibling(); continue; } // Next templates are good qCDebug(DIGIKAM_GENERAL_LOG) << "layout size " << size << " page size " << pageSize; QDomNode np = e.firstChild(); while (!np.isNull()) { QDomElement ep = np.toElement(); // try to convert the node to an element. if (!ep.isNull()) { if (ep.tagName() == QLatin1String("template")) { p = new AdvPrintPhotoSize; QSizeF sizeManaged; // set page size if (pageSize == QSizeF(0, 0)) { sizeManaged = size * scaleValue; } else if (unit == QLatin1String("inches") || unit == QLatin1String("inch")) { sizeManaged = pageSize * scaleValue / 25.4; } else { sizeManaged = pageSize * 10; } p->m_layouts.append(new QRect(0, 0, (int)sizeManaged.width(), (int)sizeManaged.height())); // create a small preview of the template TemplateIcon iconpreview(80, sizeManaged.toSize()); iconpreview.begin(); QString desktopFileName = ep.attribute(QLatin1String("name"), QLatin1String("XXX")) + QLatin1String(".desktop"); QDir dir(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("digikam/templates"), QStandardPaths::LocateDirectory)); const QStringList list = dir.entryList(QStringList() << desktopFileName); qCDebug(DIGIKAM_GENERAL_LOG) << "Template desktop files list: " << list; QStringList::ConstIterator it = list.constBegin(); QStringList::ConstIterator end = list.constEnd(); if (it != end) { p->m_label = KDesktopFile(dir.absolutePath() + QLatin1String("/") + *it).readName(); } else { p->m_label = ep.attribute(QLatin1String("name"), QLatin1String("XXX")); qCWarning(DIGIKAM_GENERAL_LOG) << "missed template translation " << desktopFileName; } p->m_dpi = ep.attribute(QLatin1String("dpi"), QLatin1String("0")).toInt(); p->m_autoRotate = (ep.attribute(QLatin1String("autorotate"), QLatin1String("false")) == QLatin1String("true")) ? true : false; QDomNode nt = ep.firstChild(); while (!nt.isNull()) { QDomElement et = nt.toElement(); // try to convert the node to an element. if (!et.isNull()) { if (et.tagName() == QLatin1String("photo")) { float value = et.attribute(QLatin1String("width"), QLatin1String("0")).toFloat(); int width = (int)((value == 0 ? size.width() : value) * scaleValue); value = et.attribute(QLatin1String("height"), QLatin1String("0")).toFloat(); int height = (int)((value == 0 ? size.height() : value) * scaleValue); int photoX = (int)((et.attribute(QLatin1String("x"), QLatin1String("0")).toFloat() * scaleValue)); int photoY = (int)((et.attribute(QLatin1String("y"), QLatin1String("0")).toFloat() * scaleValue)); p->m_layouts.append(new QRect(photoX, photoY, width, height)); iconpreview.fillRect(photoX, photoY, width, height, Qt::color1); } else if (et.tagName() == QLatin1String("photogrid")) { float value = et.attribute(QLatin1String("pageWidth"), QLatin1String("0")).toFloat(); int pageWidth = (int)((value == 0 ? size.width() : value) * scaleValue); value = et.attribute(QLatin1String("pageHeight"), QLatin1String("0")).toFloat(); int pageHeight = (int)((value == 0 ? size.height() : value) * scaleValue); int rows = et.attribute(QLatin1String("rows"), QLatin1String("0")).toInt(); int columns = et.attribute(QLatin1String("columns"), QLatin1String("0")).toInt(); if (rows > 0 && columns > 0) { createPhotoGrid(p, pageWidth, pageHeight, rows, columns, &iconpreview); } else { qCWarning(DIGIKAM_GENERAL_LOG) << " Wrong grid configuration, rows " << rows << ", columns " << columns; } } else { qCDebug(DIGIKAM_GENERAL_LOG) << " " << et.tagName(); } } nt = nt.nextSibling(); } iconpreview.end(); p->m_icon = iconpreview.getIcon(); d->settings->photosizes.append(p); } else { qCDebug(DIGIKAM_GENERAL_LOG) << "? " << ep.tagName() << " attr=" << ep.attribute(QLatin1String("name"), QLatin1String("??")); } } np = np.nextSibling(); } } else { qCDebug(DIGIKAM_GENERAL_LOG) << "??" << e.tagName() << " name=" << e.attribute(QLatin1String("name"), QLatin1String("??")); } } n = n.nextSibling(); } } } // namespace Digikam diff --git a/core/utilities/assistants/webservices/dropbox/dbwindow.cpp b/core/utilities/assistants/webservices/dropbox/dbwindow.cpp index 77fe3b130b..50d8dca9fd 100644 --- a/core/utilities/assistants/webservices/dropbox/dbwindow.cpp +++ b/core/utilities/assistants/webservices/dropbox/dbwindow.cpp @@ -1,463 +1,463 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2013-11-18 * Description : a tool to export images to Dropbox web service * * Copyright (C) 2013 by Pankaj Kumar * Copyright (C) 2013-2018 by Gilles Caulier * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "dbwindow.h" // Qt includes #include #include #include #include #include // KDE includes #include #include #include // Local includes #include "digikam_debug.h" #include "dimageslist.h" #include "digikam_version.h" #include "dbtalker.h" #include "dbitem.h" #include "dbnewalbumdlg.h" #include "dbwidget.h" namespace Digikam { class DBWindow::Private { public: explicit Private() { imagesCount = 0; imagesTotal = 0; widget = 0; albumDlg = 0; talker = 0; } unsigned int imagesCount; unsigned int imagesTotal; DBWidget* widget; DBNewAlbumDlg* albumDlg; DBTalker* talker; QString currentAlbumName; QList transferQueue; }; DBWindow::DBWindow(DInfoInterface* const iface, QWidget* const /*parent*/) : WSToolDialog(0), d(new Private) { d->widget = new DBWidget(this, iface, QLatin1String("Dropbox")); d->widget->imagesList()->setIface(iface); setMainWidget(d->widget); setModal(false); setWindowTitle(i18n("Export to Dropbox")); startButton()->setText(i18n("Start Upload")); startButton()->setToolTip(i18n("Start upload to Dropbox")); d->widget->setMinimumSize(700, 500); connect(d->widget->imagesList(), SIGNAL(signalImageListChanged()), this, SLOT(slotImageListChanged())); connect(d->widget->getChangeUserBtn(), SIGNAL(clicked()), this, SLOT(slotUserChangeRequest())); connect(d->widget->getNewAlbmBtn(), SIGNAL(clicked()), this, SLOT(slotNewAlbumRequest())); connect(d->widget->getReloadBtn(), SIGNAL(clicked()), this, SLOT(slotReloadAlbumsRequest())); connect(startButton(), SIGNAL(clicked()), this, SLOT(slotStartTransfer())); d->albumDlg = new DBNewAlbumDlg(this, QLatin1String("Dropbox")); d->talker = new DBTalker(this); connect(d->talker, SIGNAL(signalBusy(bool)), this, SLOT(slotBusy(bool))); connect(d->talker, SIGNAL(signalLinkingFailed()), this, SLOT(slotSignalLinkingFailed())); connect(d->talker, SIGNAL(signalLinkingSucceeded()), this, SLOT(slotSignalLinkingSucceeded())); connect(d->talker, SIGNAL(signalSetUserName(QString)), this, SLOT(slotSetUserName(QString))); connect(d->talker, SIGNAL(signalListAlbumsFailed(QString)), this, SLOT(slotListAlbumsFailed(QString))); connect(d->talker, SIGNAL(signalListAlbumsDone(QList >)), // krazy:exclude=normalize this, SLOT(slotListAlbumsDone(QList >))); // krazy:exclude=normalize connect(d->talker, SIGNAL(signalCreateFolderFailed(QString)), this, SLOT(slotCreateFolderFailed(QString))); connect(d->talker, SIGNAL(signalCreateFolderSucceeded()), this, SLOT(slotCreateFolderSucceeded())); connect(d->talker, SIGNAL(signalAddPhotoFailed(QString)), this, SLOT(slotAddPhotoFailed(QString))); connect(d->talker, SIGNAL(signalAddPhotoSucceeded()), this, SLOT(slotAddPhotoSucceeded())); connect(this, SIGNAL(finished(int)), this, SLOT(slotFinished())); readSettings(); buttonStateChange(false); d->talker->link(); } DBWindow::~DBWindow() { delete d->widget; delete d->albumDlg; delete d->talker; delete d; } void DBWindow::setItemsList(const QList& urls) { d->widget->imagesList()->slotAddImages(urls); } void DBWindow::reactivate() { d->widget->imagesList()->loadImagesFromCurrentSelection(); d->widget->progressBar()->hide(); show(); } void DBWindow::readSettings() { KConfig config; KConfigGroup grp = config.group("Dropbox Settings"); d->currentAlbumName = grp.readEntry("Current Album",QString()); if (grp.readEntry("Resize", false)) { d->widget->getResizeCheckBox()->setChecked(true); d->widget->getDimensionSpB()->setEnabled(true); d->widget->getImgQualitySpB()->setEnabled(true); } else { d->widget->getResizeCheckBox()->setChecked(false); d->widget->getDimensionSpB()->setEnabled(false); d->widget->getImgQualitySpB()->setEnabled(false); } d->widget->getDimensionSpB()->setValue(grp.readEntry("Maximum Width", 1600)); d->widget->getImgQualitySpB()->setValue(grp.readEntry("Image Quality", 90)); winId(); KConfigGroup dialogGroup = config.group("Dropbox Export Dialog"); KWindowConfig::restoreWindowSize(windowHandle(), dialogGroup); resize(windowHandle()->size()); } void DBWindow::writeSettings() { KConfig config; KConfigGroup grp = config.group("Dropbox Settings"); grp.writeEntry("Current Album", d->currentAlbumName); grp.writeEntry("Resize", d->widget->getResizeCheckBox()->isChecked()); grp.writeEntry("Maximum Width", d->widget->getDimensionSpB()->value()); grp.writeEntry("Image Quality", d->widget->getImgQualitySpB()->value()); KConfigGroup dialogGroup = config.group("Dropbox Export Dialog"); KWindowConfig::saveWindowSize(windowHandle(), dialogGroup); config.sync(); } void DBWindow::slotSetUserName(const QString& msg) { d->widget->updateLabels(msg, QLatin1String("")); } void DBWindow::slotListAlbumsDone(const QList >& list) { d->widget->getAlbumsCoB()->clear(); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "slotListAlbumsDone:" << list.size(); for (int i = 0 ; i < list.size() ; i++) { d->widget->getAlbumsCoB()->addItem( QIcon::fromTheme(QLatin1String("system-users")), list.value(i).second, list.value(i).first); if (d->currentAlbumName == list.value(i).first) { d->widget->getAlbumsCoB()->setCurrentIndex(i); } } buttonStateChange(true); d->talker->getUserName(); } void DBWindow::slotBusy(bool val) { if (val) { setCursor(Qt::WaitCursor); d->widget->getChangeUserBtn()->setEnabled(false); buttonStateChange(false); } else { setCursor(Qt::ArrowCursor); d->widget->getChangeUserBtn()->setEnabled(true); buttonStateChange(true); } } void DBWindow::slotStartTransfer() { d->widget->imagesList()->clearProcessedStatus(); if (d->widget->imagesList()->imageUrls().isEmpty()) { QMessageBox::critical(this, i18nc("@title:window", "Error"), i18n("No image selected. Please select which images should be uploaded.")); return; } if (!(d->talker->authenticated())) { if (QMessageBox::question(this, i18n("Login Failed"), i18n("Authentication failed. Do you want to try again?")) == QMessageBox::Yes) { d->talker->link(); return; } else { return; } } d->transferQueue = d->widget->imagesList()->imageUrls(); if (d->transferQueue.isEmpty()) { return; } d->currentAlbumName = d->widget->getAlbumsCoB()->itemData(d->widget->getAlbumsCoB()->currentIndex()).toString(); d->imagesTotal = d->transferQueue.count(); d->imagesCount = 0; d->widget->progressBar()->setFormat(i18n("%v / %m")); d->widget->progressBar()->setMaximum(d->imagesTotal); d->widget->progressBar()->setValue(0); d->widget->progressBar()->show(); d->widget->progressBar()->progressScheduled(i18n("Dropbox export"), true, true); d->widget->progressBar()->progressThumbnailChanged( QIcon(QLatin1String("dropbox")).pixmap(22, 22)); uploadNextPhoto(); } void DBWindow::uploadNextPhoto() { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "uploadNextPhoto:" << d->transferQueue.count(); if (d->transferQueue.isEmpty()) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "empty"; d->widget->progressBar()->progressCompleted(); return; } QString imgPath = d->transferQueue.first().toLocalFile(); - QString temp = d->currentAlbumName + QLatin1String("/"); + QString temp = d->currentAlbumName + QLatin1Char('/'); bool res = d->talker->addPhoto(imgPath, temp, d->widget->getResizeCheckBox()->isChecked(), d->widget->getDimensionSpB()->value(), d->widget->getImgQualitySpB()->value()); if (!res) { slotAddPhotoFailed(QLatin1String("")); return; } } void DBWindow::slotAddPhotoFailed(const QString& msg) { if (QMessageBox::question(this, i18n("Uploading Failed"), i18n("Failed to upload photo to Dropbox." "\n%1\n" "Do you want to continue?", msg)) != QMessageBox::Yes) { d->transferQueue.clear(); d->widget->progressBar()->hide(); } else { d->transferQueue.removeFirst(); d->imagesTotal--; d->widget->progressBar()->setMaximum(d->imagesTotal); d->widget->progressBar()->setValue(d->imagesCount); uploadNextPhoto(); } } void DBWindow::slotAddPhotoSucceeded() { // Remove photo uploaded from the list d->widget->imagesList()->removeItemByUrl(d->transferQueue.first()); d->transferQueue.removeFirst(); d->imagesCount++; d->widget->progressBar()->setMaximum(d->imagesTotal); d->widget->progressBar()->setValue(d->imagesCount); uploadNextPhoto(); } void DBWindow::slotImageListChanged() { startButton()->setEnabled(!(d->widget->imagesList()->imageUrls().isEmpty())); } void DBWindow::slotNewAlbumRequest() { if (d->albumDlg->exec() == QDialog::Accepted) { DBFolder newFolder; d->albumDlg->getFolderTitle(newFolder); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "slotNewAlbumRequest:" << newFolder.title; d->currentAlbumName = d->widget->getAlbumsCoB()->itemData(d->widget->getAlbumsCoB()->currentIndex()).toString(); QString temp = d->currentAlbumName + newFolder.title; d->talker->createFolder(temp); } } void DBWindow::slotReloadAlbumsRequest() { d->talker->listFolders(); } void DBWindow::slotSignalLinkingFailed() { slotSetUserName(QLatin1String("")); d->widget->getAlbumsCoB()->clear(); if (QMessageBox::question(this, i18n("Login Failed"), i18n("Authentication failed. Do you want to try again?")) == QMessageBox::Yes) { d->talker->link(); } } void DBWindow::slotSignalLinkingSucceeded() { d->talker->listFolders(); } void DBWindow::slotListAlbumsFailed(const QString& msg) { QMessageBox::critical(this, QString(), i18n("Dropbox call failed:\n%1", msg)); } void DBWindow::slotCreateFolderFailed(const QString& msg) { QMessageBox::critical(this, QString(), i18n("Dropbox call failed:\n%1", msg)); } void DBWindow::slotCreateFolderSucceeded() { d->talker->listFolders(); } void DBWindow::slotTransferCancel() { d->transferQueue.clear(); d->widget->progressBar()->hide(); d->talker->cancel(); } void DBWindow::slotUserChangeRequest() { slotSetUserName(QLatin1String("")); d->widget->getAlbumsCoB()->clear(); d->talker->unLink(); d->talker->link(); } void DBWindow::buttonStateChange(bool state) { d->widget->getNewAlbmBtn()->setEnabled(state); d->widget->getReloadBtn()->setEnabled(state); startButton()->setEnabled(state); } void DBWindow::slotFinished() { writeSettings(); d->widget->imagesList()->listView()->clear(); } void DBWindow::closeEvent(QCloseEvent* e) { if (!e) { return; } slotFinished(); e->accept(); } } // namespace Digikam diff --git a/core/utilities/import/backend/umscamera.cpp b/core/utilities/import/backend/umscamera.cpp index 9b9b7e28be..320f982658 100644 --- a/core/utilities/import/backend/umscamera.cpp +++ b/core/utilities/import/backend/umscamera.cpp @@ -1,693 +1,693 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2004-12-21 * Description : USB Mass Storage camera interface * * Copyright (C) 2004-2005 by Renchi Raju * Copyright (C) 2005-2018 by Gilles Caulier * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "umscamera.h" // C ANSI includes extern "C" { #include #include #include #include } // Qt includes #include #include #include #include #include #include #include #include #include // KDE includes #include #include #include #include #include #include // Local includes #include "drawdecoder.h" #include "digikam_debug.h" #include "digikam_config.h" #include "dimg.h" #include "dmetadata.h" #include "imagescanner.h" namespace Digikam { UMSCamera::UMSCamera(const QString& title, const QString& model, const QString& port, const QString& path) : DKCamera(title, model, port, path) { m_cancel = false; getUUIDFromSolid(); } UMSCamera::~UMSCamera() { } // Method not supported by UMS camera. bool UMSCamera::getPreview(QImage& /*preview*/) { return false; } // Method not supported by UMS camera. bool UMSCamera::capture(CamItemInfo& /*itemInfo*/) { return false; } DKCamera::CameraDriverType UMSCamera::cameraDriverType() { return DKCamera::UMSDriver; } QByteArray UMSCamera::cameraMD5ID() { QString camData; // Camera media UUID is used (if available) to improve fingerprint value // registration to database. We want to be sure that MD5 sum is really unique. // We don't use camera information here. media UUID is enough because it came be // mounted by a card reader or a camera. In this case, "already downloaded" flag will // be independent of the device used to mount memory card. camData.append(uuid()); QCryptographicHash md5(QCryptographicHash::Md5); md5.addData(camData.toUtf8()); return md5.result().toHex(); } bool UMSCamera::getFreeSpace(unsigned long& /*kBSize*/, unsigned long& /*kBAvail*/) { return false; // NOTE: implemented in gui, outside the camera thread. } bool UMSCamera::doConnect() { QFileInfo dir(m_path); if (!dir.exists() || !dir.isReadable() || !dir.isDir()) { return false; } if (dir.isWritable()) { m_deleteSupport = true; m_uploadSupport = true; m_mkDirSupport = true; m_delDirSupport = true; } else { m_deleteSupport = false; m_uploadSupport = false; m_mkDirSupport = false; m_delDirSupport = false; } m_thumbnailSupport = true; // UMS camera always support thumbnails. m_captureImageSupport = false; // UMS camera never support capture mode. return true; } void UMSCamera::cancel() { // set the cancel flag m_cancel = true; } bool UMSCamera::getFolders(const QString& folder) { if (m_cancel) { return false; } QDir dir(folder); dir.setFilter(QDir::Dirs | QDir::Executable); const QFileInfoList list = dir.entryInfoList(); if (list.isEmpty()) { return true; } QFileInfoList::const_iterator fi; QStringList subFolderList; for (fi = list.constBegin() ; !m_cancel && (fi != list.constEnd()) ; ++fi) { if (fi->fileName() == QLatin1String(".") || fi->fileName() == QLatin1String("..")) { continue; } QString subFolder = folder + QString(folder.endsWith(QLatin1Char('/')) ? QLatin1String("") : QLatin1String("/")) + fi->fileName(); subFolderList.append(subFolder); } if (subFolderList.isEmpty()) { return true; } emit signalFolderList(subFolderList); return true; } bool UMSCamera::getItemsInfoList(const QString& folder, bool useMetadata, CamItemInfoList& infoList) { m_cancel = false; infoList.clear(); QDir dir(folder); dir.setFilter(QDir::Files); if (!dir.exists()) { return false; } const QFileInfoList list = dir.entryInfoList(); if (list.isEmpty()) { return true; // Nothing to do. } for (QFileInfoList::const_iterator fi = list.constBegin() ; !m_cancel && (fi != list.constEnd()) ; ++fi) { CamItemInfo info; getItemInfo(folder, fi->fileName(), info, useMetadata); infoList.append(info); } return true; } void UMSCamera::getItemInfo(const QString& folder, const QString& itemName, CamItemInfo& info, bool useMetadata) { info.folder = !folder.endsWith(QLatin1Char('/')) ? folder + QLatin1Char('/') : folder; info.name = itemName; QFileInfo fi(info.folder + info.name); info.size = fi.size(); info.readPermissions = fi.isReadable(); info.writePermissions = fi.isWritable(); info.mime = mimeType(fi.suffix().toLower()); if (!info.mime.isEmpty()) { if (useMetadata) { // Try to use file metadata DMetadata meta; getMetadata(folder, itemName, meta); fillItemInfoFromMetadata(info, meta); // Fall back to file system info if (info.ctime.isNull()) { info.ctime = ImageScanner::creationDateFromFilesystem(fi); } } else { // Only use file system date info.ctime = ImageScanner::creationDateFromFilesystem(fi); } } // if we have an image, allow previews // TODO allow video previews at some point? /* if (info.mime.startsWith(QLatin1String("image/")) || info.mime.startsWith(QLatin1String("video/"))) */ if (info.mime.startsWith(QLatin1String("image/"))) { info.previewPossible = true; } } bool UMSCamera::getThumbnail(const QString& folder, const QString& itemName, QImage& thumbnail) { m_cancel = false; - QString path = folder + QLatin1String("/") + itemName; + QString path = folder + QLatin1Char('/') + itemName; // Try to get preview from Exif data (good quality). Can work with Raw files DMetadata metadata(path); metadata.getImagePreview(thumbnail); if (!thumbnail.isNull()) { return true; } // RAW files : try to extract embedded thumbnail using RawEngine DRawDecoder::loadRawPreview(thumbnail, path); if (!thumbnail.isNull()) { return true; } KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(QLatin1String("Camera Settings")); bool turnHighQualityThumbs = group.readEntry(QLatin1String("TurnHighQualityThumbs"), false); // Try to get thumbnail from Exif data (poor quality). if (!turnHighQualityThumbs) { thumbnail = metadata.getExifThumbnail(true); if (!thumbnail.isNull()) { return true; } } // THM files: try to get thumbnail from '.thm' files if we didn't manage to get // thumbnail from Exif. Any cameras provides *.thm files like JPEG files with RAW files. // Using this way is always speed up than ultimate loading using DImg. // Note: the thumbnail extracted with this method can be in poor quality. // 2006/27/01 - Gilles - Tested with my Minolta Dynax 5D USM camera. QFileInfo fi(path); - if (thumbnail.load(folder + QLatin1String("/") + fi.baseName() + QLatin1String(".thm"))) // Lowercase + if (thumbnail.load(folder + QLatin1Char('/') + fi.baseName() + QLatin1String(".thm"))) // Lowercase { if (!thumbnail.isNull()) { return true; } } - else if (thumbnail.load(folder + QLatin1String("/") + fi.baseName() + QLatin1String(".THM"))) // Uppercase + else if (thumbnail.load(folder + QLatin1Char('/') + fi.baseName() + QLatin1String(".THM"))) // Uppercase { if (!thumbnail.isNull()) { return true; } } // Finally, we trying to get thumbnail using DImg API (slow). qCDebug(DIGIKAM_IMPORTUI_LOG) << "Use DImg loader to get thumbnail from : " << path; DImg dimgThumb; // skip loading the data we don't need to speed it up. dimgThumb.load(path, false /*loadMetadata*/, false /*loadICCData*/, false /*loadUniqueHash*/, false /*loadHistory*/); if (!dimgThumb.isNull()) { thumbnail = dimgThumb.copyQImage(); return true; } return false; } bool UMSCamera::getMetadata(const QString& folder, const QString& itemName, DMetadata& meta) { QFileInfo fi, thmlo, thmup; bool ret = false; - fi.setFile(folder + QLatin1String("/") + itemName); - thmlo.setFile(folder + QLatin1String("/") + fi.baseName() + QLatin1String(".thm")); - thmup.setFile(folder + QLatin1String("/") + fi.baseName() + QLatin1String(".THM")); + fi.setFile(folder + QLatin1Char('/') + itemName); + thmlo.setFile(folder + QLatin1Char('/') + fi.baseName() + QLatin1String(".thm")); + thmup.setFile(folder + QLatin1Char('/') + fi.baseName() + QLatin1String(".THM")); if (thmlo.exists()) { // Try thumbnail sidecar files with lowercase extension. ret = meta.load(thmlo.filePath()); } else if (thmup.exists()) { // Try thumbnail sidecar files with uppercase extension. ret = meta.load(thmup.filePath()); } else { // If no thumbnail sidecar file available, try to load image metadata for files. ret = meta.load(fi.filePath()); } return ret; } bool UMSCamera::downloadItem(const QString& folder, const QString& itemName, const QString& saveFile) { m_cancel = false; - QString src = folder + QLatin1String("/") + itemName; + QString src = folder + QLatin1Char('/') + itemName; QString dest = saveFile; QFile sFile(src); QFile dFile(dest); if (!sFile.open(QIODevice::ReadOnly)) { qCWarning(DIGIKAM_IMPORTUI_LOG) << "Failed to open source file for reading: " << src; return false; } if (!dFile.open(QIODevice::WriteOnly)) { sFile.close(); qCWarning(DIGIKAM_IMPORTUI_LOG) << "Failed to open destination file for writing: " << dest; return false; } const int MAX_IPC_SIZE = (1024 * 32); char buffer[MAX_IPC_SIZE]; qint64 len; while (((len = sFile.read(buffer, MAX_IPC_SIZE)) != 0) && !m_cancel) { if ((len == -1) || (dFile.write(buffer, (quint64)len) != len)) { sFile.close(); dFile.close(); return false; } } sFile.close(); dFile.close(); // Set the file modification time of the downloaded file to the original file. // NOTE: this behavior don't need to be managed through Setup/Metadata settings. QT_STATBUF st; if (QT_STAT(QFile::encodeName(src).constData(), &st) == 0) { struct utimbuf ut; ut.modtime = st.st_mtime; ut.actime = st.st_atime; ::utime(QFile::encodeName(dest).constData(), &ut); } return true; } bool UMSCamera::setLockItem(const QString& folder, const QString& itemName, bool lock) { - QString src = folder + QLatin1String("/") + itemName; + QString src = folder + QLatin1Char('/') + itemName; if (lock) { // Lock the file to set read only flag if (::chmod(QFile::encodeName(src).constData(), S_IREAD) == -1) { return false; } } else { // Unlock the file to set read/write flag if (::chmod(QFile::encodeName(src).constData(), S_IREAD | S_IWRITE) == -1) { return false; } } return true; } bool UMSCamera::deleteItem(const QString& folder, const QString& itemName) { m_cancel = false; // Any camera provide THM (thumbnail) file with real image. We need to remove it also. - QFileInfo fi(folder + QLatin1String("/") + itemName); + QFileInfo fi(folder + QLatin1Char('/') + itemName); - QFileInfo thmLo(folder + QLatin1String("/") + fi.baseName() + QLatin1String(".thm")); // Lowercase + QFileInfo thmLo(folder + QLatin1Char('/') + fi.baseName() + QLatin1String(".thm")); // Lowercase if (thmLo.exists()) { ::unlink(QFile::encodeName(thmLo.filePath()).constData()); } - QFileInfo thmUp(folder + QLatin1String("/") + fi.baseName() + QLatin1String(".THM")); // Uppercase + QFileInfo thmUp(folder + QLatin1Char('/') + fi.baseName() + QLatin1String(".THM")); // Uppercase if (thmUp.exists()) { ::unlink(QFile::encodeName(thmUp.filePath()).constData()); } // Remove the real image. - return (::unlink(QFile::encodeName(folder + QLatin1String("/") + itemName).constData()) == 0); + return (::unlink(QFile::encodeName(folder + QLatin1Char('/') + itemName).constData()) == 0); } bool UMSCamera::uploadItem(const QString& folder, const QString& itemName, const QString& localFile, CamItemInfo& info) { m_cancel = false; - QString dest = folder + QLatin1String("/") + itemName; + QString dest = folder + QLatin1Char('/') + itemName; QString src = localFile; QFile sFile(src); QFile dFile(dest); if (!sFile.open(QIODevice::ReadOnly)) { qCWarning(DIGIKAM_IMPORTUI_LOG) << "Failed to open source file for reading: " << src; return false; } if (!dFile.open(QIODevice::WriteOnly)) { sFile.close(); qCWarning(DIGIKAM_IMPORTUI_LOG) << "Failed to open destination file for writing: " << dest; return false; } const int MAX_IPC_SIZE = (1024 * 32); char buffer[MAX_IPC_SIZE]; qint64 len; while (((len = sFile.read(buffer, MAX_IPC_SIZE)) != 0) && !m_cancel) { if ((len == -1) || (dFile.write(buffer, (quint64)len) == -1)) { sFile.close(); dFile.close(); return false; } } sFile.close(); dFile.close(); // Set the file modification time of the uploaded file to original file. // NOTE: this behavior don't need to be managed through Setup/Metadata settings. QT_STATBUF st; if (QT_STAT(QFile::encodeName(src).constData(), &st) == 0) { struct utimbuf ut; ut.modtime = st.st_mtime; ut.actime = st.st_atime; ::utime(QFile::encodeName(dest).constData(), &ut); } // Get new camera item information. PhotoInfoContainer pInfo; DMetadata meta; QFileInfo fi(dest); QString mime = mimeType(fi.suffix().toLower()); if (!mime.isEmpty()) { QSize dims; QDateTime dt; // Try to load image metadata. meta.load(fi.filePath()); dt = meta.getImageDateTime(); dims = meta.getImageDimensions(); pInfo = meta.getPhotographInformation(); if (dt.isNull()) // fall back to file system info { dt = ImageScanner::creationDateFromFilesystem(fi); } info.name = fi.fileName(); - info.folder = !folder.endsWith(QLatin1Char('/')) ? folder + QLatin1String("/") : folder; + info.folder = !folder.endsWith(QLatin1Char('/')) ? folder + QLatin1Char('/') : folder; info.mime = mime; info.ctime = dt; info.size = fi.size(); info.width = dims.width(); info.height = dims.height(); info.downloaded = CamItemInfo::DownloadUnknown; info.readPermissions = fi.isReadable(); info.writePermissions = fi.isWritable(); info.photoInfo = pInfo; } return true; } bool UMSCamera::cameraSummary(QString& summary) { summary = QString(i18n("Mounted Camera driver for USB/IEEE1394 mass storage cameras and " "Flash disk card readers.

")); // we do not expect title/model/etc. to contain newlines, // so we just escape HTML characters summary += i18nc("@info List of device properties", "Title: %1
" "Model: %2
" "Port: %3
" "Path: %4
" "UUID: %5

", title().toHtmlEscaped(), model().toHtmlEscaped(), port().toHtmlEscaped(), path().toHtmlEscaped(), uuid().toHtmlEscaped()); summary += i18nc("@info List of supported device operations", "Thumbnails: %1
" "Capture image: %2
" "Delete items: %3
" "Upload items: %4
" "Create directories: %5
" "Delete directories: %6

", thumbnailSupport() ? i18n("yes") : i18n("no"), captureImageSupport() ? i18n("yes") : i18n("no"), deleteSupport() ? i18n("yes") : i18n("no"), uploadSupport() ? i18n("yes") : i18n("no"), mkDirSupport() ? i18n("yes") : i18n("no"), delDirSupport() ? i18n("yes") : i18n("no")); return true; } bool UMSCamera::cameraManual(QString& manual) { manual = QString(i18n("For more information about the Mounted Camera driver, " "please read the Supported Digital Still " "Cameras section in the digiKam manual.")); return true; } bool UMSCamera::cameraAbout(QString& about) { about = QString(i18n("The Mounted Camera driver is a simple interface to a camera disk " "mounted locally on your system.

" "It does not use libgphoto2 drivers.

" "To report any problems with this driver, please contact the digiKam team at:

" "http://www.digikam.org/?q=contact")); return true; } void UMSCamera::getUUIDFromSolid() { QList devices = Solid::Device::listFromType(Solid::DeviceInterface::StorageAccess); foreach(const Solid::Device& accessDevice, devices) { // check for StorageAccess if (!accessDevice.is()) { continue; } const Solid::StorageAccess* const access = accessDevice.as(); if (!access->isAccessible()) { continue; } // check for StorageDrive Solid::Device driveDevice; for (Solid::Device currentDevice = accessDevice; currentDevice.isValid(); currentDevice = currentDevice.parent()) { if (currentDevice.is()) { driveDevice = currentDevice; break; } } if (!driveDevice.isValid()) { continue; } // check for StorageVolume Solid::Device volumeDevice; for (Solid::Device currentDevice = accessDevice; currentDevice.isValid(); currentDevice = currentDevice.parent()) { if (currentDevice.is()) { volumeDevice = currentDevice; break; } } if (!volumeDevice.isValid()) { continue; } Solid::StorageVolume* const volume = volumeDevice.as(); if (m_path.startsWith(QDir::fromNativeSeparators(access->filePath())) && QDir::fromNativeSeparators(access->filePath()) != QLatin1String("/")) { m_uuid = volume->uuid(); } } } } // namespace Digikam