diff --git a/core/libs/album/engine/albumhistory.cpp b/core/libs/album/engine/albumhistory.cpp index d4f8ed4100..0e608dc355 100644 --- a/core/libs/album/engine/albumhistory.cpp +++ b/core/libs/album/engine/albumhistory.cpp @@ -1,599 +1,599 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2004-11-17 * Description : albums history manager. * * Copyright (C) 2004 by Joern Ahrens * Copyright (C) 2006-2020 by Gilles Caulier * Copyright (C) 2014 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 "albumhistory.h" // Qt includes #include #include #include // Local includes #include "digikam_debug.h" #include "album.h" #include "iteminfo.h" #include "albummanager.h" #include "labelstreeview.h" namespace Digikam { inline uint qHash(const QList& key) { if (key.isEmpty()) { return 0; } Album* const temp = key.first(); quint64 myint = (unsigned long long)temp; uint value = ::qHash(myint); for (int it = 1 ; it < key.size() ; ++it) { Album* const al = key.at(it); quint64 myint2 = (unsigned long long)al; value ^= ::qHash(myint2); } return value; } /** * Stores an album along with the sidebar view, where the album * is selected */ class Q_DECL_HIDDEN HistoryItem { public: HistoryItem() { widget = nullptr; }; HistoryItem(QList const a, QWidget* const w) { albums.append(a); widget = w; }; HistoryItem(QList const a, QWidget* const w, QHash > selectedLabels) { albums.append(a); widget = w; labels = selectedLabels; }; bool operator==(const HistoryItem& item) { if (widget != item.widget) { return false; } return (albums == item.albums); } QList albums; QWidget* widget; QHash > labels; }; // --------------------------------------------------------------------- class Q_DECL_HIDDEN HistoryPosition { public: HistoryPosition() { }; HistoryPosition(const ItemInfo& c, const QList& s) : current(c), select(s) { }; bool operator==(const HistoryPosition& item) { return ((current == item.current) && (select == item.select)); } public: ItemInfo current; QList select; }; // --------------------------------------------------------------------- class Q_DECL_HIDDEN AlbumHistory::Private { public: explicit Private() : moving(false), blockSelection(false) { } void forward(unsigned int steps = 1); public: bool moving; bool blockSelection; QList backwardStack; QList forwardStack; QHash, HistoryPosition> historyPos; QHash > neededLabels; }; void AlbumHistory::Private::forward(unsigned int steps) { if (forwardStack.isEmpty() || ((int)steps > forwardStack.count())) { return; } while (steps) { backwardStack << forwardStack.takeFirst(); --steps; } moving = true; } AlbumHistory::AlbumHistory() : d(new Private) { } AlbumHistory::~AlbumHistory() { clearHistory(); delete d; } void AlbumHistory::clearHistory() { d->backwardStack.clear(); d->forwardStack.clear(); d->historyPos.clear(); d->moving = false; } void AlbumHistory::addAlbums(const QList& albums, QWidget* const widget) { if (albums.isEmpty() || !widget || d->moving) { d->moving = false; return; } // Same album as before in the history if (!d->backwardStack.isEmpty() && d->backwardStack.last().albums == albums) { d->backwardStack.last().widget = widget; return; } d->backwardStack << HistoryItem(albums, widget); // The forward stack has to be cleared, if backward stack was changed d->forwardStack.clear(); } /** * @brief AlbumHistory::addAlbums * A special overloaded function for handling AlbumHistory * for the Labels tree-view * * @author Mohamed_Anwer */ void AlbumHistory::addAlbums(const QList& albums, QWidget* const widget, QHash > selectedLabels) { if (albums.isEmpty() || !widget || d->moving) { d->moving = false; return; } if (!d->backwardStack.isEmpty() && d->backwardStack.last().albums.first()->isUsedByLabelsTree()) { d->backwardStack.last().widget = widget; d->backwardStack.last().labels = selectedLabels; return; } d->backwardStack << HistoryItem(albums, widget, selectedLabels); // The forward stack has to be cleared, if backward stack was changed d->forwardStack.clear(); } void AlbumHistory::deleteAlbum(Album* const album) { if (!album || d->backwardStack.isEmpty()) { return; } QList albums; albums << album; // Search all HistoryItems, with album and delete them QList::iterator it = d->backwardStack.begin(); while (it != d->backwardStack.end()) { if (it->albums == albums) { it = d->backwardStack.erase(it); } else { ++it; } } it = d->forwardStack.begin(); while (it != d->forwardStack.end()) { if (it->albums == albums) { it = d->forwardStack.erase(it); } else { ++it; } } if (d->backwardStack.isEmpty() && d->forwardStack.isEmpty()) { return; } // If backwardStack is empty, then there is no current album. // So make the first album of the forwardStack the current one. if (d->backwardStack.isEmpty()) { d->forward(); } // After the album is deleted from the history it has to be ensured, // that neighboring albums are different QList::iterator lhs = d->backwardStack.begin(); QList::iterator rhs = lhs; ++rhs; while (rhs != d->backwardStack.end()) { if (*lhs == *rhs) { rhs = d->backwardStack.erase(rhs); } else { ++lhs; rhs = lhs; ++rhs; } } rhs = d->forwardStack.begin(); while (rhs != d->forwardStack.end()) { if (*lhs == *rhs) { rhs = d->forwardStack.erase(rhs); } else { - if (lhs == (d->backwardStack.isEmpty() ? d->backwardStack.end() + if (lhs == (d->backwardStack.isEmpty() ? d->backwardStack.end() : --d->backwardStack.end())) { lhs = d->forwardStack.begin(); } else { ++lhs; rhs = lhs; } ++rhs; } } if (d->backwardStack.isEmpty() && !d->forwardStack.isEmpty()) { d->forward(); } } void AlbumHistory::getBackwardHistory(QStringList& list) const { if (d->backwardStack.isEmpty()) { return; } QList::const_iterator it = d->backwardStack.constBegin(); for ( ; it != (d->backwardStack.isEmpty() ? d->backwardStack.constEnd() : --d->backwardStack.constEnd()) ; ++it) { if (!(it->albums.isEmpty())) { QString name; for (int iter = 0 ; iter < it->albums.size() ; ++iter) { name.append(it->albums.at(iter)->title()); if (iter+1 < it->albums.size()) { name.append(QLatin1Char('/')); } } list.prepend(name); } } } void AlbumHistory::getForwardHistory(QStringList& list) const { if (d->forwardStack.isEmpty()) { return; } QList::const_iterator it; for (it = d->forwardStack.constBegin() ; it != d->forwardStack.constEnd() ; ++it) { if (!(it->albums.isEmpty())) { QString name; for (int iter = 0 ; iter < it->albums.size() ; ++iter) { name.append(it->albums.at(iter)->title()); if (iter+1 < it->albums.size()) { name.append(QLatin1Char('/')); } } list.append(name); } } } void AlbumHistory::back(QList& album, QWidget** const widget, unsigned int steps) { *widget = nullptr; if ((d->backwardStack.count() <= 1) || ((int)steps > d->backwardStack.count())) { return; // Only the current album available } while (steps) { d->forwardStack.prepend(d->backwardStack.takeLast()); --steps; } d->moving = true; if (d->backwardStack.isEmpty()) { return; } album.append(d->backwardStack.last().albums); *widget = d->backwardStack.last().widget; d->neededLabels = d->backwardStack.last().labels; } void AlbumHistory::forward(QList& album, QWidget** const widget, unsigned int steps) { *widget = nullptr; if (d->forwardStack.isEmpty() || ((int)steps > d->forwardStack.count())) { return; } d->forward(steps); if (d->backwardStack.isEmpty()) { return; } album.append(d->backwardStack.last().albums); *widget = d->backwardStack.last().widget; d->neededLabels = d->backwardStack.last().labels; } void AlbumHistory::getCurrentAlbum(Album** const album, QWidget** const widget) const { *album = nullptr; *widget = nullptr; if (d->backwardStack.isEmpty()) { return; } if (!(d->backwardStack.last().albums.isEmpty())) { *album = d->backwardStack.last().albums.first(); } *widget = d->backwardStack.last().widget; } bool AlbumHistory::isForwardEmpty() const { return d->forwardStack.isEmpty(); } bool AlbumHistory::isBackwardEmpty() const { // the last album of the backwardStack is the currently shown // album, and therefore not really a previous album return ((d->backwardStack.count() <= 1) ? true : false); } QHash > AlbumHistory::neededLabels() { return d->neededLabels; } void AlbumHistory::slotAlbumSelected() { QList albumList = AlbumManager::instance()->currentAlbums(); if (d->historyPos.contains(albumList)) { d->blockSelection = true; emit signalSetCurrent(d->historyPos[albumList].current.id()); } } void AlbumHistory::slotAlbumCurrentChanged() { QList albumList = AlbumManager::instance()->currentAlbums(); if (!(albumList.isEmpty()) && d->historyPos.contains(albumList)) { if (d->historyPos[albumList].select.size()) { emit signalSetSelectedInfos(d->historyPos[albumList].select); } } d->blockSelection = false; } void AlbumHistory::slotCurrentChange(const ItemInfo& info) { QList albumList = AlbumManager::instance()->currentAlbums(); if (albumList.isEmpty()) { return; } d->historyPos[albumList].current = info; } void AlbumHistory::slotImageSelected(const ItemInfoList& selectedImages) { if (d->blockSelection) { return; } QList albumList = AlbumManager::instance()->currentAlbums(); if (d->historyPos.contains(albumList)) { d->historyPos[albumList].select = selectedImages; } } void AlbumHistory::slotClearSelectPAlbum(const ItemInfo& imageInfo) { Album* const album = dynamic_cast(AlbumManager::instance()->findPAlbum(imageInfo.albumId())); QList albums; albums << album; if (d->historyPos.contains(albums)) { d->historyPos[albums].select.clear(); } } void AlbumHistory::slotClearSelectTAlbum(int id) { Album* const album = dynamic_cast(AlbumManager::instance()->findTAlbum(id)); QList albums; albums << album; if (d->historyPos.contains(albums)) { d->historyPos[albums].select.clear(); } } void AlbumHistory::slotAlbumDeleted(Album* album) { deleteAlbum(album); QList albums; albums << album; if (d->historyPos.contains(albums)) { d->historyPos.remove(albums); } } void AlbumHistory::slotAlbumsCleared() { clearHistory(); } } // namespace Digikam diff --git a/core/libs/album/manager/albummanager_dalbum.cpp b/core/libs/album/manager/albummanager_dalbum.cpp index 6be4207600..6969203374 100644 --- a/core/libs/album/manager/albummanager_dalbum.cpp +++ b/core/libs/album/manager/albummanager_dalbum.cpp @@ -1,278 +1,278 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2004-06-15 * Description : Albums manager interface - Date Album helpers. * * Copyright (C) 2006-2020 by Gilles Caulier * Copyright (C) 2006-2011 by Marcel Wiesweg * 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 "albummanager_p.h" namespace Digikam { void AlbumManager::scanDAlbums() { d->scanDAlbumsTimer->stop(); if (d->dateListJob) { d->dateListJob->cancel(); d->dateListJob = nullptr; } DatesDBJobInfo jInfo; jInfo.setFoldersJob(); d->dateListJob = DBJobsManager::instance()->startDatesJobThread(jInfo); connect(d->dateListJob, SIGNAL(finished()), this, SLOT(slotDatesJobResult())); connect(d->dateListJob, SIGNAL(foldersData(QMap)), this, SLOT(slotDatesJobData(QMap))); } AlbumList AlbumManager::allDAlbums() const { AlbumList list; if (d->rootDAlbum) { list.append(d->rootDAlbum); } AlbumIterator it(d->rootDAlbum); while (it.current()) { list.append(*it); ++it; } return list; } DAlbum* AlbumManager::findDAlbum(int id) const { if (!d->rootDAlbum) { return nullptr; } int gid = d->rootDAlbum->globalID() + id; return static_cast((d->allAlbumsIdHash.value(gid))); } QMap AlbumManager::getDAlbumsCount() const { return d->dAlbumsCount; } void AlbumManager::slotDatesJobResult() { if (!d->dateListJob) { return; } if (d->dateListJob->hasErrors()) { qCWarning(DIGIKAM_GENERAL_LOG) << "Failed to list dates"; // Pop-up a message about the error. - + DNotificationWrapper(QString(), d->dateListJob->errorsList().first(), nullptr, i18n("digiKam")); } d->dateListJob = nullptr; emit signalAllDAlbumsLoaded(); } void AlbumManager::slotDatesJobData(const QMap& datesStatMap) { if (datesStatMap.isEmpty() || !d->rootDAlbum) { return; } // insert all the DAlbums into a qmap for quick access QMap mAlbumMap; QMap yAlbumMap; AlbumIterator it(d->rootDAlbum); while (it.current()) { DAlbum* const a = (DAlbum*)(*it); if (a->range() == DAlbum::Month) { mAlbumMap.insert(a->date(), a); } else { yAlbumMap.insert(a->date().year(), a); } ++it; } QMap yearMonthMap; for (QMap::const_iterator it3 = datesStatMap.constBegin() ; it3 != datesStatMap.constEnd() ; ++it3) { YearMonth yearMonth = YearMonth(it3.key().date().year(), it3.key().date().month()); QMap::iterator it2 = yearMonthMap.find(yearMonth); if (it2 == yearMonthMap.end()) { yearMonthMap.insert(yearMonth, *it3); } else { *it2 += *it3; } } int year, month; for (QMap::const_iterator it4 = yearMonthMap.constBegin() ; it4 != yearMonthMap.constEnd() ; ++it4) { year = it4.key().first; month = it4.key().second; QDate md(year, month, 1); // Do we already have this Month album if (mAlbumMap.contains(md)) { // already there. remove Month album from map mAlbumMap.remove(md); if (yAlbumMap.contains(year)) { // already there. remove from map yAlbumMap.remove(year); } continue; } // Check if Year Album already exist. DAlbum* yAlbum = nullptr; AlbumIterator it5(d->rootDAlbum); while (it5.current()) { DAlbum* const a = (DAlbum*)(*it5); if (a->date() == QDate(year, 1, 1) && a->range() == DAlbum::Year) { yAlbum = a; break; } ++it5; } // If no, create Year album. if (!yAlbum) { yAlbum = new DAlbum(QDate(year, 1, 1), false, DAlbum::Year); emit signalAlbumAboutToBeAdded(yAlbum, d->rootDAlbum, d->rootDAlbum->lastChild()); yAlbum->setParent(d->rootDAlbum); d->allAlbumsIdHash.insert(yAlbum->globalID(), yAlbum); emit signalAlbumAdded(yAlbum); } // Create Month album DAlbum* const mAlbum = new DAlbum(md); emit signalAlbumAboutToBeAdded(mAlbum, yAlbum, yAlbum->lastChild()); mAlbum->setParent(yAlbum); d->allAlbumsIdHash.insert(mAlbum->globalID(), mAlbum); emit signalAlbumAdded(mAlbum); } // Now the items contained in the maps are the ones which // have been deleted. for (QMap::const_iterator it6 = mAlbumMap.constBegin() ; it6 != mAlbumMap.constEnd() ; ++it6) { DAlbum* const album = it6.value(); emit signalAlbumAboutToBeDeleted(album); d->allAlbumsIdHash.remove(album->globalID()); emit signalAlbumDeleted(album); quintptr deletedAlbum = reinterpret_cast(album); delete album; emit signalAlbumHasBeenDeleted(deletedAlbum); } for (QMap::const_iterator it7 = yAlbumMap.constBegin() ; it7 != yAlbumMap.constEnd() ; ++it7) { DAlbum* const album = it7.value(); emit signalAlbumAboutToBeDeleted(album); d->allAlbumsIdHash.remove(album->globalID()); emit signalAlbumDeleted(album); quintptr deletedAlbum = reinterpret_cast(album); delete album; emit signalAlbumHasBeenDeleted(deletedAlbum); } d->dAlbumsCount = yearMonthMap; emit signalDAlbumsDirty(yearMonthMap); emit signalDatesMapDirty(datesStatMap); } void AlbumManager::scanDAlbumsScheduled() { // Avoid a cycle of killing a job which takes longer than the timer interval if (d->dateListJob) { d->scanDAlbumsTimer->start(); return; } scanDAlbums(); } } // namespace Digikam diff --git a/core/libs/database/coredb/coredb.cpp b/core/libs/database/coredb/coredb.cpp index d67d92666d..9a8329bab1 100644 --- a/core/libs/database/coredb/coredb.cpp +++ b/core/libs/database/coredb/coredb.cpp @@ -1,4556 +1,4556 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2004-06-18 * Description : Core database interface. * * Copyright (C) 2004-2005 by Renchi Raju * Copyright (C) 2006-2020 by Gilles Caulier * Copyright (C) 2006-2012 by Marcel Wiesweg * Copyright (C) 2012 by Andi Clemens * * 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 "coredb.h" // KDE includes #include #include // Local includes #include "digikam_debug.h" #include "digikam_globals.h" #include "coredbbackend.h" #include "collectionmanager.h" #include "collectionlocation.h" #include "dbengineactiontype.h" #include "tagscache.h" #include "album.h" namespace Digikam { class Q_DECL_HIDDEN CoreDB::Private { public: explicit Private() : db(nullptr), uniqueHashVersion(-1) { } static const QString configGroupName; static const QString configRecentlyUsedTags; CoreDbBackend* db; QList recentlyAssignedTags; int uniqueHashVersion; public: QString constructRelatedImagesSQL(bool fromOrTo, DatabaseRelation::Type type, bool boolean); QList execRelatedImagesQuery(DbEngineSqlQuery& query, qlonglong id, DatabaseRelation::Type type); }; const QString CoreDB::Private::configGroupName(QLatin1String("CoreDB Settings")); const QString CoreDB::Private::configRecentlyUsedTags(QLatin1String("Recently Used Tags")); QString CoreDB::Private::constructRelatedImagesSQL(bool fromOrTo, DatabaseRelation::Type type, bool boolean) { QString sql; if (fromOrTo) { sql = QString::fromUtf8("SELECT object FROM ImageRelations " "INNER JOIN Images ON ImageRelations.object=Images.id " " WHERE subject=? %1 AND status<3 %2;"); } else { sql = QString::fromUtf8("SELECT subject FROM ImageRelations " "INNER JOIN Images ON ImageRelations.subject=Images.id " " WHERE object=? %1 AND status<3 %2;"); } if (type != DatabaseRelation::UndefinedType) { sql = sql.arg(QString::fromUtf8("AND type=?")); } else { sql = sql.arg(QString()); } if (boolean) { sql = sql.arg(QString::fromUtf8("LIMIT 1")); } else { sql = sql.arg(QString()); } return sql; } QList CoreDB::Private::execRelatedImagesQuery(DbEngineSqlQuery& query, qlonglong id, DatabaseRelation::Type type) { QVariantList values; if (type == DatabaseRelation::UndefinedType) { db->execSql(query, id, &values); } else { db->execSql(query, id, type, &values); } QList imageIds; if (values.isEmpty()) { return imageIds; } for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ++it) { imageIds << (*it).toInt(); } return imageIds; } // -------------------------------------------------------- CoreDB::CoreDB(CoreDbBackend* const backend) : d(new Private) { d->db = backend; readSettings(); } CoreDB::~CoreDB() { writeSettings(); delete d; } QList CoreDB::getAlbumRoots() const { QList list; QList values; d->db->execSql(QString::fromUtf8("SELECT id, label, status, type, identifier, specificPath FROM AlbumRoots;"), &values); for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ) { AlbumRootInfo info; info.id = (*it).toInt(); ++it; info.label = (*it).toString(); ++it; info.status = (*it).toInt(); ++it; info.type = (AlbumRoot::Type)(*it).toInt(); ++it; info.identifier = (*it).toString(); ++it; info.specificPath = (*it).toString(); ++it; list << info; } return list; } int CoreDB::addAlbumRoot(AlbumRoot::Type type, const QString& identifier, const QString& specificPath, const QString& label) const { QVariant id; d->db->execSql(QString::fromUtf8("REPLACE INTO AlbumRoots (type, label, status, identifier, specificPath) " "VALUES(?, ?, 0, ?, ?);"), (int)type, label, identifier, specificPath, nullptr, &id); d->db->recordChangeset(AlbumRootChangeset(id.toInt(), AlbumRootChangeset::Added)); return id.toInt(); } void CoreDB::deleteAlbumRoot(int rootId) { d->db->execSql(QString::fromUtf8("DELETE FROM AlbumRoots WHERE id=?;"), rootId); QMap parameters; parameters.insert(QLatin1String(":albumRoot"), rootId); if (BdEngineBackend::NoErrors != d->db->execDBAction(d->db->getDBAction(QLatin1String("deleteAlbumRoot")), parameters)) { return; } d->db->recordChangeset(AlbumRootChangeset(rootId, AlbumRootChangeset::Deleted)); } void CoreDB::migrateAlbumRoot(int rootId, const QString& identifier) { d->db->execSql(QString::fromUtf8("UPDATE AlbumRoots SET identifier=? WHERE id=?;"), identifier, rootId); d->db->recordChangeset(AlbumRootChangeset(rootId, AlbumRootChangeset::PropertiesChanged)); } void CoreDB::setAlbumRootLabel(int rootId, const QString& newLabel) { d->db->execSql(QString::fromUtf8("UPDATE AlbumRoots SET label=? WHERE id=?;"), newLabel, rootId); d->db->recordChangeset(AlbumRootChangeset(rootId, AlbumRootChangeset::PropertiesChanged)); } void CoreDB::changeAlbumRootType(int rootId, AlbumRoot::Type newType) { d->db->execSql(QString::fromUtf8("UPDATE AlbumRoots SET type=? WHERE id=?;"), (int)newType, rootId); d->db->recordChangeset(AlbumRootChangeset(rootId, AlbumRootChangeset::PropertiesChanged)); } void CoreDB::setAlbumRootPath(int rootId, const QString& newPath) { d->db->execSql(QString::fromUtf8("UPDATE AlbumRoots SET specificPath=? WHERE id=?;"), newPath, rootId); d->db->recordChangeset(AlbumRootChangeset(rootId, AlbumRootChangeset::PropertiesChanged)); } AlbumInfo::List CoreDB::scanAlbums() const { AlbumInfo::List aList; QList values; d->db->execSql(QString::fromUtf8("SELECT albumRoot, id, relativePath, date, caption, collection, icon " "FROM Albums WHERE albumRoot != 0;"), // exclude stale albums &values); QString iconAlbumUrl, iconName; for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ) { AlbumInfo info; info.albumRootId = (*it).toInt(); ++it; info.id = (*it).toInt(); ++it; info.relativePath = (*it).toString(); ++it; info.date = (*it).toDate(); ++it; info.caption = (*it).toString(); ++it; info.category = (*it).toString(); ++it; info.iconId = (*it).toLongLong(); ++it; aList.append(info); } return aList; } TagInfo::List CoreDB::scanTags() const { TagInfo::List tList; QList values; d->db->execSql(QString::fromUtf8("SELECT id, pid, name, icon, iconkde FROM Tags;"), &values); for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ) { TagInfo info; info.id = (*it).toInt(); ++it; info.pid = (*it).toInt(); ++it; info.name = (*it).toString(); ++it; info.iconId = (*it).toLongLong(); ++it; info.icon = (*it).toString(); ++it; tList.append(info); } return tList; } TagInfo CoreDB::getTagInfo(int tagId) const { QList values; d->db->execSql(QString::fromUtf8("SELECT id, pid, name, icon, iconkde WHERE id=? FROM Tags;"), tagId, &values); TagInfo info; if (!values.isEmpty() && values.size() == 5) { QList::const_iterator it = values.constBegin(); info.id = (*it).toInt(); ++it; info.pid = (*it).toInt(); ++it; info.name = (*it).toString(); ++it; info.iconId = (*it).toLongLong(); ++it; info.icon = (*it).toString(); ++it; } return info; } SearchInfo::List CoreDB::scanSearches() const { SearchInfo::List searchList; QList values; d->db->execSql(QString::fromUtf8("SELECT id, type, name, query FROM Searches;"), &values); for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ) { SearchInfo info; info.id = (*it).toInt(); ++it; info.type = (DatabaseSearch::Type)(*it).toInt(); ++it; info.name = (*it).toString(); ++it; info.query = (*it).toString(); ++it; searchList.append(info); } return searchList; } QList CoreDB::getAlbumShortInfos() const { QList values; d->db->execSql(QString::fromUtf8("SELECT id, relativePath, albumRoot FROM Albums ORDER BY id;"), &values); QList albumList; for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ) { AlbumShortInfo info; info.id = (*it).toInt(); ++it; info.relativePath = (*it).toString(); ++it; info.albumRootId = (*it).toInt(); ++it; albumList << info; } return albumList; } QList CoreDB::getTagShortInfos() const { QList values; d->db->execSql(QString::fromUtf8("SELECT id, pid, name FROM Tags ORDER BY id;"), &values); QList tagList; for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ) { TagShortInfo info; info.id = (*it).toInt(); ++it; info.pid = (*it).toInt(); ++it; info.name = (*it).toString(); ++it; tagList << info; } return tagList; } int CoreDB::addAlbum(int albumRootId, const QString& relativePath, const QString& caption, const QDate& date, const QString& collection) const { QVariant id; QList boundValues; boundValues << albumRootId << relativePath << date << caption << collection; d->db->execSql(QString::fromUtf8("REPLACE INTO Albums (albumRoot, relativePath, date, caption, collection) " "VALUES(?, ?, ?, ?, ?);"), boundValues, nullptr, &id); d->db->recordChangeset(AlbumChangeset(id.toInt(), AlbumChangeset::Added)); return id.toInt(); } void CoreDB::setAlbumCaption(int albumID, const QString& caption) { d->db->execSql(QString::fromUtf8("UPDATE Albums SET caption=? WHERE id=?;"), caption, albumID); d->db->recordChangeset(AlbumChangeset(albumID, AlbumChangeset::PropertiesChanged)); } void CoreDB::setAlbumCategory(int albumID, const QString& category) { // TODO : change "collection" property in DB ALbum table to "category" d->db->execSql(QString::fromUtf8("UPDATE Albums SET collection=? WHERE id=?;"), category, albumID); d->db->recordChangeset(AlbumChangeset(albumID, AlbumChangeset::PropertiesChanged)); } void CoreDB::setAlbumDate(int albumID, const QDate& date) { d->db->execSql(QString::fromUtf8("UPDATE Albums SET date=? WHERE id=?;"), date, albumID); d->db->recordChangeset(AlbumChangeset(albumID, AlbumChangeset::PropertiesChanged)); } void CoreDB::setAlbumIcon(int albumID, qlonglong iconID) { if (iconID == 0) { d->db->execSql(QString::fromUtf8("UPDATE Albums SET icon=NULL WHERE id=?;"), albumID); } else { d->db->execSql(QString::fromUtf8("UPDATE Albums SET icon=? WHERE id=?;"), iconID, albumID); } d->db->recordChangeset(AlbumChangeset(albumID, AlbumChangeset::PropertiesChanged)); } void CoreDB::deleteAlbum(int albumID) { QMap parameters; parameters.insert(QLatin1String(":albumId"), albumID); if (BdEngineBackend::NoErrors != d->db->execDBAction(d->db->getDBAction(QLatin1String("deleteAlbumID")), parameters)) { return; } d->db->recordChangeset(AlbumChangeset(albumID, AlbumChangeset::Deleted)); } void CoreDB::makeStaleAlbum(int albumID) { // We need to work around the table constraint, no we want to delete older stale albums with // the same relativePath, and adjust relativePaths depending on albumRoot. QList values; // retrieve information d->db->execSql(QString::fromUtf8("SELECT albumRoot, relativePath FROM Albums WHERE id=?;"), albumID, &values); if (values.isEmpty()) { return; } // prepend albumRootId to relativePath. relativePath is unused and officially undefined after this call. QString newRelativePath = values.at(0).toString() + QLatin1Char('-') + values.at(1).toString(); // delete older stale albums QMap parameters; parameters.insert(QLatin1String(":albumRoot"), 0); parameters.insert(QLatin1String(":relativePath"), newRelativePath); if (BdEngineBackend::NoErrors != d->db->execDBAction(d->db->getDBAction(QLatin1String("deleteAlbumRootPath")), parameters)) { return; } // now do our update d->db->setForeignKeyChecks(false); d->db->execSql(QString::fromUtf8("UPDATE Albums SET albumRoot=0, relativePath=? WHERE id=?;"), newRelativePath, albumID); // for now, we make no distinction to deleteAlbums wrt to changeset d->db->recordChangeset(AlbumChangeset(albumID, AlbumChangeset::Deleted)); d->db->setForeignKeyChecks(true); } void CoreDB::deleteStaleAlbums() { QMap parameters; parameters.insert(QLatin1String(":albumRoot"), 0); if (BdEngineBackend::NoErrors != d->db->execDBAction(d->db->getDBAction(QLatin1String("deleteAlbumRoot")), parameters)) { return; } // deliberately no changeset here, is done above } int CoreDB::addTag(int parentTagID, const QString& name, const QString& iconKDE, qlonglong iconID) const { QVariant id; QMap parameters; parameters.insert(QLatin1String(":tagPID"), parentTagID); parameters.insert(QLatin1String(":tagname"), name); if (BdEngineBackend::NoErrors != d->db->execDBAction(d->db->getDBAction(QLatin1String("InsertTag")), parameters, nullptr , &id)) { return -1; } if (!iconKDE.isEmpty()) { d->db->execSql(QString::fromUtf8("UPDATE Tags SET iconkde=? WHERE id=?;"), iconKDE, id.toInt()); } else if (iconID == 0) { d->db->execSql(QString::fromUtf8("UPDATE Tags SET icon=NULL WHERE id=?;"), id.toInt()); } else { d->db->execSql(QString::fromUtf8("UPDATE Tags SET icon=? WHERE id=?;"), iconID, id.toInt()); } d->db->recordChangeset(TagChangeset(id.toInt(), TagChangeset::Added)); return id.toInt(); } void CoreDB::deleteTag(int tagID) { /* QString("DELETE FROM Tags WHERE id=?;"), tagID */ QMap bindingMap; bindingMap.insert(QLatin1String(":tagID"), tagID); d->db->execDBAction(d->db->getDBAction(QLatin1String("DeleteTag")), bindingMap); d->db->recordChangeset(TagChangeset(tagID, TagChangeset::Deleted)); } void CoreDB::setTagIcon(int tagID, const QString& iconKDE, qlonglong iconID) { int dbIconID = iconKDE.isEmpty() ? iconID : 0; QString dbIconKDE = iconKDE; if (iconKDE.isEmpty() || iconKDE.toLower() == QLatin1String("tag")) { dbIconKDE.clear(); } if (dbIconID == 0) { d->db->execSql(QString::fromUtf8("UPDATE Tags SET iconkde=?, icon=NULL WHERE id=?;"), dbIconKDE, tagID); } else { d->db->execSql(QString::fromUtf8("UPDATE Tags SET iconkde=?, icon=? WHERE id=?;"), dbIconKDE, dbIconID, tagID); } d->db->recordChangeset(TagChangeset(tagID, TagChangeset::IconChanged)); } void CoreDB::setTagParentID(int tagID, int newParentTagID) { if (d->db->databaseType() == BdEngineBackend::DbType::SQLite) { d->db->execSql(QString::fromUtf8("UPDATE OR REPLACE Tags SET pid=? WHERE id=?;"), newParentTagID, tagID); } else { d->db->execSql(QString::fromUtf8("UPDATE Tags SET pid=? WHERE id=?;"), newParentTagID, tagID); // NOTE: Update the Mysql TagsTree table which is used only in some search SQL queries (See lft/rgt tag ID properties). // In SQlite, it is nicely maintained by Triggers. // With MySQL, this did not work for some reason, and we patch a tree structure mimics in a different way. QMap bindingMap; bindingMap.insert(QLatin1String(":tagID"), tagID); bindingMap.insert(QLatin1String(":newTagPID"), newParentTagID); d->db->execDBAction(d->db->getDBAction(QLatin1String("MoveTagTree")), bindingMap); } d->db->recordChangeset(TagChangeset(tagID, TagChangeset::Reparented)); } QList CoreDB::getTagProperties(int tagId) const { QList values; d->db->execSql(QString::fromUtf8("SELECT property, value FROM TagProperties WHERE tagid=?;"), tagId, &values); QList properties; if (values.isEmpty()) { return properties; } for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ) { TagProperty property; property.tagId = tagId; property.property = (*it).toString(); ++it; property.value = (*it).toString(); ++it; properties << property; } return properties; } QList CoreDB::getTagProperties(const QString& property) const { QList values; d->db->execSql(QString::fromUtf8("SELECT tagid, property, value FROM TagProperties WHERE property=?;"), property, &values); QList properties; if (values.isEmpty()) { return properties; } for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ) { TagProperty property; property.tagId = (*it).toInt(); ++it; property.property = (*it).toString(); ++it; property.value = (*it).toString(); ++it; properties << property; } return properties; } QList CoreDB::getTagProperties() const { QList values; d->db->execSql(QString::fromUtf8("SELECT tagid, property, value FROM TagProperties ORDER BY tagid, property;"), &values); QList properties; if (values.isEmpty()) { return properties; } for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ) { TagProperty property; property.tagId = (*it).toInt(); ++it; property.property = (*it).toString(); ++it; property.value = (*it).toString(); ++it; properties << property; } return properties; } QList< int > CoreDB::getTagsWithProperty(const QString& property) const { QList values; d->db->execSql(QString::fromUtf8("SELECT DISTINCT tagid FROM TagProperties WHERE property=?;"), property, &values); QList tagIds; foreach (const QVariant& var, values) { tagIds << var.toInt(); } return tagIds; } void CoreDB::addTagProperty(int tagId, const QString& property, const QString& value) { d->db->execSql(QString::fromUtf8("INSERT INTO TagProperties (tagid, property, value) VALUES(?, ?, ?);"), tagId, property, value); d->db->recordChangeset(TagChangeset(tagId, TagChangeset::PropertiesChanged)); } void CoreDB::addTagProperty(const TagProperty& property) { addTagProperty(property.tagId, property.property, property.value); } void CoreDB::removeTagProperties(int tagId, const QString& property, const QString& value) { if (property.isNull()) { d->db->execSql(QString::fromUtf8("DELETE FROM TagProperties WHERE tagid=?;"), tagId); } else if (value.isNull()) { d->db->execSql(QString::fromUtf8("DELETE FROM TagProperties WHERE tagid=? AND property=?;"), tagId, property); } else { d->db->execSql(QString::fromUtf8("DELETE FROM TagProperties WHERE tagid=? AND property=? AND value=?;"), tagId, property, value); } d->db->recordChangeset(TagChangeset(tagId, TagChangeset::PropertiesChanged)); } int CoreDB::addSearch(DatabaseSearch::Type type, const QString& name, const QString& query) const { QVariant id; if (!d->db->execSql(QString::fromUtf8("INSERT INTO Searches (type, name, query) VALUES(?, ?, ?);"), type, name, query, nullptr, &id)) { return -1; } d->db->recordChangeset(SearchChangeset(id.toInt(), SearchChangeset::Added)); return id.toInt(); } void CoreDB::updateSearch(int searchID, DatabaseSearch::Type type, const QString& name, const QString& query) { d->db->execSql(QString::fromUtf8("UPDATE Searches SET type=?, name=?, query=? WHERE id=?;"), type, name, query, searchID); d->db->recordChangeset(SearchChangeset(searchID, SearchChangeset::Changed)); } void CoreDB::deleteSearch(int searchID) { d->db->execSql(QString::fromUtf8("DELETE FROM Searches WHERE id=?;"), searchID); d->db->recordChangeset(SearchChangeset(searchID, SearchChangeset::Deleted)); } void CoreDB::deleteSearches(DatabaseSearch::Type type) { d->db->execSql(QString::fromUtf8("DELETE FROM Searches WHERE type=?;"), type); d->db->recordChangeset(SearchChangeset(0, SearchChangeset::Deleted)); } QString CoreDB::getSearchQuery(int searchId) const { QList values; d->db->execSql(QString::fromUtf8("SELECT query FROM Searches WHERE id=?;"), searchId, &values); if (values.isEmpty()) { return QString(); } return values.first().toString(); } SearchInfo CoreDB::getSearchInfo(int searchId) const { SearchInfo info; QList values; d->db->execSql(QString::fromUtf8("SELECT id, type, name, query FROM Searches WHERE id=?;"), searchId, &values); if (values.size() == 4) { QList::const_iterator it = values.constBegin(); info.id = (*it).toInt(); ++it; info.type = (DatabaseSearch::Type)(*it).toInt(); ++it; info.name = (*it).toString(); ++it; info.query = (*it).toString(); ++it; } return info; } void CoreDB::setSetting(const QString& keyword, const QString& value) { d->db->execSql(QString::fromUtf8("REPLACE INTO Settings VALUES (?,?);"), keyword, value); } QString CoreDB::getSetting(const QString& keyword) const { QList values; d->db->execSql(QString::fromUtf8("SELECT value FROM Settings " "WHERE keyword=?;"), keyword, &values); if (values.isEmpty()) { return QString(); } return values.first().toString(); } // helper method static QStringList joinMainAndUserFilterString(const QChar& sep, const QString& filter, const QString& userFilter) { QStringList filterList; QStringList userFilterList; filterList = filter.split(sep, QString::SkipEmptyParts); userFilterList = userFilter.split(sep, QString::SkipEmptyParts); foreach (const QString& userFormat, userFilterList) { if (userFormat.startsWith(QLatin1Char('-'))) { filterList.removeAll(userFormat.mid(1)); } else { filterList << userFormat; } } filterList.removeDuplicates(); filterList.sort(); return filterList; } void CoreDB::getFilterSettings(QStringList* imageFilter, QStringList* videoFilter, QStringList* audioFilter) { QString imageFormats, videoFormats, audioFormats, userImageFormats, userVideoFormats, userAudioFormats; if (imageFilter) { imageFormats = getSetting(QLatin1String("databaseImageFormats")); userImageFormats = getSetting(QLatin1String("databaseUserImageFormats")); *imageFilter = joinMainAndUserFilterString(QLatin1Char(';'), imageFormats, userImageFormats); } if (videoFilter) { videoFormats = getSetting(QLatin1String("databaseVideoFormats")); userVideoFormats = getSetting(QLatin1String("databaseUserVideoFormats")); *videoFilter = joinMainAndUserFilterString(QLatin1Char(';'), videoFormats, userVideoFormats); } if (audioFilter) { audioFormats = getSetting(QLatin1String("databaseAudioFormats")); userAudioFormats = getSetting(QLatin1String("databaseUserAudioFormats")); *audioFilter = joinMainAndUserFilterString(QLatin1Char(';'), audioFormats, userAudioFormats); } } void CoreDB::getUserFilterSettings(QString* imageFilterString, QString* videoFilterString, QString* audioFilterString) { if (imageFilterString) { *imageFilterString = getSetting(QLatin1String("databaseUserImageFormats")); } if (videoFilterString) { *videoFilterString = getSetting(QLatin1String("databaseUserVideoFormats")); } if (audioFilterString) { *audioFilterString = getSetting(QLatin1String("databaseUserAudioFormats")); } } void CoreDB::getUserIgnoreDirectoryFilterSettings(QString* ignoreDirectoryFilterString) { *ignoreDirectoryFilterString = getSetting(QLatin1String("databaseUserIgnoreDirectoryFormats")); } void CoreDB::getIgnoreDirectoryFilterSettings(QStringList* ignoreDirectoryFilter) { QString ignoreDirectoryFormats, userIgnoreDirectoryFormats; ignoreDirectoryFormats = getSetting(QLatin1String("databaseIgnoreDirectoryFormats")); userIgnoreDirectoryFormats = getSetting(QLatin1String("databaseUserIgnoreDirectoryFormats")); *ignoreDirectoryFilter = joinMainAndUserFilterString(QLatin1Char(';'), ignoreDirectoryFormats, userIgnoreDirectoryFormats); } void CoreDB::setFilterSettings(const QStringList& imageFilter, const QStringList& videoFilter, const QStringList& audioFilter) { setSetting(QLatin1String("databaseImageFormats"), imageFilter.join(QLatin1Char(';'))); setSetting(QLatin1String("databaseVideoFormats"), videoFilter.join(QLatin1Char(';'))); setSetting(QLatin1String("databaseAudioFormats"), audioFilter.join(QLatin1Char(';'))); } void CoreDB::setIgnoreDirectoryFilterSettings(const QStringList& ignoreDirectoryFilter) { setSetting(QLatin1String("databaseIgnoreDirectoryFormats"), ignoreDirectoryFilter.join(QLatin1Char(';'))); } void CoreDB::setUserFilterSettings(const QStringList& imageFilter, const QStringList& videoFilter, const QStringList& audioFilter) { setSetting(QLatin1String("databaseUserImageFormats"), imageFilter.join(QLatin1Char(';'))); setSetting(QLatin1String("databaseUserVideoFormats"), videoFilter.join(QLatin1Char(';'))); setSetting(QLatin1String("databaseUserAudioFormats"), audioFilter.join(QLatin1Char(';'))); } void CoreDB::setUserIgnoreDirectoryFilterSettings(const QStringList& ignoreDirectoryFilters) { qCDebug(DIGIKAM_DATABASE_LOG) << "CoreDB::setUserIgnoreDirectoryFilterSettings. " "ignoreDirectoryFilterString: " << ignoreDirectoryFilters.join(QLatin1Char(';')); setSetting(QLatin1String("databaseUserIgnoreDirectoryFormats"), ignoreDirectoryFilters.join(QLatin1Char(';'))); } QUuid CoreDB::databaseUuid() { QString uuidString = getSetting(QLatin1String("databaseUUID")); QUuid uuid = QUuid(uuidString); if (uuidString.isNull() || uuid.isNull()) { uuid = QUuid::createUuid(); setSetting(QLatin1String("databaseUUID"), uuid.toString()); } return uuid; } int CoreDB::getUniqueHashVersion() const { if (d->uniqueHashVersion == -1) { QString v = getSetting(QLatin1String("uniqueHashVersion")); if (v.isEmpty()) { d->uniqueHashVersion = 1; } else { d->uniqueHashVersion = v.toInt(); } } return d->uniqueHashVersion; } bool CoreDB::isUniqueHashV2() const { return (getUniqueHashVersion() == 2); } void CoreDB::setUniqueHashVersion(int version) { d->uniqueHashVersion = version; setSetting(QLatin1String("uniqueHashVersion"), QString::number(d->uniqueHashVersion)); } qlonglong CoreDB::getImageId(int albumID, const QString& name) const { QList values; d->db->execSql(QString::fromUtf8("SELECT id FROM Images " "WHERE album=? AND name=?;"), albumID, name, &values); if (values.isEmpty()) { return -1; } return values.first().toLongLong(); } QList CoreDB::getImageIds(int albumID, const QString& name, DatabaseItem::Status status) const { QList values; if (albumID == -1) { d->db->execSql(QString::fromUtf8("SELECT id FROM Images " "WHERE album IS NULL AND name=? AND status=?;"), name, status, &values); } else { d->db->execSql(QString::fromUtf8("SELECT id FROM Images " "WHERE album=? AND name=? AND status=?;"), albumID, name, status, &values); } QList items; for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ++it) { items << it->toLongLong(); } return items; } QList CoreDB::getImageIds(DatabaseItem::Status status) const { QList values; d->db->execSql(QString::fromUtf8("SELECT id FROM Images " "WHERE status=?;"), status, &values); QList imageIds; foreach (const QVariant& object, values) { imageIds << object.toLongLong(); } return imageIds; } QList CoreDB::getImageIds(DatabaseItem::Status status, DatabaseItem::Category category) const { QList values; d->db->execSql(QString::fromUtf8("SELECT id FROM Images " "WHERE status=? AND category=?;"), status, category, &values); QList imageIds; foreach (const QVariant& object, values) { imageIds << object.toLongLong(); } return imageIds; } qlonglong CoreDB::findImageId(int albumID, const QString& name, DatabaseItem::Status status, DatabaseItem::Category category, qlonglong fileSize, const QString& uniqueHash) const { QList values; QVariantList boundValues; // Add the standard bindings boundValues << name << (int)status << (int)category << fileSize << uniqueHash; // If the album id is -1, no album is assigned. Get all images with NULL album if (albumID == -1) { d->db->execSql(QString::fromUtf8("SELECT id FROM Images " "WHERE name=? AND status=? " "AND category=? AND fileSize=? " "AND uniqueHash=? AND album IS NULL;"), boundValues, &values); } else { boundValues << albumID; d->db->execSql(QString::fromUtf8("SELECT id FROM Images " "WHERE name=? AND status=? " "AND category=? AND fileSize=? " "AND uniqueHash=? AND album=?;"), boundValues, &values); } if (values.isEmpty()) { return -1; } // If there are several identical image ids, // probably use the last most recent one. return values.last().toLongLong(); } QStringList CoreDB::getItemTagNames(qlonglong imageID) const { QList values; d->db->execSql(QString::fromUtf8("SELECT name FROM Tags " "WHERE id IN (SELECT tagid FROM ImageTags " " WHERE imageid=?) " " ORDER BY name;"), imageID, &values); QStringList names; for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ++it) { names << it->toString(); } return names; } QList CoreDB::getItemTagIDs(qlonglong imageID) const { QList values; d->db->execSql(QString::fromUtf8("SELECT tagid FROM ImageTags WHERE imageID=?;"), imageID, &values); QList ids; if (values.isEmpty()) { return ids; } for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ++it) { ids << it->toInt(); } return ids; } QVector > CoreDB::getItemsTagIDs(const QList imageIds) const { if (imageIds.isEmpty()) { return QVector >(); } QVector > results(imageIds.size()); DbEngineSqlQuery query = d->db->prepareQuery(QString::fromUtf8("SELECT tagid FROM ImageTags WHERE imageID=?;")); QVariantList values; for (int i = 0 ; i < imageIds.size() ; ++i) { d->db->execSql(query, imageIds[i], &values); QList& tagIds = results[i]; foreach (const QVariant& v, values) { tagIds << v.toInt(); } } return results; } QList CoreDB::getImageTagProperties(qlonglong imageId, int tagId) const { QList values; if (tagId == -1) { d->db->execSql(QString::fromUtf8("SELECT tagid, property, value FROM ImageTagProperties " "WHERE imageid=?;"), imageId, &values); } else { d->db->execSql(QString::fromUtf8("SELECT tagid, property, value FROM ImageTagProperties " "WHERE imageid=? AND tagid=?;"), imageId, tagId, &values); } QList properties; if (values.isEmpty()) { return properties; } for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ) { ImageTagProperty property; property.imageId = imageId; property.tagId = (*it).toInt(); ++it; property.property = (*it).toString(); ++it; property.value = (*it).toString(); ++it; properties << property; } return properties; } QList CoreDB::getTagIdsWithProperties(qlonglong imageId) const { QList values; d->db->execSql(QString::fromUtf8("SELECT DISTINCT tagid FROM ImageTagProperties WHERE imageid=?;"), imageId, &values); QList tagIds; for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ++it) { tagIds << (*it).toInt(); } return tagIds; } void CoreDB::addImageTagProperty(qlonglong imageId, int tagId, const QString& property, const QString& value) { d->db->execSql(QString::fromUtf8("INSERT INTO ImageTagProperties (imageid, tagid, property, value) " "VALUES(?, ?, ?, ?);"), imageId, tagId, property, value); d->db->recordChangeset(ImageTagChangeset(imageId, tagId, ImageTagChangeset::PropertiesChanged)); } void CoreDB::addImageTagProperty(const ImageTagProperty& property) { addImageTagProperty(property.imageId, property.tagId, property.property, property.value); } void CoreDB::removeImageTagProperties(qlonglong imageId, int tagId, const QString& property, const QString& value) { if (tagId == -1) { d->db->execSql(QString::fromUtf8("DELETE FROM ImageTagProperties " "WHERE imageid=?;"), imageId); } else if (property.isNull()) { d->db->execSql(QString::fromUtf8("DELETE FROM ImageTagProperties " "WHERE imageid=? AND tagid=?;"), imageId, tagId); } else if (value.isNull()) { d->db->execSql(QString::fromUtf8("DELETE FROM ImageTagProperties " "WHERE imageid=? AND tagid=? AND property=?;"), imageId, tagId, property); } else { d->db->execSql(QString::fromUtf8("DELETE FROM ImageTagProperties " "WHERE imageid=? AND tagid=? AND property=? AND value=?;"), imageId, tagId, property, value); } d->db->recordChangeset(ImageTagChangeset(imageId, tagId, ImageTagChangeset::PropertiesChanged)); } ItemShortInfo CoreDB::getItemShortInfo(qlonglong imageID) const { QList values; d->db->execSql(QString::fromUtf8("SELECT Images.name, Albums.albumRoot, Albums.relativePath, Albums.id " "FROM Images " " INNER JOIN Albums ON Albums.id=Images.album " " WHERE Images.id=?;"), imageID, &values); ItemShortInfo info; if (!values.isEmpty()) { info.id = imageID; info.itemName = values.at(0).toString(); info.albumRootID = values.at(1).toInt(); info.album = values.at(2).toString(); info.albumID = values.at(3).toInt(); } return info; } ItemShortInfo CoreDB::getItemShortInfo(int albumRootId, const QString& relativePath, const QString& name) const { QList values; d->db->execSql(QString::fromUtf8("SELECT Images.id, Albums.id FROM Images " "INNER JOIN Albums ON Albums.id=Images.album " " WHERE name=? AND albumRoot=? AND relativePath=?;"), name, albumRootId, relativePath, &values); ItemShortInfo info; if (!values.isEmpty()) { info.id = values.at(0).toLongLong(); info.itemName = name; info.albumRootID = albumRootId; info.album = relativePath; info.albumID = values.at(1).toInt(); } return info; } bool CoreDB::hasTags(const QList& imageIDList) const { QList ids; if (imageIDList.isEmpty()) { return false; } QList values; QList boundValues; QString sql = QString::fromUtf8("SELECT COUNT(tagid) FROM ImageTags " "WHERE imageid=? "); boundValues << imageIDList.first(); QList::const_iterator it = imageIDList.constBegin(); ++it; for (; it != imageIDList.constEnd() ; ++it) { sql += QString::fromUtf8(" OR imageid=? "); boundValues << (*it); } sql += QString::fromUtf8(";"); d->db->execSql(sql, boundValues, &values); if (values.isEmpty() || values.first().toInt() == 0) { return false; } return true; } QList CoreDB::getItemCommonTagIDs(const QList& imageIDList) const { QList ids; if (imageIDList.isEmpty()) { return ids; } QList values; QList boundValues; QString sql = QString::fromUtf8("SELECT DISTINCT tagid FROM ImageTags " "WHERE imageid=? "); boundValues << imageIDList.first(); QList::const_iterator it = imageIDList.constBegin(); ++it; for (; it != imageIDList.constEnd() ; ++it) { sql += QString::fromUtf8(" OR imageid=? "); boundValues << (*it); } sql += QString::fromUtf8(";"); d->db->execSql(sql, boundValues, &values); if (values.isEmpty()) { return ids; } for (QList::const_iterator it2 = values.constBegin() ; it2 != values.constEnd() ; ++it2) { ids << it2->toInt(); } return ids; } QVariantList CoreDB::getImagesFields(qlonglong imageID, DatabaseFields::Images fields) const { QVariantList values; if (fields != DatabaseFields::ImagesNone) { QString query(QString::fromUtf8("SELECT ")); QStringList fieldNames = imagesFieldList(fields); query += fieldNames.join(QString::fromUtf8(", ")); query += QString::fromUtf8(" FROM Images WHERE id=?;"); d->db->execSql(query, imageID, &values); if (fieldNames.size() != values.size()) { return QVariantList(); } // Convert date times to QDateTime, they come as QString if ((fields & DatabaseFields::ModificationDate)) { int index = fieldNames.indexOf(QLatin1String("modificationDate")); values[index] = values.at(index).toDateTime(); } } return values; } QVariantList CoreDB::getItemInformation(qlonglong imageID, DatabaseFields::ItemInformation fields) const { QVariantList values; if (fields != DatabaseFields::ItemInformationNone) { QString query(QString::fromUtf8("SELECT ")); QStringList fieldNames = imageInformationFieldList(fields); query += fieldNames.join(QString::fromUtf8(", ")); query += QString::fromUtf8(" FROM ImageInformation WHERE imageid=?;"); d->db->execSql(query, imageID, &values); if (fieldNames.size() != values.size()) { return QVariantList(); } // Convert date times to QDateTime, they come as QString if ((fields & DatabaseFields::CreationDate)) { int index = fieldNames.indexOf(QLatin1String("creationDate")); values[index] = values.at(index).toDateTime(); } if ((fields & DatabaseFields::DigitizationDate)) { int index = fieldNames.indexOf(QLatin1String("digitizationDate")); values[index] = values.at(index).toDateTime(); } } return values; } QVariantList CoreDB::getImageMetadata(qlonglong imageID, DatabaseFields::ImageMetadata fields) const { QVariantList values; if (fields != DatabaseFields::ImageMetadataNone) { QString query(QString::fromUtf8("SELECT ")); QStringList fieldNames = imageMetadataFieldList(fields); query += fieldNames.join(QString::fromUtf8(", ")); query += QString::fromUtf8(" FROM ImageMetadata WHERE imageid=?;"); d->db->execSql(query, imageID, &values); // For some reason, if REAL values may be required from variables stored as QString QVariants. Convert code will come here. } return values; } QVariantList CoreDB::getVideoMetadata(qlonglong imageID, DatabaseFields::VideoMetadata fields) const { QVariantList values; if (fields != DatabaseFields::VideoMetadataNone) { QString query(QString::fromUtf8("SELECT ")); QStringList fieldNames = videoMetadataFieldList(fields); query += fieldNames.join(QString::fromUtf8(", ")); query += QString::fromUtf8(" FROM VideoMetadata WHERE imageid=?;"); d->db->execSql(query, imageID, &values); // For some reason REAL values may come as QString QVariants. Convert here. if (values.size() == fieldNames.size() && ((fields & DatabaseFields::Aperture) || (fields & DatabaseFields::FocalLength) || (fields & DatabaseFields::FocalLength35) || (fields & DatabaseFields::ExposureTime) || (fields & DatabaseFields::SubjectDistance)) ) { for (int i = 0 ; i < values.size() ; ++i) { if (values.at(i).type() == QVariant::String && (fieldNames.at(i) == QLatin1String("aperture") || fieldNames.at(i) == QLatin1String("focalLength") || fieldNames.at(i) == QLatin1String("focalLength35") || fieldNames.at(i) == QLatin1String("exposureTime") || fieldNames.at(i) == QLatin1String("subjectDistance")) ) { values[i] = values.at(i).toDouble(); } } } } return values; } QVariantList CoreDB::getItemPosition(qlonglong imageID, DatabaseFields::ItemPositions fields) const { QVariantList values; if (fields != DatabaseFields::ItemPositionsNone) { QString query(QString::fromUtf8("SELECT ")); QStringList fieldNames = imagePositionsFieldList(fields); query += fieldNames.join(QString::fromUtf8(", ")); query += QString::fromUtf8(" FROM ImagePositions WHERE imageid=?;"); d->db->execSql(query, imageID, &values); // For some reason REAL values may come as QString QVariants. Convert here. if (values.size() == fieldNames.size() && ((fields & DatabaseFields::LatitudeNumber) || (fields & DatabaseFields::LongitudeNumber) || (fields & DatabaseFields::Altitude) || (fields & DatabaseFields::PositionOrientation) || (fields & DatabaseFields::PositionTilt) || (fields & DatabaseFields::PositionRoll) || (fields & DatabaseFields::PositionAccuracy)) ) { for (int i = 0 ; i < values.size() ; ++i) { if (values.at(i).type() == QVariant::String && (fieldNames.at(i) == QLatin1String("latitudeNumber") || fieldNames.at(i) == QLatin1String("longitudeNumber") || fieldNames.at(i) == QLatin1String("altitude") || fieldNames.at(i) == QLatin1String("orientation") || fieldNames.at(i) == QLatin1String("tilt") || fieldNames.at(i) == QLatin1String("roll") || fieldNames.at(i) == QLatin1String("accuracy")) ) { if (!values.at(i).isNull()) values[i] = values.at(i).toDouble(); } } } } return values; } QVariantList CoreDB::getItemPositions(QList imageIDs, DatabaseFields::ItemPositions fields) const { QVariantList values; if (fields != DatabaseFields::ItemPositionsNone) { QString sql(QString::fromUtf8("SELECT ")); QStringList fieldNames = imagePositionsFieldList(fields); sql += fieldNames.join(QString::fromUtf8(", ")); sql += QString::fromUtf8(" FROM ImagePositions WHERE imageid=?;"); DbEngineSqlQuery query = d->db->prepareQuery(sql); foreach (const qlonglong& imageid, imageIDs) { QVariantList singleValueList; d->db->execSql(query, imageid, &singleValueList); values << singleValueList; } // For some reason REAL values may come as QString QVariants. Convert here. if (values.size() == fieldNames.size() && (fields & DatabaseFields::LatitudeNumber || fields & DatabaseFields::LongitudeNumber || fields & DatabaseFields::Altitude || fields & DatabaseFields::PositionOrientation || fields & DatabaseFields::PositionTilt || fields & DatabaseFields::PositionRoll || fields & DatabaseFields::PositionAccuracy) ) { for (int i = 0 ; i < values.size() ; ++i) { if (values.at(i).type() == QVariant::String && (fieldNames.at(i) == QLatin1String("latitudeNumber") || fieldNames.at(i) == QLatin1String("longitudeNumber") || fieldNames.at(i) == QLatin1String("altitude") || fieldNames.at(i) == QLatin1String("orientation") || fieldNames.at(i) == QLatin1String("tilt") || fieldNames.at(i) == QLatin1String("roll") || fieldNames.at(i) == QLatin1String("accuracy")) ) { if (!values.at(i).isNull()) values[i] = values.at(i).toDouble(); } } } } return values; } void CoreDB::addItemInformation(qlonglong imageID, const QVariantList& infos, DatabaseFields::ItemInformation fields) { if (fields == DatabaseFields::ItemInformationNone) { return; } QString query(QString::fromUtf8("REPLACE INTO ImageInformation ( imageid, ")); QStringList fieldNames = imageInformationFieldList(fields); Q_ASSERT(fieldNames.size() == infos.size()); query += fieldNames.join(QLatin1String(", ")); query += QString::fromUtf8(" ) VALUES ("); addBoundValuePlaceholders(query, infos.size() + 1); query += QString::fromUtf8(");"); QVariantList boundValues; boundValues << imageID; boundValues << infos; d->db->execSql(query, boundValues); d->db->recordChangeset(ImageChangeset(imageID, DatabaseFields::Set(fields))); } void CoreDB::changeItemInformation(qlonglong imageId, const QVariantList& infos, DatabaseFields::ItemInformation fields) { if (fields == DatabaseFields::ItemInformationNone) { return; } QStringList fieldNames = imageInformationFieldList(fields); d->db->execUpsertDBAction(QLatin1String("changeItemInformation"), imageId, fieldNames, infos); d->db->recordChangeset(ImageChangeset(imageId, DatabaseFields::Set(fields))); } void CoreDB::addImageMetadata(qlonglong imageID, const QVariantList& infos, DatabaseFields::ImageMetadata fields) { if (fields == DatabaseFields::ImageMetadataNone) { return; } QString query(QString::fromUtf8("REPLACE INTO ImageMetadata ( imageid, ")); QStringList fieldNames = imageMetadataFieldList(fields); Q_ASSERT(fieldNames.size() == infos.size()); query += fieldNames.join(QLatin1String(", ")); query += QString::fromUtf8(" ) VALUES ("); addBoundValuePlaceholders(query, infos.size() + 1); query += QString::fromUtf8(");"); QVariantList boundValues; boundValues << imageID << infos; d->db->execSql(query, boundValues); d->db->recordChangeset(ImageChangeset(imageID, DatabaseFields::Set(fields))); } void CoreDB::changeImageMetadata(qlonglong imageId, const QVariantList& infos, DatabaseFields::ImageMetadata fields) { if (fields == DatabaseFields::ImageMetadataNone) { return; } QString query(QString::fromUtf8("UPDATE ImageMetadata SET ")); QStringList fieldNames = imageMetadataFieldList(fields); Q_ASSERT(fieldNames.size() == infos.size()); query += fieldNames.join(QString::fromUtf8("=?,")); query += QString::fromUtf8("=? WHERE imageid=?;"); QVariantList boundValues; boundValues << infos << imageId; d->db->execSql(query, boundValues); d->db->recordChangeset(ImageChangeset(imageId, DatabaseFields::Set(fields))); } void CoreDB::addVideoMetadata(qlonglong imageID, const QVariantList& infos, DatabaseFields::VideoMetadata fields) { if (fields == DatabaseFields::VideoMetadataNone) { return; } QString query(QString::fromUtf8("REPLACE INTO VideoMetadata ( imageid, ")); // need to create this database QStringList fieldNames = videoMetadataFieldList(fields); Q_ASSERT(fieldNames.size() == infos.size()); query += fieldNames.join(QLatin1String(", ")); query += QString::fromUtf8(" ) VALUES ("); addBoundValuePlaceholders(query, infos.size() + 1); query += QString::fromUtf8(");"); QVariantList boundValues; boundValues << imageID << infos; d->db->execSql(query, boundValues); d->db->recordChangeset(ImageChangeset(imageID, DatabaseFields::Set(fields))); } void CoreDB::changeVideoMetadata(qlonglong imageId, const QVariantList& infos, DatabaseFields::VideoMetadata fields) { if (fields == DatabaseFields::VideoMetadataNone) { return; } QString query(QString::fromUtf8("UPDATE VideoMetadata SET ")); QStringList fieldNames = videoMetadataFieldList(fields); Q_ASSERT(fieldNames.size() == infos.size()); query += fieldNames.join(QString::fromUtf8("=?,")); query += QString::fromUtf8("=? WHERE imageid=?;"); QVariantList boundValues; boundValues << infos << imageId; d->db->execSql(query, boundValues); d->db->recordChangeset(ImageChangeset(imageId, DatabaseFields::Set(fields))); } void CoreDB::addItemPosition(qlonglong imageID, const QVariantList& infos, DatabaseFields::ItemPositions fields) { if (fields == DatabaseFields::ItemPositionsNone) { return; } QString query(QString::fromUtf8("REPLACE INTO ImagePositions ( imageid, ")); QStringList fieldNames = imagePositionsFieldList(fields); Q_ASSERT(fieldNames.size() == infos.size()); query += fieldNames.join(QLatin1String(", ")); query += QString::fromUtf8(" ) VALUES ("); addBoundValuePlaceholders(query, infos.size() + 1); query += QString::fromUtf8(");"); QVariantList boundValues; boundValues << imageID << infos; d->db->execSql(query, boundValues); d->db->recordChangeset(ImageChangeset(imageID, DatabaseFields::Set(fields))); } void CoreDB::changeItemPosition(qlonglong imageId, const QVariantList& infos, DatabaseFields::ItemPositions fields) { if (fields == DatabaseFields::ItemPositionsNone) { return; } QString query(QString::fromUtf8("UPDATE ImagePositions SET ")); QStringList fieldNames = imagePositionsFieldList(fields); Q_ASSERT(fieldNames.size() == infos.size()); query += fieldNames.join(QString::fromUtf8("=?,")); query += QString::fromUtf8("=? WHERE imageid=?;"); QVariantList boundValues; boundValues << infos << imageId; d->db->execSql(query, boundValues); d->db->recordChangeset(ImageChangeset(imageId, DatabaseFields::Set(fields))); } void CoreDB::removeItemPosition(qlonglong imageid) { d->db->execSql(QString(QString::fromUtf8("DELETE FROM ImagePositions WHERE imageid=?;")), imageid); d->db->recordChangeset(ImageChangeset(imageid, DatabaseFields::Set(DatabaseFields::ItemPositionsAll))); } void CoreDB::removeItemPositionAltitude(qlonglong imageid) { d->db->execSql(QString(QString::fromUtf8("UPDATE ImagePositions SET altitude=NULL WHERE imageid=?;")), imageid); d->db->recordChangeset(ImageChangeset(imageid, DatabaseFields::Set(DatabaseFields::Altitude))); } QList CoreDB::getItemComments(qlonglong imageID) const { QList list; QList values; d->db->execSql(QString::fromUtf8("SELECT id, type, language, author, date, comment " "FROM ImageComments WHERE imageid=?;"), imageID, &values); for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ) { CommentInfo info; info.imageId = imageID; info.id = (*it).toInt(); ++it; info.type = (DatabaseComment::Type)(*it).toInt(); ++it; info.language = (*it).toString(); ++it; info.author = (*it).toString(); ++it; info.date = (*it).toDateTime(); ++it; info.comment = (*it).toString(); ++it; list << info; } return list; } int CoreDB::setImageComment(qlonglong imageID, const QString& comment, DatabaseComment::Type type, const QString& language, const QString& author, const QDateTime& date) const { QVariantList boundValues; boundValues << imageID << (int)type << language << author << date << comment; QVariant id; d->db->execSql(QString::fromUtf8("REPLACE INTO ImageComments " "( imageid, type, language, author, date, comment ) " " VALUES (?,?,?,?,?,?);"), boundValues, nullptr, &id); d->db->recordChangeset(ImageChangeset(imageID, DatabaseFields::Set(DatabaseFields::ItemCommentsAll))); return id.toInt(); } void CoreDB::changeImageComment(int commentId, qlonglong imageID, const QVariantList& infos, DatabaseFields::ItemComments fields) { if (fields == DatabaseFields::ItemCommentsNone) { return; } QString query(QString::fromUtf8("UPDATE ImageComments SET ")); QStringList fieldNames = imageCommentsFieldList(fields); Q_ASSERT(fieldNames.size() == infos.size()); query += fieldNames.join(QString::fromUtf8("=?,")); query += QString::fromUtf8("=? WHERE id=?;"); QVariantList boundValues; boundValues << infos << commentId; d->db->execSql(query, boundValues); d->db->recordChangeset(ImageChangeset(imageID, DatabaseFields::Set(fields))); } void CoreDB::removeImageComment(int commentid, qlonglong imageid) { d->db->execSql(QString::fromUtf8("DELETE FROM ImageComments WHERE id=?;"), commentid); d->db->recordChangeset(ImageChangeset(imageid, DatabaseFields::Set(DatabaseFields::ItemCommentsAll))); } QString CoreDB::getImageProperty(qlonglong imageID, const QString& property) const { QList values; d->db->execSql(QString::fromUtf8("SELECT value FROM ImageProperties " "WHERE imageid=? and property=?;"), imageID, property, &values); if (values.isEmpty()) { return QString(); } return values.first().toString(); } void CoreDB::setImageProperty(qlonglong imageID, const QString& property, const QString& value) { d->db->execSql(QString::fromUtf8("REPLACE INTO ImageProperties " "(imageid, property, value) " " VALUES(?, ?, ?);"), imageID, property, value); } void CoreDB::removeImageProperty(qlonglong imageID, const QString& property) { d->db->execSql(QString::fromUtf8("DELETE FROM ImageProperties WHERE imageid=? AND property=?;"), imageID, property); } void CoreDB::removeImagePropertyByName(const QString& property) { d->db->execSql(QString::fromUtf8("DELETE FROM ImageProperties WHERE property=?;"), property); } QList CoreDB::getItemCopyright(qlonglong imageID, const QString& property) const { QList list; QList values; if (property.isNull()) { d->db->execSql(QString::fromUtf8("SELECT property, value, extraValue FROM ImageCopyright " "WHERE imageid=?;"), imageID, &values); } else { d->db->execSql(QString::fromUtf8("SELECT property, value, extraValue FROM ImageCopyright " "WHERE imageid=? and property=?;"), imageID, property, &values); } for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ) { CopyrightInfo info; info.id = imageID; info.property = (*it).toString(); ++it; info.value = (*it).toString(); ++it; info.extraValue = (*it).toString(); ++it; list << info; } return list; } void CoreDB::setItemCopyrightProperty(qlonglong imageID, const QString& property, const QString& value, const QString& extraValue, CopyrightPropertyUnique uniqueness) { if (uniqueness == PropertyUnique) { d->db->execSql(QString::fromUtf8("DELETE FROM ImageCopyright " "WHERE imageid=? AND property=?;"), imageID, property); } else if (uniqueness == PropertyExtraValueUnique) { d->db->execSql(QString::fromUtf8("DELETE FROM ImageCopyright " "WHERE imageid=? AND property=? AND extraValue=?;"), imageID, property, extraValue); } d->db->execSql(QString::fromUtf8("REPLACE INTO ImageCopyright " "(imageid, property, value, extraValue) " " VALUES(?, ?, ?, ?);"), imageID, property, value, extraValue); } void CoreDB::removeItemCopyrightProperties(qlonglong imageID, const QString& property, const QString& extraValue, const QString& value) { int removeBy = 0; if (!property.isNull()) { ++removeBy; } if (!extraValue.isNull()) { ++removeBy; } if (!value.isNull()) { ++removeBy; } switch (removeBy) { case 0: d->db->execSql(QString::fromUtf8("DELETE FROM ImageCopyright " "WHERE imageid=?;"), imageID); break; case 1: d->db->execSql(QString::fromUtf8("DELETE FROM ImageCopyright " "WHERE imageid=? AND property=?;"), imageID, property); break; case 2: d->db->execSql(QString::fromUtf8("DELETE FROM ImageCopyright " "WHERE imageid=? AND property=? AND extraValue=?;"), imageID, property, extraValue); break; case 3: d->db->execSql(QString::fromUtf8("DELETE FROM ImageCopyright " "WHERE imageid=? AND property=? AND extraValue=? AND value=?;"), imageID, property, extraValue, value); break; } } QList CoreDB::findByNameAndCreationDate(const QString& fileName, const QDateTime& creationDate) const { QList values; d->db->execSql(QString::fromUtf8("SELECT id FROM Images " "LEFT JOIN ImageInformation ON id=imageid " " WHERE name=? AND creationDate=? AND status<3;"), fileName, creationDate, &values); QList ids; foreach (const QVariant& var, values) { ids << var.toLongLong(); } return ids; } bool CoreDB::hasImageHistory(qlonglong imageId) const { QList values; d->db->execSql(QString::fromUtf8("SELECT history FROM ImageHistory WHERE imageid=?;"), imageId, &values); return !values.isEmpty(); } ImageHistoryEntry CoreDB::getItemHistory(qlonglong imageId) const { QList values; d->db->execSql(QString::fromUtf8("SELECT uuid, history FROM ImageHistory WHERE imageid=?;"), imageId, &values); ImageHistoryEntry entry; entry.imageId = imageId; if (values.count() != 2) { return entry; } QList::const_iterator it = values.constBegin(); entry.uuid = (*it).toString(); ++it; entry.history = (*it).toString(); ++it; return entry; } QList CoreDB::getItemsForUuid(const QString& uuid) const { QList values; d->db->execSql(QString::fromUtf8("SELECT imageid FROM ImageHistory " "INNER JOIN Images ON imageid=id " " WHERE uuid=? AND status<3;"), uuid, &values); QList imageIds; if (values.isEmpty()) { return imageIds; } for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ++it) { imageIds << (*it).toInt(); } return imageIds; } QString CoreDB::getImageUuid(qlonglong imageId) const { QList values; d->db->execSql(QString::fromUtf8("SELECT uuid FROM ImageHistory WHERE imageid=?;"), imageId, &values); if (values.isEmpty()) { return QString(); } QString uuid = values.first().toString(); if (uuid.isEmpty()) { return QString(); } return uuid; } void CoreDB::setItemHistory(qlonglong imageId, const QString& history) { d->db->execUpsertDBAction(QLatin1String("changeImageHistory"), imageId, QStringList() << QLatin1String("history"), QVariantList() << history); d->db->recordChangeset(ImageChangeset(imageId, DatabaseFields::Set(DatabaseFields::ImageHistory))); } void CoreDB::setImageUuid(qlonglong imageId, const QString& uuid) { d->db->execUpsertDBAction(QLatin1String("changeImageHistory"), imageId, QStringList() << QLatin1String("uuid"), QVariantList() << uuid); d->db->recordChangeset(ImageChangeset(imageId, DatabaseFields::Set(DatabaseFields::ImageUUID))); } void CoreDB::addImageRelation(qlonglong subjectId, qlonglong objectId, DatabaseRelation::Type type) { d->db->execSql(QString::fromUtf8("REPLACE INTO ImageRelations (subject, object, type) " "VALUES (?, ?, ?);"), subjectId, objectId, type); d->db->recordChangeset(ImageChangeset(QList() << subjectId << objectId, DatabaseFields::Set(DatabaseFields::ImageRelations))); } void CoreDB::addImageRelations(const QList& subjectIds, const QList& objectIds, DatabaseRelation::Type type) { DbEngineSqlQuery query = d->db->prepareQuery(QString::fromUtf8("REPLACE INTO ImageRelations (subject, object, type) " "VALUES (?, ?, ?);")); QVariantList subjects, objects, types; for (int i = 0 ; i < subjectIds.size() ; ++i) { subjects << subjectIds.at(i); objects << objectIds.at(i); types << type; } query.addBindValue(subjects); query.addBindValue(objects); query.addBindValue(types); d->db->execBatch(query); d->db->recordChangeset(ImageChangeset(subjectIds + objectIds, DatabaseFields::Set(DatabaseFields::ImageRelations))); } void CoreDB::addImageRelation(const ImageRelation& relation) { addImageRelation(relation.subjectId, relation.objectId, relation.type); } void CoreDB::removeImageRelation(qlonglong subjectId, qlonglong objectId, DatabaseRelation::Type type) { d->db->execSql(QString::fromUtf8("DELETE FROM ImageRelations WHERE subject=? AND object=? AND type=?;"), subjectId, objectId, type); d->db->recordChangeset(ImageChangeset(QList() << subjectId << objectId, DatabaseFields::Set(DatabaseFields::ImageRelations))); } void CoreDB::removeImageRelation(const ImageRelation& relation) { removeImageRelation(relation.subjectId, relation.objectId, relation.type); } QList CoreDB::removeAllImageRelationsTo(qlonglong objectId, DatabaseRelation::Type type) const { QList affected = getImagesRelatingTo(objectId, type); if (affected.isEmpty()) { return affected; } d->db->execSql(QString::fromUtf8("DELETE FROM ImageRelations WHERE object=? AND type=?;"), objectId, type); d->db->recordChangeset(ImageChangeset(QList() << affected << objectId, DatabaseFields::Set(DatabaseFields::ImageRelations))); return affected; } QList CoreDB::removeAllImageRelationsFrom(qlonglong subjectId, DatabaseRelation::Type type) const { QList affected = getImagesRelatedFrom(subjectId, type); if (affected.isEmpty()) { return affected; } d->db->execSql(QString::fromUtf8("DELETE FROM ImageRelations WHERE subject=? AND type=?;"), subjectId, type); - d->db->recordChangeset(ImageChangeset(QList() << affected << subjectId, + d->db->recordChangeset(ImageChangeset(QList() << affected << subjectId, DatabaseFields::Set(DatabaseFields::ImageRelations))); return affected; } QList CoreDB::getImagesRelatedFrom(qlonglong subjectId, DatabaseRelation::Type type) const { return getRelatedImages(subjectId, true, type, false); } QVector > CoreDB::getImagesRelatedFrom(QList subjectIds, DatabaseRelation::Type type) const { return getRelatedImages(subjectIds, true, type, false); } bool CoreDB::hasImagesRelatedFrom(qlonglong subjectId, DatabaseRelation::Type type) const { // returns 0 or 1 item in list return !getRelatedImages(subjectId, true, type, true).isEmpty(); } QList CoreDB::getImagesRelatingTo(qlonglong objectId, DatabaseRelation::Type type) const { return getRelatedImages(objectId, false, type, false); } QVector > CoreDB::getImagesRelatingTo(QList objectIds, DatabaseRelation::Type type) const { return getRelatedImages(objectIds, false, type, false); } bool CoreDB::hasImagesRelatingTo(qlonglong objectId, DatabaseRelation::Type type) const { // returns 0 or 1 item in list return !getRelatedImages(objectId, false, type, true).isEmpty(); } QList CoreDB::getRelatedImages(qlonglong id, bool fromOrTo, DatabaseRelation::Type type, bool boolean) const { QString sql = d->constructRelatedImagesSQL(fromOrTo, type, boolean); DbEngineSqlQuery query = d->db->prepareQuery(sql); return d->execRelatedImagesQuery(query, id, type); } QVector > CoreDB::getRelatedImages(QList ids, bool fromOrTo, DatabaseRelation::Type type, bool boolean) const { if (ids.isEmpty()) { return QVector >(); } QVector > result(ids.size()); QString sql = d->constructRelatedImagesSQL(fromOrTo, type, boolean); DbEngineSqlQuery query = d->db->prepareQuery(sql); for (int i = 0 ; i < ids.size() ; ++i) { result[i] = d->execRelatedImagesQuery(query, ids[i], type); } return result; } QList > CoreDB::getRelationCloud(qlonglong imageId, DatabaseRelation::Type type) const { QSet todo, done; QSet > pairs; todo << imageId; QString sql = QString::fromUtf8("SELECT subject, object FROM ImageRelations " "INNER JOIN Images AS SubjectImages " "ON ImageRelations.subject=SubjectImages.id " " INNER JOIN Images AS ObjectImages " " ON ImageRelations.object=ObjectImages.id " " WHERE (subject=? OR object=?) %1 " " AND SubjectImages.status<3 " " AND ObjectImages.status<3;"); if (type == DatabaseRelation::UndefinedType) { sql = sql.arg(QString()); } else { sql = sql.arg(QString::fromUtf8("AND type=?")); } DbEngineSqlQuery query = d->db->prepareQuery(sql); QList values; qlonglong subject, object; while (!todo.isEmpty()) { qlonglong id = *todo.begin(); todo.erase(todo.begin()); done << id; if (type == DatabaseRelation::UndefinedType) { d->db->execSql(query, id, id, &values); } else { d->db->execSql(query, id, id, type, &values); } for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ) { subject = (*it).toLongLong(); ++it; object = (*it).toLongLong(); ++it; pairs << qMakePair(subject, object); if (!done.contains(subject)) { todo << subject; } if (!done.contains(object)) { todo << object; } } } return pairs.values(); } QList CoreDB::getOneRelatedImageEach(const QList& ids, DatabaseRelation::Type type) const { QString sql = QString::fromUtf8("SELECT subject, object FROM ImageRelations " "INNER JOIN Images AS SubjectImages " "ON ImageRelations.subject=SubjectImages.id " " INNER JOIN Images AS ObjectImages " " ON ImageRelations.object=ObjectImages.id " " WHERE ( (subject=? AND ObjectImages.status<3) " " OR (object=? AND SubjectImages.status<3) ) " " %1 LIMIT 1;"); if (type == DatabaseRelation::UndefinedType) { sql = sql.arg(QString()); } else { sql = sql.arg(QString::fromUtf8("AND type=?")); } DbEngineSqlQuery query = d->db->prepareQuery(sql); QSet result; QList values; foreach (const qlonglong& id, ids) { if (type == DatabaseRelation::UndefinedType) { d->db->execSql(query, id, id, &values); } else { d->db->execSql(query, id, id, type, &values); } if (values.size() != 2) { continue; } // one of subject and object is the given id, the other our result if (values.first() != id) { result << values.first().toLongLong(); } else { result << values.last().toLongLong(); } } return result.values(); } QList CoreDB::getRelatedImagesToByType(DatabaseRelation::Type type) const { QList values; d->db->execSql(QString::fromUtf8("SELECT object FROM ImageRelations " "INNER JOIN Images AS SubjectImages " "ON ImageRelations.subject=SubjectImages.id " " INNER JOIN Images AS ObjectImages " " ON ImageRelations.object=ObjectImages.id " " WHERE type=? " " AND SubjectImages.status<3 " " AND ObjectImages.status<3;"), (int)type, &values); QList imageIds; if (values.isEmpty()) { return imageIds; } for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ++it) { imageIds << (*it).toLongLong(); } return imageIds; } QStringList CoreDB::getItemsURLsWithTag(int tagId) const { QList values; QList boundValues; QString query(QString::fromUtf8("SELECT DISTINCT Albums.albumRoot, Albums.relativePath, Images.name FROM Images " "LEFT JOIN ImageTags ON Images.id=ImageTags.imageid " "INNER JOIN Albums ON Albums.id=Images.album " " WHERE Images.status=1 AND Images.category=1 AND ")); if (tagId == TagsCache::instance()->tagForPickLabel(NoPickLabel) || tagId == TagsCache::instance()->tagForColorLabel(NoColorLabel)) { query += QString::fromUtf8("( ImageTags.tagid=? OR ImageTags.tagid " "NOT BETWEEN ? AND ? OR ImageTags.tagid IS NULL );"); boundValues << tagId; if (tagId == TagsCache::instance()->tagForPickLabel(NoPickLabel)) { boundValues << TagsCache::instance()->tagForPickLabel(FirstPickLabel); boundValues << TagsCache::instance()->tagForPickLabel(LastPickLabel); } else { boundValues << TagsCache::instance()->tagForColorLabel(FirstColorLabel); boundValues << TagsCache::instance()->tagForColorLabel(LastColorLabel); } } else { query += QString::fromUtf8("ImageTags.tagid=?;"); boundValues << tagId; } d->db->execSql(query, boundValues, &values); QStringList urls; QString albumRootPath, relativePath, name; for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ) { albumRootPath = CollectionManager::instance()->albumRootPath((*it).toInt()); ++it; relativePath = (*it).toString(); ++it; name = (*it).toString(); ++it; if (relativePath == QLatin1String("/")) { urls << albumRootPath + relativePath + name; } else { urls << albumRootPath + relativePath + QLatin1Char('/') + name; } } return urls; } QStringList CoreDB::getDirtyOrMissingFaceImageUrls() const { QList values; d->db->execSql(QString::fromUtf8("SELECT Albums.albumRoot, Albums.relativePath, Images.name FROM Images " "LEFT JOIN ImageScannedMatrix ON Images.id=ImageScannedMatrix.imageid " "INNER JOIN Albums ON Albums.id=Images.album " " WHERE Images.status=1 AND Images.category=1 AND " " ( ImageScannedMatrix.imageid IS NULL " " OR Images.modificationDate != ImageScannedMatrix.modificationDate " " OR Images.uniqueHash != ImageScannedMatrix.uniqueHash );"), &values); QStringList urls; QString albumRootPath, relativePath, name; for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ) { albumRootPath = CollectionManager::instance()->albumRootPath((*it).toInt()); ++it; relativePath = (*it).toString(); ++it; name = (*it).toString(); ++it; if (relativePath == QLatin1String("/")) { urls << albumRootPath + relativePath + name; } else { urls << albumRootPath + relativePath + QLatin1Char('/') + name; } } return urls; } QList CoreDB::getIdenticalFiles(qlonglong id) const { if (!id) { return QList(); } QList values; // retrieve unique hash and file size d->db->execSql(QString::fromUtf8("SELECT uniqueHash, fileSize FROM Images WHERE id=?;"), id, &values); if (values.isEmpty()) { return QList(); } QString uniqueHash = values.at(0).toString(); qlonglong fileSize = values.at(1).toLongLong(); return getIdenticalFiles(uniqueHash, fileSize, id); } QList CoreDB::getIdenticalFiles(const QString& uniqueHash, qlonglong fileSize, qlonglong sourceId) const { // enforce validity if (uniqueHash.isEmpty() || fileSize <= 0) { return QList(); } QList values; // find items with same fingerprint d->db->execSql(QString::fromUtf8("SELECT id, album, name, status, category, modificationDate, fileSize " "FROM Images WHERE fileSize=? AND uniqueHash=? AND album IS NOT NULL;"), fileSize, uniqueHash, &values); QList list; for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ) { ItemScanInfo info; info.id = (*it).toLongLong(); ++it; info.albumID = (*it).toInt(); ++it; info.itemName = (*it).toString(); ++it; info.status = (DatabaseItem::Status)(*it).toInt(); ++it; info.category = (DatabaseItem::Category)(*it).toInt(); ++it; info.modificationDate = (*it).toDateTime(); ++it; info.fileSize = (*it).toLongLong(); ++it; // exclude one source id from list if (sourceId == info.id) { continue; } // same for all here, per definition info.uniqueHash = uniqueHash; list << info; } return list; } QStringList CoreDB::imagesFieldList(DatabaseFields::Images fields) { // adds no spaces at beginning or end QStringList list; if (fields & DatabaseFields::Album) { list << QLatin1String("album"); } if (fields & DatabaseFields::Name) { list << QLatin1String("name"); } if (fields & DatabaseFields::Status) { list << QLatin1String("status"); } if (fields & DatabaseFields::Category) { list << QLatin1String("category"); } if (fields & DatabaseFields::ModificationDate) { list << QLatin1String("modificationDate"); } if (fields & DatabaseFields::FileSize) { list << QLatin1String("fileSize"); } if (fields & DatabaseFields::UniqueHash) { list << QLatin1String("uniqueHash"); } if (fields & DatabaseFields::ManualOrder) { list << QLatin1String("manualOrder"); } return list; } QStringList CoreDB::imageInformationFieldList(DatabaseFields::ItemInformation fields) { // adds no spaces at beginning or end QStringList list; if (fields & DatabaseFields::Rating) { list << QLatin1String("rating"); } if (fields & DatabaseFields::CreationDate) { list << QLatin1String("creationDate"); } if (fields & DatabaseFields::DigitizationDate) { list << QLatin1String("digitizationDate"); } if (fields & DatabaseFields::Orientation) { list << QLatin1String("orientation"); } if (fields & DatabaseFields::Width) { list << QLatin1String("width"); } if (fields & DatabaseFields::Height) { list << QLatin1String("height"); } if (fields & DatabaseFields::Format) { list << QLatin1String("format"); } if (fields & DatabaseFields::ColorDepth) { list << QLatin1String("colorDepth"); } if (fields & DatabaseFields::ColorModel) { list << QLatin1String("colorModel"); } return list; } QStringList CoreDB::videoMetadataFieldList(DatabaseFields::VideoMetadata fields) { // adds no spaces at beginning or end QStringList list; if (fields & DatabaseFields::AspectRatio) { list << QLatin1String("aspectRatio"); } if (fields & DatabaseFields::AudioBitRate) { list << QLatin1String("audioBitRate"); } if (fields & DatabaseFields::AudioChannelType) { list << QLatin1String("audioChannelType"); } if (fields & DatabaseFields::AudioCodec) { list << QLatin1String("audioCompressor"); } if (fields & DatabaseFields::Duration) { list << QLatin1String("duration"); } if (fields & DatabaseFields::FrameRate) { list << QLatin1String("frameRate"); } if (fields & DatabaseFields::VideoCodec) { list << QLatin1String("videoCodec"); } return list; } QStringList CoreDB::imageMetadataFieldList(DatabaseFields::ImageMetadata fields) { // adds no spaces at beginning or end QStringList list; if (fields & DatabaseFields::Make) { list << QLatin1String("make"); } if (fields & DatabaseFields::Model) { list << QLatin1String("model"); } if (fields & DatabaseFields::Lens) { list << QLatin1String("lens"); } if (fields & DatabaseFields::Aperture) { list << QLatin1String("aperture"); } if (fields & DatabaseFields::FocalLength) { list << QLatin1String("focalLength"); } if (fields & DatabaseFields::FocalLength35) { list << QLatin1String("focalLength35"); } if (fields & DatabaseFields::ExposureTime) { list << QLatin1String("exposureTime"); } if (fields & DatabaseFields::ExposureProgram) { list << QLatin1String("exposureProgram"); } if (fields & DatabaseFields::ExposureMode) { list << QLatin1String("exposureMode"); } if (fields & DatabaseFields::Sensitivity) { list << QLatin1String("sensitivity"); } if (fields & DatabaseFields::FlashMode) { list << QLatin1String("flash"); } if (fields & DatabaseFields::WhiteBalance) { list << QLatin1String("whiteBalance"); } if (fields & DatabaseFields::WhiteBalanceColorTemperature) { list << QLatin1String("whiteBalanceColorTemperature"); } if (fields & DatabaseFields::MeteringMode) { list << QLatin1String("meteringMode"); } if (fields & DatabaseFields::SubjectDistance) { list << QLatin1String("subjectDistance"); } if (fields & DatabaseFields::SubjectDistanceCategory) { list << QLatin1String("subjectDistanceCategory"); } return list; } QStringList CoreDB::imagePositionsFieldList(DatabaseFields::ItemPositions fields) { // adds no spaces at beginning or end QStringList list; if (fields & DatabaseFields::Latitude) { list << QLatin1String("latitude"); } if (fields & DatabaseFields::LatitudeNumber) { list << QLatin1String("latitudeNumber"); } if (fields & DatabaseFields::Longitude) { list << QLatin1String("longitude"); } if (fields & DatabaseFields::LongitudeNumber) { list << QLatin1String("longitudeNumber"); } if (fields & DatabaseFields::Altitude) { list << QLatin1String("altitude"); } if (fields & DatabaseFields::PositionOrientation) { list << QLatin1String("orientation"); } if (fields & DatabaseFields::PositionTilt) { list << QLatin1String("tilt"); } if (fields & DatabaseFields::PositionRoll) { list << QLatin1String("roll"); } if (fields & DatabaseFields::PositionAccuracy) { list << QLatin1String("accuracy"); } if (fields & DatabaseFields::PositionDescription) { list << QLatin1String("description"); } return list; } QStringList CoreDB::imageCommentsFieldList(DatabaseFields::ItemComments fields) { // adds no spaces at beginning or end QStringList list; if (fields & DatabaseFields::CommentType) { list << QLatin1String("type"); } if (fields & DatabaseFields::CommentLanguage) { list << QLatin1String("language"); } if (fields & DatabaseFields::CommentAuthor) { list << QLatin1String("author"); } if (fields & DatabaseFields::CommentDate) { list << QLatin1String("date"); } if (fields & DatabaseFields::Comment) { list << QLatin1String("comment"); } return list; } void CoreDB::addBoundValuePlaceholders(QString& query, int count) { // adds no spaces at beginning or end QString questionMarks; questionMarks.reserve(count * 2); QString questionMark(QString::fromUtf8("?,")); for (int i = 0 ; i < count ; ++i) { questionMarks += questionMark; } // remove last ',' questionMarks.chop(1); query += questionMarks; } int CoreDB::findInDownloadHistory(const QString& identifier, const QString& name, qlonglong fileSize, const QDateTime& date) const { QList values; QVariantList boundValues; boundValues << identifier << name << fileSize << date.addSecs(-2) << date.addSecs(2); d->db->execSql(QString::fromUtf8("SELECT id FROM DownloadHistory " " WHERE identifier=? AND filename=? " " AND filesize=? AND (filedate>? " " AND filedatedb->execSql(QString::fromUtf8("REPLACE INTO DownloadHistory " "(identifier, filename, filesize, filedate) " " VALUES (?,?,?,?);"), identifier, name, fileSize, date, nullptr, &id); return id.toInt(); } void CoreDB::addItemTag(qlonglong imageID, int tagID) { d->db->execSql(QString::fromUtf8("REPLACE INTO ImageTags (imageid, tagid) " "VALUES(?, ?);"), imageID, tagID); d->db->recordChangeset(ImageTagChangeset(imageID, tagID, ImageTagChangeset::Added)); //don't save pick or color tags if (TagsCache::instance()->isInternalTag(tagID)) return; //move current tag to front d->recentlyAssignedTags.removeAll(tagID); d->recentlyAssignedTags.prepend(tagID); if (d->recentlyAssignedTags.size() > 10) { d->recentlyAssignedTags.removeLast(); } } void CoreDB::addItemTag(int albumID, const QString& name, int tagID) { // easier because of attributes watch return addItemTag(getImageId(albumID, name), tagID); } void CoreDB::addTagsToItems(QList imageIDs, QList tagIDs) { if (imageIDs.isEmpty() || tagIDs.isEmpty()) { return; } DbEngineSqlQuery query = d->db->prepareQuery(QString::fromUtf8("REPLACE INTO ImageTags (imageid, tagid) " "VALUES(?, ?);")); QVariantList images; QVariantList tags; foreach (const qlonglong& imageid, imageIDs) { foreach (int tagid, tagIDs) { images << imageid; tags << tagid; } } query.addBindValue(images); query.addBindValue(tags); d->db->execBatch(query); d->db->recordChangeset(ImageTagChangeset(imageIDs, tagIDs, ImageTagChangeset::Added)); } QList CoreDB::getRecentlyAssignedTags() const { return d->recentlyAssignedTags; } void CoreDB::removeItemTag(qlonglong imageID, int tagID) { d->db->execSql(QString::fromUtf8("DELETE FROM ImageTags " "WHERE imageID=? AND tagid=?;"), imageID, tagID); d->db->recordChangeset(ImageTagChangeset(imageID, tagID, ImageTagChangeset::Removed)); } void CoreDB::removeItemAllTags(qlonglong imageID, const QList& currentTagIds) { d->db->execSql(QString::fromUtf8("DELETE FROM ImageTags " "WHERE imageID=?;"), imageID); d->db->recordChangeset(ImageTagChangeset(imageID, currentTagIds, ImageTagChangeset::RemovedAll)); } void CoreDB::removeTagsFromItems(QList imageIDs, const QList& tagIDs) { if (imageIDs.isEmpty() || tagIDs.isEmpty()) { return; } DbEngineSqlQuery query = d->db->prepareQuery(QString::fromUtf8("DELETE FROM ImageTags WHERE imageID=? AND tagid=?;")); QVariantList images; QVariantList tags; foreach (const qlonglong& imageid, imageIDs) { foreach (int tagid, tagIDs) { images << imageid; tags << tagid; } } query.addBindValue(images); query.addBindValue(tags); d->db->execBatch(query); d->db->recordChangeset(ImageTagChangeset(imageIDs, tagIDs, ImageTagChangeset::Removed)); } QStringList CoreDB::getItemNamesInAlbum(int albumID, bool recursive) const { QList values; if (recursive) { int rootId = getAlbumRootId(albumID); QString path = getAlbumRelativePath(albumID); d->db->execSql(QString::fromUtf8("SELECT Images.name FROM Images WHERE Images.album IN " " (SELECT DISTINCT id FROM Albums " " WHERE albumRoot=? AND (relativePath=? OR relativePath LIKE ?));"), rootId, path, path == QLatin1String("/") ? QLatin1String("/%") : QString(path + QLatin1String("/%")), &values); } else { d->db->execSql(QString::fromUtf8("SELECT name FROM Images " "WHERE album=?;"), albumID, &values); } QStringList names; for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ++it) { names << it->toString(); } return names; } qlonglong CoreDB::getItemFromAlbum(int albumID, const QString& fileName) const { QList values; d->db->execSql(QString::fromUtf8("SELECT id FROM Images " "WHERE album=? AND name=?;"), albumID, fileName, &values); if (values.isEmpty()) { return -1; } return values.first().toLongLong(); } QList CoreDB::getAllCreationDates() const { QList values; d->db->execSql(QString::fromUtf8("SELECT creationDate FROM ImageInformation " "INNER JOIN Images ON Images.id=ImageInformation.imageid " " WHERE Images.status=1;"), &values); QList list; foreach (const QVariant& value, values) { if (!value.isNull()) { list << value.toDateTime(); } } return list; } QMap CoreDB::getAllCreationDatesAndNumberOfImages() const { QList values; d->db->execSql(QString::fromUtf8("SELECT creationDate FROM ImageInformation " "INNER JOIN Images ON Images.id=ImageInformation.imageid " " WHERE Images.status=1;"), &values); QMap datesStatMap; foreach (const QVariant& value, values) { if (!value.isNull()) { QDateTime dateTime = value.toDateTime(); if (!dateTime.isValid()) { continue; } QMap::iterator it2 = datesStatMap.find(dateTime); if (it2 == datesStatMap.end()) { datesStatMap.insert(dateTime, 1); } else { it2.value()++; } } } return datesStatMap; } int CoreDB::getNumberOfItemsInAlbum(int albumID) const { QList values; d->db->execSql(QString::fromUtf8("SELECT COUNT(id) FROM Images WHERE album=?;"), albumID, &values); if (values.isEmpty()) { return 0; } return values.first().toInt(); } QMap CoreDB::getNumberOfImagesInAlbums() const { QList values, allAbumIDs; QMap albumsStatMap; int albumID, count; // initialize allAbumIDs with all existing albums from db to prevent // wrong album image counters d->db->execSql(QString::fromUtf8("SELECT id from Albums;"), &allAbumIDs); for (QList::const_iterator it = allAbumIDs.constBegin() ; it != allAbumIDs.constEnd() ; ++it) { albumID = (*it).toInt(); albumsStatMap.insert(albumID, 0); } d->db->execSql(QString::fromUtf8("SELECT album, COUNT(*) FROM Images " "WHERE Images.status=1 GROUP BY album;"), &values); for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ) { albumID = (*it).toInt(); ++it; count = (*it).toInt(); ++it; albumsStatMap[albumID] = count; } return albumsStatMap; } QMap CoreDB::getNumberOfImagesInTags() const { QList values, allTagIDs; QMap tagsStatMap; int tagID, count; // initialize allTagIDs with all existing tags from db to prevent // wrong tag counters d->db->execSql(QString::fromUtf8("SELECT id from Tags;"), &allTagIDs); for (QList::const_iterator it = allTagIDs.constBegin() ; it != allTagIDs.constEnd() ; ++it) { tagID = (*it).toInt(); tagsStatMap.insert(tagID, 0); } d->db->execSql(QString::fromUtf8("SELECT tagid, COUNT(*) FROM ImageTags " "LEFT JOIN Images ON Images.id=ImageTags.imageid " " WHERE Images.status=1 GROUP BY tagid;"), &values); for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ) { tagID = (*it).toInt(); ++it; count = (*it).toInt(); ++it; tagsStatMap[tagID] = count; } return tagsStatMap; } QMap CoreDB::getNumberOfImagesInTagProperties(const QString& property) const { QList values; QMap tagsStatMap; int tagID, count; d->db->execSql(QString::fromUtf8("SELECT tagid, COUNT(*) FROM ImageTagProperties " "LEFT JOIN Images ON Images.id=ImageTagProperties.imageid " " WHERE ImageTagProperties.property=? AND Images.status=1 " " GROUP BY tagid;"), property, &values); for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ) { tagID = (*it).toInt(); ++it; count = (*it).toInt(); ++it; tagsStatMap[tagID] = count; } return tagsStatMap; } int CoreDB::getNumberOfImagesInTagProperties(int tagId, const QString& property) const { QList values; d->db->execSql(QString::fromUtf8("SELECT COUNT(*) FROM ImageTagProperties " "LEFT JOIN Images ON Images.id=ImageTagProperties.imageid " " WHERE ImageTagProperties.property=? AND Images.status=1 " " AND ImageTagProperties.tagid=?;"), property, tagId, &values); if (values.isEmpty()) { return 0; } return values.first().toInt(); } QList CoreDB::getImagesWithImageTagProperty(int tagId, const QString& property) const { QList values; QList imageIds; d->db->execSql(QString::fromUtf8("SELECT DISTINCT Images.id FROM ImageTagProperties " "LEFT JOIN Images ON Images.id=ImageTagProperties.imageid " " WHERE ImageTagProperties.property=? AND Images.status=1 " " AND ImageTagProperties.tagid=?;"), property, tagId, &values); for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ++it) { imageIds.append((*it).toInt()); } return imageIds; } QList CoreDB::getImagesWithProperty(const QString& property) const { QList values; QList imageIds; d->db->execSql(QString::fromUtf8("SELECT DISTINCT Images.id FROM ImageTagProperties " "LEFT JOIN Images ON Images.id=ImageTagProperties.imageid " " WHERE ImageTagProperties.property=? AND Images.status=1;"), property, &values); for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ++it) { imageIds.append((*it).toInt()); } return imageIds; } QMap CoreDB::getFormatStatistics() const { return getFormatStatistics(DatabaseItem::UndefinedCategory); } QMap CoreDB::getFormatStatistics(DatabaseItem::Category category) const { QMap map; QString queryString = QString::fromUtf8("SELECT COUNT(*), II.format " "FROM ImageInformation AS II " "INNER JOIN Images ON II.imageid=Images.id " " WHERE Images.status=1 "); if (category != DatabaseItem::UndefinedCategory) { queryString.append(QString::fromUtf8("AND Images.category=%1 ").arg(category)); } queryString.append(QString::fromUtf8("GROUP BY II.format;")); qCDebug(DIGIKAM_DATABASE_LOG) << queryString; DbEngineSqlQuery query = d->db->prepareQuery(queryString); if (d->db->exec(query)) { while (query.next()) { QString quantity = query.value(0).toString(); QString format = query.value(1).toString(); if (format.isEmpty()) { continue; } map[format] = quantity.isEmpty() ? 0 : quantity.toInt(); } } return map; } QStringList CoreDB::getListFromImageMetadata(DatabaseFields::ImageMetadata field) const { QStringList list; QList values; QStringList fieldName = imageMetadataFieldList(field); if (fieldName.count() != 1) { return list; } QString sql = QString::fromUtf8("SELECT DISTINCT %1 FROM ImageMetadata " "INNER JOIN Images ON imageid=Images.id;"); sql = sql.arg(fieldName.first()); d->db->execSql(sql, &values); for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ++it) { if (!it->isNull()) { list << it->toString(); } } return list; } int CoreDB::getAlbumForPath(int albumRootId, const QString& folder, bool create) const { QList values; d->db->execSql(QString::fromUtf8("SELECT id FROM Albums WHERE albumRoot=? AND relativePath=?;"), albumRootId, folder, &values); int albumID = -1; if (values.isEmpty()) { if (create) { albumID = addAlbum(albumRootId, folder, QString(), QDate::currentDate(), QString()); } } else { albumID = values.first().toInt(); } return albumID; } QList CoreDB::getAlbumAndSubalbumsForPath(int albumRootId, const QString& relativePath) const { QList values; d->db->execSql(QString::fromUtf8("SELECT id, relativePath FROM Albums " "WHERE albumRoot=? AND (relativePath=? OR relativePath LIKE ?);"), albumRootId, relativePath, (relativePath == QLatin1String("/") ? QLatin1String("/%") : QString(relativePath + QLatin1String("/%"))), &values); QList albumIds; int id; QString albumRelativePath; for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ) { id = (*it).toInt(); ++it; albumRelativePath = (*it).toString(); ++it; // bug #223050: The LIKE operator is case insensitive if (albumRelativePath.startsWith(relativePath)) { albumIds << id; } } return albumIds; } QList CoreDB::getAlbumsOnAlbumRoot(int albumRootId) const { QList values; d->db->execSql(QString::fromUtf8("SELECT id FROM Albums WHERE albumRoot=?;"), albumRootId, &values); QList albumIds; for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ++it) { albumIds << (*it).toInt(); } return albumIds; } qlonglong CoreDB::addItem(int albumID, const QString& name, DatabaseItem::Status status, DatabaseItem::Category category, const QDateTime& modificationDate, qlonglong fileSize, const QString& uniqueHash) const { QVariantList boundValues; boundValues << albumID << name << (int)status << (int)category << modificationDate << fileSize << uniqueHash; QVariant id; d->db->execSql(QString::fromUtf8("REPLACE INTO Images " "( album, name, status, category, modificationDate, fileSize, uniqueHash ) " " VALUES (?,?,?,?,?,?,?);"), boundValues, nullptr, &id); if (id.isNull()) { return -1; } d->db->recordChangeset(ImageChangeset(id.toLongLong(), DatabaseFields::Set(DatabaseFields::ImagesAll))); - d->db->recordChangeset(CollectionImageChangeset(id.toLongLong(), albumID, + d->db->recordChangeset(CollectionImageChangeset(id.toLongLong(), albumID, CollectionImageChangeset::Added)); return id.toLongLong(); } void CoreDB::updateItem(qlonglong imageID, DatabaseItem::Category category, const QDateTime& modificationDate, qlonglong fileSize, const QString& uniqueHash) { QVariantList boundValues; boundValues << (int)category << modificationDate << fileSize << uniqueHash << imageID; d->db->execSql(QString::fromUtf8("UPDATE Images SET category=?, modificationDate=?, fileSize=?, uniqueHash=? " "WHERE id=?;"), boundValues); - d->db->recordChangeset(ImageChangeset(imageID, + d->db->recordChangeset(ImageChangeset(imageID, DatabaseFields::Set(DatabaseFields::Category | DatabaseFields::ModificationDate | DatabaseFields::FileSize | DatabaseFields::UniqueHash))); } void CoreDB::setItemStatus(qlonglong imageID, DatabaseItem::Status status) { QVariantList boundValues; boundValues << (int)status << imageID; d->db->execSql(QString::fromUtf8("UPDATE Images SET status=? WHERE id=?;"), boundValues); d->db->recordChangeset(ImageChangeset(imageID, DatabaseFields::Set(DatabaseFields::Status))); } void CoreDB::setItemAlbum(qlonglong imageID, qlonglong album) { QVariantList boundValues; boundValues << album << imageID; d->db->execSql(QString::fromUtf8("UPDATE Images SET album=? WHERE id=?;"), boundValues); // record that the image was assigned a new album d->db->recordChangeset(ImageChangeset(imageID, DatabaseFields::Set(DatabaseFields::Album))); // also record that the collection was changed by adding an image to an album. d->db->recordChangeset(CollectionImageChangeset(imageID, album, CollectionImageChangeset::Added)); } void CoreDB::setItemManualOrder(qlonglong imageID, qlonglong value) { QVariantList boundValues; boundValues << value << imageID; d->db->execSql(QString::fromUtf8("UPDATE Images SET manualOrder=? WHERE id=?;"), boundValues); d->db->recordChangeset(ImageChangeset(imageID, DatabaseFields::Set(DatabaseFields::ManualOrder))); } void CoreDB::setItemModificationDate(qlonglong imageID, const QDateTime& modificationDate) { QVariantList boundValues; boundValues << modificationDate << imageID; d->db->execSql(QString::fromUtf8("UPDATE Images SET modificationDate=? WHERE id=?;"), boundValues); d->db->recordChangeset(ImageChangeset(imageID, DatabaseFields::Set(DatabaseFields::ModificationDate))); } void CoreDB::renameItem(qlonglong imageID, const QString& newName) { d->db->execSql(QString::fromUtf8("UPDATE Images SET name=? WHERE id=?;"), newName, imageID); } int CoreDB::getItemAlbum(qlonglong imageID) const { QList values; d->db->execSql(QString::fromUtf8("SELECT album FROM Images WHERE id=?;"), imageID, &values); if (values.isEmpty()) { return 1; } return values.first().toInt(); } QString CoreDB::getItemName(qlonglong imageID) const { QList values; d->db->execSql(QString::fromUtf8("SELECT name FROM Images WHERE id=?;"), imageID, &values); if (values.isEmpty()) { return QString(); } return values.first().toString(); } QStringList CoreDB::getItemURLsInAlbum(int albumID, ItemSortOrder sortOrder) const { QList values; int albumRootId = getAlbumRootId(albumID); if (albumRootId == -1) { return QStringList(); } QString albumRootPath = CollectionManager::instance()->albumRootPath(albumRootId); if (albumRootPath.isNull()) { return QStringList(); } QMap bindingMap; bindingMap.insert(QString::fromUtf8(":albumID"), albumID); switch (sortOrder) { case ByItemName: d->db->execDBAction(d->db->getDBAction(QString::fromUtf8("getItemURLsInAlbumByItemName")), bindingMap, &values); break; case ByItemPath: // Don't collate on the path - this is to maintain the same behavior // that happens when sort order is "By Path" d->db->execDBAction(d->db->getDBAction(QString::fromUtf8("getItemURLsInAlbumByItemPath")), bindingMap, &values); break; case ByItemDate: d->db->execDBAction(d->db->getDBAction(QString::fromUtf8("getItemURLsInAlbumByItemDate")), bindingMap, &values); break; case ByItemRating: d->db->execDBAction(d->db->getDBAction(QString::fromUtf8("getItemURLsInAlbumByItemRating")), bindingMap, &values); break; case NoItemSorting: default: d->db->execDBAction(d->db->getDBAction(QString::fromUtf8("getItemURLsInAlbumNoItemSorting")), bindingMap, &values); break; } QStringList urls; QString relativePath, name; for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ) { relativePath = (*it).toString(); ++it; name = (*it).toString(); ++it; if (relativePath == QLatin1String("/")) { urls << albumRootPath + relativePath + name; } else { urls << albumRootPath + relativePath + QLatin1Char('/') + name; } } return urls; } QList CoreDB::getItemIDsInAlbum(int albumID) const { QList itemIDs; QList values; d->db->execSql(QString::fromUtf8("SELECT id FROM Images WHERE album=?;"), albumID, &values); for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ++it) { itemIDs << (*it).toLongLong(); } return itemIDs; } QMap CoreDB::getItemIDsAndURLsInAlbum(int albumID) const { int albumRootId = getAlbumRootId(albumID); if (albumRootId == -1) { return QMap(); } QString albumRootPath = CollectionManager::instance()->albumRootPath(albumRootId); if (albumRootPath.isNull()) { return QMap(); } QMap itemsMap; QList values; d->db->execSql(QString::fromUtf8("SELECT Images.id, Albums.relativePath, Images.name " "FROM Images " " INNER JOIN Albums ON Albums.id=Images.album " " WHERE Albums.id=?;"), albumID, &values); QString path; qlonglong id; QString relativePath, name; for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ) { id = (*it).toLongLong(); ++it; relativePath = (*it).toString(); ++it; name = (*it).toString(); ++it; if (relativePath == QLatin1String("/")) { path = albumRootPath + relativePath + name; } else { path = albumRootPath + relativePath + QLatin1Char('/') + name; } itemsMap.insert(id, path); }; return itemsMap; } QList CoreDB::getAllItems() const { QList values; d->db->execSql(QString::fromUtf8("SELECT id FROM Images;"), &values); QList items; foreach (const QVariant& item, values) { items << item.toLongLong(); } return items; } QHash > CoreDB::getAllItemsWithAlbum() const { QList values; d->db->execSql(QString::fromUtf8("SELECT Images.id, Albums.albumRoot, Albums.id FROM Images " "INNER JOIN Albums ON Albums.id=Images.album " " WHERE Images.status<3;"), &values); QHash > itemAlbumHash; for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ) { qlonglong id = (*it).toLongLong(); ++it; int albumRoot = (*it).toInt(); ++it; int album = (*it).toInt(); ++it; itemAlbumHash[id] = qMakePair(albumRoot, album); } return itemAlbumHash; } QList CoreDB::getItemScanInfos(int albumID) const { QList list; QString sql = QString::fromUtf8("SELECT id, album, name, status, category, modificationDate, fileSize, uniqueHash " "FROM Images WHERE album=?;"); DbEngineSqlQuery query = d->db->prepareQuery(sql); query.addBindValue(albumID); if (d->db->exec(query)) { while (query.next()) { ItemScanInfo info; info.id = query.value(0).toLongLong(); info.albumID = query.value(1).toInt(); info.itemName = query.value(2).toString(); info.status = (DatabaseItem::Status)query.value(3).toInt(); info.category = (DatabaseItem::Category)query.value(4).toInt(); info.modificationDate = query.value(5).toDateTime(); info.fileSize = query.value(6).toLongLong(); info.uniqueHash = query.value(7).toString(); list << info; } } return list; } ItemScanInfo CoreDB::getItemScanInfo(qlonglong imageID) const { QList values; d->db->execSql(QString::fromUtf8("SELECT id, album, name, status, category, modificationDate, fileSize, uniqueHash " "FROM Images WHERE id=?;"), imageID, &values); ItemScanInfo info; if (!values.isEmpty()) { QList::const_iterator it = values.constBegin(); info.id = (*it).toLongLong(); ++it; info.albumID = (*it).toInt(); ++it; info.itemName = (*it).toString(); ++it; info.status = (DatabaseItem::Status)(*it).toInt(); ++it; info.category = (DatabaseItem::Category)(*it).toInt(); ++it; info.modificationDate = (*it).toDateTime(); ++it; info.fileSize = (*it).toLongLong(); ++it; info.uniqueHash = (*it).toString(); ++it; } return info; } QStringList CoreDB::getItemURLsInTag(int tagID, bool recursive) const { QList values; QMap bindingMap; bindingMap.insert(QString::fromUtf8(":tagID"), tagID); bindingMap.insert(QString::fromUtf8(":tagID2"), tagID); if (recursive) { d->db->execDBAction(d->db->getDBAction(QString::fromUtf8("GetItemURLsInTagRecursive")), bindingMap, &values); } else { d->db->execDBAction(d->db->getDBAction(QString::fromUtf8("GetItemURLsInTag")), bindingMap, &values); } QStringList urls; QString albumRootPath, relativePath, name; for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ) { albumRootPath = CollectionManager::instance()->albumRootPath((*it).toInt()); ++it; relativePath = (*it).toString(); ++it; name = (*it).toString(); ++it; if (relativePath == QLatin1String("/")) { urls << albumRootPath + relativePath + name; } else { urls << albumRootPath + relativePath + QLatin1Char('/') + name; } } return urls; } QList CoreDB::getItemIDsInTag(int tagID, bool recursive) const { QList itemIDs; QList values; QMap parameters; parameters.insert(QString::fromUtf8(":tagPID"), tagID); parameters.insert(QString::fromUtf8(":tagID"), tagID); if (recursive) { d->db->execDBAction(d->db->getDBAction(QString::fromUtf8("getItemIDsInTagRecursive")), parameters, &values); } else { d->db->execDBAction(d->db->getDBAction(QString::fromUtf8("getItemIDsInTag")), parameters, &values); } for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ++it) { itemIDs << (*it).toLongLong(); } return itemIDs; } QString CoreDB::getAlbumRelativePath(int albumID) const { QList values; d->db->execSql(QString::fromUtf8("SELECT relativePath from Albums WHERE id=?;"), albumID, &values); if (values.isEmpty()) { return QString(); } return values.first().toString(); } int CoreDB::getAlbumRootId(int albumID) const { QList values; d->db->execSql(QString::fromUtf8("SELECT albumRoot FROM Albums WHERE id=?;"), albumID, &values); if (values.isEmpty()) { return -1; } return values.first().toInt(); } QDate CoreDB::getAlbumLowestDate(int albumID) const { QList values; d->db->execSql(QString::fromUtf8("SELECT MIN(creationDate) FROM ImageInformation " "INNER JOIN Images ON Images.id=ImageInformation.imageid " " WHERE Images.album=? GROUP BY Images.album;"), albumID, &values); if (values.isEmpty()) { return QDate(); } return values.first().toDate(); } QDate CoreDB::getAlbumHighestDate(int albumID) const { QList values; d->db->execSql(QString::fromUtf8("SELECT MAX(creationDate) FROM ImageInformation " "INNER JOIN Images ON Images.id=ImageInformation.imageid " " WHERE Images.album=? GROUP BY Images.album;"), albumID , &values); if (values.isEmpty()) { return QDate(); } return values.first().toDate(); } QDate CoreDB::getAlbumAverageDate(int albumID) const { QList values; d->db->execSql(QString::fromUtf8("SELECT creationDate FROM ImageInformation " "INNER JOIN Images ON Images.id=ImageInformation.imageid " " WHERE Images.album=?;"), albumID , &values); QList dates; for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ++it) { QDateTime itemDateTime = (*it).toDateTime(); if (itemDateTime.isValid()) { dates << itemDateTime.date(); } } if (dates.isEmpty()) { return QDate(); } qint64 julianDays = 0; foreach (const QDate& date, dates) { julianDays += date.toJulianDay(); } return QDate::fromJulianDay(julianDays / dates.size()); } void CoreDB::deleteItem(int albumID, const QString& file) { qlonglong imageId = getImageId(albumID, file); if (imageId == -1) { return; } d->db->execSql(QString::fromUtf8("DELETE FROM Images WHERE id=?;"), imageId); d->db->recordChangeset(CollectionImageChangeset(imageId, albumID, CollectionImageChangeset::Deleted)); } void CoreDB::deleteItem(qlonglong imageId) { d->db->execSql(QString::fromUtf8("DELETE FROM Images WHERE id=? AND album IS NULL;"), imageId); } void CoreDB::removeItemsFromAlbum(int albumID, const QList& ids_forInformation) { d->db->execSql(QString::fromUtf8("UPDATE Images SET status=?, album=NULL WHERE album=?;"), (int)DatabaseItem::Trashed, albumID); d->db->recordChangeset(CollectionImageChangeset(ids_forInformation, albumID, CollectionImageChangeset::RemovedAll)); } void CoreDB::removeItems(QList itemIDs, const QList& albumIDs) { DbEngineSqlQuery query = d->db->prepareQuery(QString::fromUtf8("UPDATE Images SET status=?, album=NULL WHERE id=?;")); QVariantList imageIds; QVariantList status; foreach (const qlonglong& id, itemIDs) { status << (int)DatabaseItem::Trashed; imageIds << id; } query.addBindValue(status); query.addBindValue(imageIds); d->db->execBatch(query); d->db->recordChangeset(CollectionImageChangeset(itemIDs, albumIDs, CollectionImageChangeset::Removed)); } void CoreDB::removeItemsPermanently(QList itemIDs, const QList& albumIDs) { DbEngineSqlQuery query = d->db->prepareQuery(QString::fromUtf8("UPDATE Images SET status=?, album=NULL WHERE id=?;")); QVariantList imageIds; QVariantList status; foreach (const qlonglong& id, itemIDs) { status << (int)DatabaseItem::Obsolete; imageIds << id; } query.addBindValue(status); query.addBindValue(imageIds); d->db->execBatch(query); d->db->recordChangeset(CollectionImageChangeset(itemIDs, albumIDs, CollectionImageChangeset::Removed)); } void CoreDB::deleteRemovedItems() { d->db->execSql(QString::fromUtf8("DELETE FROM Images WHERE status=?;"), (int)DatabaseItem::Obsolete); d->db->recordChangeset(CollectionImageChangeset(QList(), QList(), CollectionImageChangeset::RemovedDeleted)); } void CoreDB::renameAlbum(int albumID, int newAlbumRoot, const QString& newRelativePath) { int albumRoot = getAlbumRootId(albumID); QString relativePath = getAlbumRelativePath(albumID); if (relativePath == newRelativePath && albumRoot == newAlbumRoot) { return; } // first delete any stale albums left behind at the destination of renaming QMap parameters; parameters.insert(QString::fromUtf8(":albumRoot"), newAlbumRoot); parameters.insert(QString::fromUtf8(":relativePath"), newRelativePath); if (BdEngineBackend::NoErrors != d->db->execDBAction(d->db->getDBAction(QString::fromUtf8("deleteAlbumRootPath")), parameters)) { return; } // now update the album d->db->execSql(QString::fromUtf8("UPDATE Albums SET albumRoot=?, relativePath=? WHERE id=? AND albumRoot=?;"), newAlbumRoot, newRelativePath, albumID, albumRoot); d->db->recordChangeset(AlbumChangeset(albumID, AlbumChangeset::Renamed)); } void CoreDB::setTagName(int tagID, const QString& name) { d->db->execSql(QString::fromUtf8("UPDATE Tags SET name=? WHERE id=?;"), name, tagID); d->db->recordChangeset(TagChangeset(tagID, TagChangeset::Renamed)); } void CoreDB::moveItem(int srcAlbumID, const QString& srcName, int dstAlbumID, const QString& dstName) { // find id of src image qlonglong imageId = getImageId(srcAlbumID, srcName); if (imageId == -1) { return; } // first delete any stale database entries (for destination) if any deleteItem(dstAlbumID, dstName); d->db->execSql(QString::fromUtf8("UPDATE Images SET album=?, name=? " "WHERE id=?;"), dstAlbumID, dstName, imageId); d->db->recordChangeset(ImageChangeset(imageId, DatabaseFields::Set(DatabaseFields::Album))); d->db->recordChangeset(CollectionImageChangeset(imageId, srcAlbumID, CollectionImageChangeset::Moved)); d->db->recordChangeset(CollectionImageChangeset(imageId, srcAlbumID, CollectionImageChangeset::Removed)); d->db->recordChangeset(CollectionImageChangeset(imageId, dstAlbumID, CollectionImageChangeset::Added)); } int CoreDB::copyItem(int srcAlbumID, const QString& srcName, int dstAlbumID, const QString& dstName) { // find id of src image qlonglong srcId = getImageId(srcAlbumID, srcName); if (srcId == -1 || dstAlbumID == -1 || dstName.isEmpty()) { return -1; } // check for src == dest if (srcAlbumID == dstAlbumID && srcName == dstName) { return srcId; } // first delete any stale database entries if any deleteItem(dstAlbumID, dstName); // copy entry in Images table QVariant id; d->db->execSql(QString::fromUtf8("INSERT INTO Images " "( album, name, status, category, modificationDate, fileSize, uniqueHash ) " " SELECT ?, ?, status, category, modificationDate, fileSize, uniqueHash " " FROM Images WHERE id=?;"), dstAlbumID, dstName, srcId, nullptr, &id); if (id.isNull()) { return -1; } d->db->recordChangeset(ImageChangeset(id.toLongLong(), DatabaseFields::Set(DatabaseFields::ImagesAll))); d->db->recordChangeset(CollectionImageChangeset(id.toLongLong(), srcAlbumID, CollectionImageChangeset::Copied)); d->db->recordChangeset(CollectionImageChangeset(id.toLongLong(), dstAlbumID, CollectionImageChangeset::Added)); // copy all other tables copyImageAttributes(srcId, id.toLongLong()); return id.toLongLong(); } void CoreDB::copyImageAttributes(qlonglong srcId, qlonglong dstId) { // Go through all image-specific tables and copy the entries DatabaseFields::Set fields; d->db->execSql(QString::fromUtf8("REPLACE INTO ImageInformation " "(imageid, rating, creationDate, digitizationDate, orientation, " " width, height, format, colorDepth, colorModel) " "SELECT ?, rating, creationDate, digitizationDate, orientation, " " width, height, format, colorDepth, colorModel " "FROM ImageInformation WHERE imageid=?;"), dstId, srcId); fields |= DatabaseFields::ItemInformationAll; d->db->execSql(QString::fromUtf8("REPLACE INTO ImageMetadata " "(imageid, make, model, lens, aperture, focalLength, focalLength35, " " exposureTime, exposureProgram, exposureMode, sensitivity, flash, whiteBalance, " " whiteBalanceColorTemperature, meteringMode, subjectDistance, subjectDistanceCategory) " "SELECT ?, make, model, lens, aperture, focalLength, focalLength35, " " exposureTime, exposureProgram, exposureMode, sensitivity, flash, whiteBalance, " " whiteBalanceColorTemperature, meteringMode, subjectDistance, subjectDistanceCategory " "FROM ImageMetadata WHERE imageid=?;"), dstId, srcId); fields |= DatabaseFields::ImageMetadataAll; d->db->execSql(QString::fromUtf8("REPLACE INTO VideoMetadata " "(imageid, aspectRatio, audioBitRate, audioChannelType, audioCompressor, duration, " " frameRate, videoCodec) " "SELECT ?, aspectRatio, audioBitRate, audioChannelType, audioCompressor, duration, " " frameRate, videoCodec " "FROM VideoMetadata WHERE imageid=?;"), dstId, srcId); fields |= DatabaseFields::VideoMetadataAll; d->db->execSql(QString::fromUtf8("REPLACE INTO ImagePositions " "(imageid, latitude, latitudeNumber, longitude, longitudeNumber, " " altitude, orientation, tilt, roll, accuracy, description) " "SELECT ?, latitude, latitudeNumber, longitude, longitudeNumber, " " altitude, orientation, tilt, roll, accuracy, description " "FROM ImagePositions WHERE imageid=?;"), dstId, srcId); fields |= DatabaseFields::ItemPositionsAll; d->db->execSql(QString::fromUtf8("REPLACE INTO ImageComments " "(imageid, type, language, author, date, comment) " "SELECT ?, type, language, author, date, comment " "FROM ImageComments WHERE imageid=?;"), dstId, srcId); fields |= DatabaseFields::ItemCommentsAll; d->db->execSql(QString::fromUtf8("REPLACE INTO ImageCopyright " "(imageid, property, value, extraValue) " "SELECT ?, property, value, extraValue " "FROM ImageCopyright WHERE imageid=?;"), dstId, srcId); d->db->execSql(QString::fromUtf8("REPLACE INTO ImageHistory " "(imageid, uuid, history) " "SELECT ?, uuid, history " "FROM ImageHistory WHERE imageid=?;"), dstId, srcId); fields |= DatabaseFields::ImageHistoryInfoAll; d->db->execSql(QString::fromUtf8("REPLACE INTO ImageRelations " "(subject, object, type) " "SELECT ?, object, type " "FROM ImageRelations WHERE subject=?;"), dstId, srcId); d->db->execSql(QString::fromUtf8("REPLACE INTO ImageRelations " "(subject, object, type) " "SELECT subject, ?, type " "FROM ImageRelations WHERE object=?;"), dstId, srcId); fields |= DatabaseFields::ImageRelations; d->db->recordChangeset(ImageChangeset(dstId, fields)); copyImageTags(srcId, dstId); copyImageProperties(srcId, dstId); } void CoreDB::copyImageProperties(qlonglong srcId, qlonglong dstId) { d->db->execSql(QString::fromUtf8("REPLACE INTO ImageProperties " "(imageid, property, value) " "SELECT ?, property, value " "FROM ImageProperties WHERE imageid=?;"), dstId, srcId); } void CoreDB::copyImageTags(qlonglong srcId, qlonglong dstId) { d->db->execSql(QString::fromUtf8("REPLACE INTO ImageTags " "(imageid, tagid) " "SELECT ?, tagid " "FROM ImageTags WHERE imageid=?;"), dstId, srcId); d->db->execSql(QString::fromUtf8("REPLACE INTO ImageTagProperties " "(imageid, tagid, property, value) " "SELECT ?, tagid, property, value " "FROM ImageTagProperties WHERE imageid=?;"), dstId, srcId); // leave empty tag list for now d->db->recordChangeset(ImageTagChangeset(dstId, QList(), ImageTagChangeset::Added)); d->db->recordChangeset(ImageTagChangeset(dstId, QList(), ImageTagChangeset::PropertiesChanged)); } bool CoreDB::copyAlbumProperties(int srcAlbumID, int dstAlbumID) const { if (srcAlbumID == dstAlbumID) { return true; } QList values; d->db->execSql(QString::fromUtf8("SELECT date, caption, collection, icon " "FROM Albums WHERE id=?;"), srcAlbumID, &values); if (values.isEmpty()) { qCWarning(DIGIKAM_DATABASE_LOG) << " src album ID " << srcAlbumID << " does not exist"; return false; } QList boundValues; boundValues << values.at(0) << values.at(1) << values.at(2) << values.at(3); boundValues << dstAlbumID; d->db->execSql(QString::fromUtf8("UPDATE Albums SET date=?, caption=?, " "collection=?, icon=? WHERE id=?;"), boundValues); return true; } QList CoreDB::getImageIdsFromArea(qreal lat1, qreal lat2, qreal lng1, qreal lng2, int /*sortMode*/, const QString& /*sortBy*/) const { QList values; QList boundValues; boundValues << lat1 << lat2 << lng1 << lng2; d->db->execSql(QString::fromUtf8("Select ImageInformation.imageid, ImageInformation.rating, " "ImagePositions.latitudeNumber, ImagePositions.longitudeNumber " "FROM ImageInformation INNER JOIN ImagePositions " " ON ImageInformation.imageid = ImagePositions.imageid " " WHERE (ImagePositions.latitudeNumber>? AND ImagePositions.latitudeNumber? AND ImagePositions.longitudeNumberdb->execSql(QString::fromUtf8("DELETE FROM ImagePositions WHERE imageid=?;"), imageID); fields |= DatabaseFields::ItemPositionsAll; d->db->execSql(QString::fromUtf8("DELETE FROM ImageCopyright WHERE imageid=?;"), imageID); d->db->execSql(QString::fromUtf8("DELETE FROM ImageComments WHERE imageid=?;"), imageID); fields |= DatabaseFields::ItemCommentsAll; d->db->execSql(QString::fromUtf8("DELETE FROM ImageMetadata WHERE imageid=?;"), imageID); fields |= DatabaseFields::ImageMetadataAll; d->db->execSql(QString::fromUtf8("DELETE FROM VideoMetadata WHERE imageid=?;"), imageID); fields |= DatabaseFields::VideoMetadataAll; d->db->recordChangeset(ImageChangeset(imageID, fields)); QList tagIds = getItemTagIDs(imageID); if (!tagIds.isEmpty()) { d->db->execSql(QString::fromUtf8("DELETE FROM ImageTags WHERE imageid=?;"), imageID); d->db->recordChangeset(ImageTagChangeset(imageID, tagIds, ImageTagChangeset::RemovedAll)); } QList properties = getImageTagProperties(imageID); if (!properties.isEmpty()) { QList tids; foreach (const ImageTagProperty& property, properties) { tids << property.tagId; } d->db->execSql(QString::fromUtf8("DELETE FROM ImageTagProperties WHERE imageid=?;"), imageID); d->db->recordChangeset(ImageTagChangeset(imageID, tids, ImageTagChangeset::PropertiesChanged)); } } bool CoreDB::integrityCheck() const { QList values; d->db->execDBAction(d->db->getDBAction(QString::fromUtf8("checkCoreDbIntegrity")), &values); switch (d->db->databaseType()) { case BdEngineBackend::DbType::SQLite: // For SQLite the integrity check returns a single row with one string column "ok" on success and multiple rows on error. return values.size() == 1 && values.first().toString().toLower().compare(QLatin1String("ok")) == 0; case BdEngineBackend::DbType::MySQL: // For MySQL, for every checked table, the table name, operation (check), message type (status) and the message text (ok on success) // are returned. So we check if there are four elements and if yes, whether the fourth element is "ok". //qCDebug(DIGIKAM_DATABASE_LOG) << "MySQL check returned " << values.size() << " rows"; if ((values.size() % 4) != 0) { return false; } for (QList::iterator it = values.begin() ; it != values.end() ; ) { QString tableName = (*it).toString(); ++it; QString operation = (*it).toString(); ++it; QString messageType = (*it).toString(); ++it; QString messageText = (*it).toString(); ++it; if (messageText.toLower().compare(QLatin1String("ok")) != 0) { qCDebug(DIGIKAM_DATABASE_LOG) << "Failed integrity check for table " << tableName << ". Reason:" << messageText; return false; } else { //qCDebug(DIGIKAM_DATABASE_LOG) << "Passed integrity check for table " << tableName; } } // No error conditions. Db passed the integrity check. return true; default: return false; } } void CoreDB::vacuum() { d->db->execDBAction(d->db->getDBAction(QString::fromUtf8("vacuumCoreDB"))); } void CoreDB::readSettings() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(d->configGroupName); d->recentlyAssignedTags = group.readEntry(d->configRecentlyUsedTags, QList()); } void CoreDB::writeSettings() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(d->configGroupName); group.writeEntry(d->configRecentlyUsedTags, d->recentlyAssignedTags); } } // namespace Digikam diff --git a/core/libs/database/coredb/coredbalbuminfo.h b/core/libs/database/coredb/coredbalbuminfo.h index 57b6b0882f..fa26b966f2 100644 --- a/core/libs/database/coredb/coredbalbuminfo.h +++ b/core/libs/database/coredb/coredbalbuminfo.h @@ -1,452 +1,452 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2007-04-21 * Description : Structures to define Albums used in CoreDb * * Copyright (C) 2007-2011 by Marcel Wiesweg * Copyright (C) 2005 by Renchi Raju * Copyright (C) 2006-2020 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. * * ============================================================ */ #ifndef DIGIKAM_CORE_DB_ALBUM_INFO_H #define DIGIKAM_CORE_DB_ALBUM_INFO_H // Qt includes #include #include #include // Local includes #include "coredbconstants.h" namespace Digikam { typedef QPair YearMonth; /** * \class AlbumRootInfo */ class AlbumRootInfo { public: explicit AlbumRootInfo() : id(0), type(AlbumRoot::UndefinedType), status(0) { }; public: int id; QString label; AlbumRoot::Type type; int status; QString identifier; QString specificPath; }; // -------------------------------------------------------------------------- /** * \class AlbumInfo * A container class for transporting album information * from the database to AlbumManager */ class AlbumInfo { public: typedef QList List; public: explicit AlbumInfo() : id(0), albumRootId(0), iconId(0) { }; bool isNull() const { return (id == 0); } /** * needed for sorting */ bool operator<(const AlbumInfo& info) const { // include album root id? return (relativePath < info.relativePath); } public: int id; int albumRootId; QString relativePath; QString caption; QString category; QDate date; qlonglong iconId; }; // -------------------------------------------------------------------------- /** * \class TagInfo * A container class for transporting tag information * from the database to AlbumManager */ class TagInfo { public: - + typedef QList List; public: explicit TagInfo() : id(0), pid(0), iconId(0) { }; bool isNull() const { return (id == 0); } bool operator<(const TagInfo& info) const { return (name < info.name); } public: int id; int pid; QString name; QString icon; qlonglong iconId; }; // -------------------------------------------------------------------------- /** * \class SearchInfo * A container class for transporting search information * from the database to AlbumManager */ class SearchInfo { public: typedef QList List; public: explicit SearchInfo() : id(0), type(DatabaseSearch::UndefinedType) { }; bool isNull() const { return (id == 0); } /** * needed for sorting */ bool operator<(const SearchInfo& info) const { return (id < info.id); } public: int id; QString name; DatabaseSearch::Type type; QString query; }; // -------------------------------------------------------------------------- class AlbumShortInfo { public: explicit AlbumShortInfo() : id(0), albumRootId(0) { }; bool isNull() const { return (id == 0); } public: int id; QString relativePath; int albumRootId; }; // -------------------------------------------------------------------------- class TagShortInfo { public: explicit TagShortInfo() : id(0), pid(0) { }; bool isNull() const { return (id == 0); } public: int id; int pid; QString name; }; // -------------------------------------------------------------------------- class ItemShortInfo { public: explicit ItemShortInfo() : id(0), albumID(0), albumRootID(0) { }; bool isNull() const { return (id == 0); } public: qlonglong id; QString itemName; int albumID; int albumRootID; QString album; }; // -------------------------------------------------------------------------- class ItemScanInfo { public: explicit ItemScanInfo() : id(0), albumID(0), status(DatabaseItem::UndefinedStatus), category(DatabaseItem::UndefinedCategory), fileSize(0) { }; bool isNull() const { return (id == 0); } public: qlonglong id; int albumID; QString itemName; DatabaseItem::Status status; DatabaseItem::Category category; QDateTime modificationDate; qlonglong fileSize; QString uniqueHash; }; // -------------------------------------------------------------------------- class CommentInfo { public: explicit CommentInfo() : id(-1), imageId(-1), type(DatabaseComment::UndefinedType) { }; bool isNull() const { return (id == -1); } public: int id; qlonglong imageId; DatabaseComment::Type type; QString author; QString language; QDateTime date; QString comment; }; // -------------------------------------------------------------------------- class CopyrightInfo { public: explicit CopyrightInfo() : id(-1) { }; bool isNull() const { return (id == -1); } public: qlonglong id; QString property; QString value; QString extraValue; }; // -------------------------------------------------------------------------- class ImageHistoryEntry { public: explicit ImageHistoryEntry() : imageId(0) { }; bool isNull() const { return (imageId == 0); } public: qlonglong imageId; QString uuid; QString history; }; // -------------------------------------------------------------------------- class ImageRelation { public: explicit ImageRelation() : subjectId(0), objectId(0), type(DatabaseRelation::UndefinedType) { } public: qlonglong subjectId; qlonglong objectId; DatabaseRelation::Type type; }; // -------------------------------------------------------------------------- class TagProperty { public: explicit TagProperty() : tagId(-1) { }; bool isNull() const { return (tagId == -1); } public: int tagId; QString property; QString value; }; // -------------------------------------------------------------------------- class ImageTagProperty { public: explicit ImageTagProperty() : imageId(-1), tagId(-1) { }; bool isNull() const { return (imageId == -1); } public: qlonglong imageId; int tagId; QString property; QString value; }; } // namespace Digikam #endif // DIGIKAM_CORE_DB_ALBUM_INFO_H diff --git a/core/libs/database/coredb/coredbchangesets.h b/core/libs/database/coredb/coredbchangesets.h index ae93aa4f48..019417bb69 100644 --- a/core/libs/database/coredb/coredbchangesets.h +++ b/core/libs/database/coredb/coredbchangesets.h @@ -1,394 +1,394 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2007-12-01 * Description : Core database recording changes. * * Copyright (C) 2007-2008 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. * * ============================================================ */ #ifndef DIGIKAM_CORE_DB_CHANGESETS_H #define DIGIKAM_CORE_DB_CHANGESETS_H #include "digikam_config.h" // Qt includes #include #include #ifdef HAVE_DBUS # include # include "dbenginedbusutils.h" #endif // Local includes #include "digikam_export.h" #include "coredbfields.h" namespace Digikam { class DIGIKAM_DATABASE_EXPORT ImageChangeset { public: /** * An ImageChangeset covers adding or changing any properties of an image. * It is described by a list of affected image ids, and a set of affected database fields. * There is no guarantee that information in the database has actually been changed. */ ImageChangeset(); ImageChangeset(QList ids, DatabaseFields::Set changes); ImageChangeset(qlonglong id, DatabaseFields::Set changes); QList ids() const; bool containsImage(qlonglong id) const; DatabaseFields::Set changes() const; #ifdef HAVE_DBUS ImageChangeset& operator<<(const QDBusArgument& argument); const ImageChangeset& operator>>(QDBusArgument& argument) const; #endif private: QList m_ids; DatabaseFields::Set m_changes; }; // ---------------------------------------------------------------------------- class DIGIKAM_DATABASE_EXPORT ImageTagChangeset { public: /** * An ImageTagChangeset covers adding and removing the association of a tag with an image. * It is described by a list of affected image ids, a list of affected tags, * and an operation. * There is no guarantee that information in the database has actually been changed. */ enum Operation { Unknown, Added, Moved, Removed, RemovedAll, PropertiesChanged }; public: ImageTagChangeset(); ImageTagChangeset(QList ids, QList tags, Operation operation); ImageTagChangeset(qlonglong id, QList tags, Operation operation); ImageTagChangeset(qlonglong id, int tag, Operation operation); /** * Combines two ImageTagChangesets. * The operations shall not differ between the two sets; * the operation is set to Unknown if it differs. * This is especially not suitable for RemovedAll changesets. */ ImageTagChangeset& operator<<(const ImageTagChangeset& other); #ifdef HAVE_DBUS ImageTagChangeset& operator<<(const QDBusArgument& argument); const ImageTagChangeset& operator>>(QDBusArgument& argument) const; #endif QList ids() const; bool containsImage(qlonglong id) const; QList tags() const; bool containsTag(int id) const; Operation operation() const; bool tagsWereAdded() const { return operation() == Added; } bool tagsWereRemoved() const { return operation() == Removed || operation() == RemovedAll; } bool propertiesWereChanged() const { return operation() == PropertiesChanged; } private: QList m_ids; QList m_tags; Operation m_operation; }; // ---------------------------------------------------------------------------- class DIGIKAM_DATABASE_EXPORT CollectionImageChangeset { public: enum Operation { Unknown, /** "Added" indicates that images have been added to albums. */ Added, /** "Removed" indicates that an image has been removed from the given album, * and has possibly set a status of Removed and a null Album (though this can * already have changed to valid values), but the image-specific tables have not been removed. */ Removed, /** "RemovedAll" indicates that for all entries in the specified album, the "Removed" operation * has been carried out. This is equivalent to a "Removed" changesets with all image ids in the * list, but for RemovedAll, the list may not be explicitly given (may be empty). */ RemovedAll, /** "Deleted" indicates that the image-specific tables have been removed from the database. * While "Removed" means all data is still there, though possibly not accessible from an album, * this means all data has been irreversibly deleted. */ Deleted, /** Special combination: Images which has the "Removed" status have now been "Delete"d. * A changeset with Removed or RemovedAll is guaranteed to have been sent anytime before. * Image ids nor albums ids may or may be not available in any combination. */ RemovedDeleted, /** Images have been moved. This is extra information; a Removed and then an Added changeset * are guaranteed to be sent subsequently. * Album is the source album. */ Moved, /** Images have been copied. This is extra information; an Added changeset * is guaranteed to be sent subsequently. * Album is the source album. */ Copied }; public: /** * An CollectionImageChangeset covers adding and removing an image to/from the collection. * It is described by a list of affected image ids, a list of affected albums, * and an operation. * Special Case "RemovedAll": * If all images have been removed from an album, operation is RemovedAll, * the album list contains the (now empty) albums, ids() is empty, * but containsImage() always returns true. * Special Case "RemovedDeleted": * Images with the "Removed" status are now irreversibly deleted. * ids() and/or albums() may be empty (this means information is not available). */ CollectionImageChangeset(); CollectionImageChangeset(QList ids, QList albums, Operation operation); CollectionImageChangeset(QList ids, int album, Operation operation); CollectionImageChangeset(qlonglong id, int album, Operation operation); /** * Combines two CollectionImageChangesets. * The operations shall not differ between the two sets; * the operation is set to Unknown if it differs. * This is especially not suitable for RemovedAll changesets. */ CollectionImageChangeset& operator<<(const CollectionImageChangeset& other); #ifdef HAVE_DBUS CollectionImageChangeset& operator<<(const QDBusArgument& argument); const CollectionImageChangeset& operator>>(QDBusArgument& argument) const; #endif - + /** Specification of this changeset. * All special cases where the returned list may be empty are noted above. * The lists are valid unless such a case is explicitly mentioned. */ QList ids() const; bool containsImage(qlonglong id) const; QList albums() const; bool containsAlbum(int id) const; Operation operation() const; private: QList m_ids; QList m_albums; Operation m_operation; }; // ---------------------------------------------------------------------------- class DIGIKAM_DATABASE_EXPORT AlbumChangeset { public: enum Operation { Unknown, Added, Deleted, Renamed, PropertiesChanged }; public: AlbumChangeset(); AlbumChangeset(int albumId, Operation operation); int albumId() const; Operation operation() const; #ifdef HAVE_DBUS AlbumChangeset& operator<<(const QDBusArgument& argument); const AlbumChangeset& operator>>(QDBusArgument& argument) const; #endif private: int m_id; Operation m_operation; }; // ---------------------------------------------------------------------------- class DIGIKAM_DATABASE_EXPORT TagChangeset { public: enum Operation { Unknown, Added, Moved, Deleted, Renamed, Reparented, IconChanged, PropertiesChanged /// ImageTagProperties Table }; public: TagChangeset(); TagChangeset(int albumId, Operation operation); int tagId() const; Operation operation() const; #ifdef HAVE_DBUS TagChangeset& operator<<(const QDBusArgument& argument); const TagChangeset& operator>>(QDBusArgument& argument) const; #endif private: int m_id; Operation m_operation; }; // ---------------------------------------------------------------------------- class DIGIKAM_DATABASE_EXPORT AlbumRootChangeset { public: enum Operation { Unknown, Added, Deleted, PropertiesChanged }; public: AlbumRootChangeset(); AlbumRootChangeset(int albumRootId, Operation operation); int albumRootId() const; Operation operation() const; #ifdef HAVE_DBUS AlbumRootChangeset& operator<<(const QDBusArgument& argument); const AlbumRootChangeset& operator>>(QDBusArgument& argument) const; #endif private: int m_id; Operation m_operation; }; // ---------------------------------------------------------------------------- class DIGIKAM_DATABASE_EXPORT SearchChangeset { public: enum Operation { Unknown, Added, Deleted, Changed }; public: SearchChangeset(); SearchChangeset(int searchId, Operation operation); int searchId() const; Operation operation() const; #ifdef HAVE_DBUS SearchChangeset& operator<<(const QDBusArgument& argument); const SearchChangeset& operator>>(QDBusArgument& argument) const; #endif private: int m_id; Operation m_operation; }; } // namespace Digikam #ifdef HAVE_DBUS // custom macro from our dbusutilities.h DECLARE_METATYPE_FOR_DBUS(Digikam::ImageChangeset) DECLARE_METATYPE_FOR_DBUS(Digikam::ImageTagChangeset) DECLARE_METATYPE_FOR_DBUS(Digikam::CollectionImageChangeset) DECLARE_METATYPE_FOR_DBUS(Digikam::AlbumChangeset) DECLARE_METATYPE_FOR_DBUS(Digikam::TagChangeset) DECLARE_METATYPE_FOR_DBUS(Digikam::SearchChangeset) DECLARE_METATYPE_FOR_DBUS(Digikam::AlbumRootChangeset) DECLARE_METATYPE_FOR_DBUS(Digikam::DatabaseFields::Set) #endif #endif // DIGIKAM_CORE_DB_CHANGESETS_H diff --git a/core/libs/database/models/itemfiltersettings.cpp b/core/libs/database/models/itemfiltersettings.cpp index 9c3237b020..ddb9d760bb 100644 --- a/core/libs/database/models/itemfiltersettings.cpp +++ b/core/libs/database/models/itemfiltersettings.cpp @@ -1,993 +1,993 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2009-03-05 * Description : Filter values for use with ItemFilterModel * * Copyright (C) 2009-2011 by Marcel Wiesweg * Copyright (C) 2011-2020 by Gilles Caulier * Copyright (C) 2010 by Andi Clemens * Copyright (C) 2011 by Michael G. Hansen * Copyright (C) 2014 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 "itemfiltersettings.h" // C++ includes #include // Qt includes #include // Local includes #include "digikam_debug.h" #include "coredbfields.h" #include "digikam_globals.h" #include "iteminfo.h" #include "tagscache.h" #include "versionmanagersettings.h" namespace Digikam { ItemFilterSettings::ItemFilterSettings() : m_untaggedFilter(false), m_matchingCond(OrCondition), m_ratingFilter(0), m_ratingCond(GreaterEqualCondition), m_isUnratedExcluded(false), m_mimeTypeFilter(MimeFilter::AllFiles), m_geolocationCondition(GeolocationNoFilter) { } DatabaseFields::Set ItemFilterSettings::watchFlags() const { DatabaseFields::Set set; if (isFilteringByDay()) { set |= DatabaseFields::CreationDate; } if (isFilteringByText()) { set |= DatabaseFields::Name; set |= DatabaseFields::Comment; } if (isFilteringByRating()) { set |= DatabaseFields::Rating; } if (isFilteringByTypeMime()) { set |= DatabaseFields::Category; set |= DatabaseFields::Format; } if (isFilteringByGeolocation()) { set |= DatabaseFields::ItemPositionsAll; } if (isFilteringByColorLabels()) { set |= DatabaseFields::ColorLabel; } if (isFilteringByPickLabels()) { set |= DatabaseFields::PickLabel; } return set; } bool ItemFilterSettings::isFilteringByDay() const { if (!m_dayFilter.isEmpty()) { return true; } return false; } bool ItemFilterSettings::isFilteringByTags() const { if (!m_includeTagFilter.isEmpty() || !m_excludeTagFilter.isEmpty() || m_untaggedFilter) { return true; } return false; } bool ItemFilterSettings::isFilteringByColorLabels() const { if (!m_colorLabelTagFilter.isEmpty()) { return true; } return false; } bool ItemFilterSettings::isFilteringByPickLabels() const { if (!m_pickLabelTagFilter.isEmpty()) { return true; } return false; } bool ItemFilterSettings::isFilteringByText() const { if (!m_textFilterSettings.text.isEmpty()) { return true; } return false; } bool ItemFilterSettings::isFilteringByTypeMime() const { if (m_mimeTypeFilter != MimeFilter::AllFiles) { return true; } return false; } bool ItemFilterSettings::isFilteringByGeolocation() const { return (m_geolocationCondition != GeolocationNoFilter); } bool ItemFilterSettings::isFilteringByRating() const { if ((m_ratingFilter != 0) || (m_ratingCond != GreaterEqualCondition) || m_isUnratedExcluded) { return true; } return false; } bool ItemFilterSettings::isFilteringInternally() const { return (isFiltering() || !m_urlWhitelists.isEmpty() || !m_idWhitelists.isEmpty()); } bool ItemFilterSettings::isFiltering() const { return (isFilteringByDay() || isFilteringByTags() || isFilteringByText() || isFilteringByRating() || isFilteringByTypeMime() || isFilteringByColorLabels() || isFilteringByPickLabels() || isFilteringByGeolocation()); } void ItemFilterSettings::setDayFilter(const QList& days) { m_dayFilter.clear(); for (QList::const_iterator it = days.constBegin() ; it != days.constEnd() ; ++it) { m_dayFilter.insert(*it, true); } } void ItemFilterSettings::setTagFilter(const QList& includedTags, const QList& excludedTags, MatchingCondition matchingCondition, bool showUnTagged, const QList& clTagIds, const QList& plTagIds) { m_includeTagFilter = includedTags; m_excludeTagFilter = excludedTags; m_matchingCond = matchingCondition; m_untaggedFilter = showUnTagged; m_colorLabelTagFilter = clTagIds; m_pickLabelTagFilter = plTagIds; } void ItemFilterSettings::setRatingFilter(int rating, RatingCondition ratingCondition, bool isUnratedExcluded) { m_ratingFilter = rating; m_ratingCond = ratingCondition; m_isUnratedExcluded = isUnratedExcluded; } void ItemFilterSettings::setMimeTypeFilter(int mime) { m_mimeTypeFilter = (MimeFilter::TypeMimeFilter)mime; } void ItemFilterSettings::setGeolocationFilter(const GeolocationCondition& condition) { m_geolocationCondition = condition; } void ItemFilterSettings::setTextFilter(const SearchTextFilterSettings& settings) { m_textFilterSettings = settings; } void ItemFilterSettings::setTagNames(const QHash& hash) { m_tagNameHash = hash; } void ItemFilterSettings::setAlbumNames(const QHash& hash) { m_albumNameHash = hash; } void ItemFilterSettings::setUrlWhitelist(const QList& urlList, const QString& id) { if (urlList.isEmpty()) { m_urlWhitelists.remove(id); } else { m_urlWhitelists.insert(id, urlList); } } void ItemFilterSettings::setIdWhitelist(const QList& idList, const QString& id) { if (idList.isEmpty()) { m_idWhitelists.remove(id); } else { m_idWhitelists.insert(id, idList); } } template bool containsAnyOf(const ContainerA& listA, const ContainerB& listB) { foreach (const typename ContainerA::value_type& a, listA) { if (listB.contains(a)) { return true; } } return false; } template bool containsNoneOfExcept(const ContainerA& list, const ContainerB& noneOfList, const Value& exception) { foreach (const typename ContainerB::value_type& n, noneOfList) { if (n != exception && list.contains(n)) { return false; } } return true; } bool ItemFilterSettings::matches(const ItemInfo& info, bool* const foundText) const { if (foundText) { *foundText = false; } if (!isFilteringInternally()) { return true; } bool match = false; if (!m_includeTagFilter.isEmpty() || !m_excludeTagFilter.isEmpty()) { QList tagIds = info.tagIds(); QList::const_iterator it; match = m_includeTagFilter.isEmpty(); if (m_matchingCond == OrCondition) { for (it = m_includeTagFilter.begin() ; it != m_includeTagFilter.end() ; ++it) { if (tagIds.contains(*it)) { match = true; break; } } match |= (m_untaggedFilter && tagIds.isEmpty()); } else // AND matching condition... { // m_untaggedFilter and non-empty tag filter, combined with AND, is logically no match if (!m_untaggedFilter) { for (it = m_includeTagFilter.begin() ; it != m_includeTagFilter.end() ; ++it) { if (!tagIds.contains(*it)) { break; } } if (it == m_includeTagFilter.end()) { match = true; } } } for (it = m_excludeTagFilter.begin() ; it != m_excludeTagFilter.end() ; ++it) { if (tagIds.contains(*it)) { match = false; break; } } } else if (m_untaggedFilter) { match = !TagsCache::instance()->containsPublicTags(info.tagIds()); } else { match = true; } //-- Filter by pick labels ------------------------------------------------ if (!m_pickLabelTagFilter.isEmpty()) { QList tagIds = info.tagIds(); bool matchPL = false; if (containsAnyOf(m_pickLabelTagFilter, tagIds)) { matchPL = true; } else { int noPickLabelTagId = TagsCache::instance()->tagForPickLabel(NoPickLabel); if (m_pickLabelTagFilter.contains(noPickLabelTagId)) { // Searching for "has no ColorLabel" requires special handling: // Scan that the tag ids contains none of the ColorLabel tags, except maybe the NoColorLabel tag matchPL = containsNoneOfExcept(tagIds, TagsCache::instance()->pickLabelTags(), noPickLabelTagId); } } match &= matchPL; } //-- Filter by color labels ------------------------------------------------ if (!m_colorLabelTagFilter.isEmpty()) { QList tagIds = info.tagIds(); bool matchCL = false; if (containsAnyOf(m_colorLabelTagFilter, tagIds)) { matchCL = true; } else { int noColorLabelTagId = TagsCache::instance()->tagForColorLabel(NoColorLabel); if (m_colorLabelTagFilter.contains(noColorLabelTagId)) { // Searching for "has no ColorLabel" requires special handling: // Scan that the tag ids contains none of the ColorLabel tags, except maybe the NoColorLabel tag matchCL = containsNoneOfExcept(tagIds, TagsCache::instance()->colorLabelTags(), noColorLabelTagId); } } match &= matchCL; } //-- Filter by date ----------------------------------------------------------- if (!m_dayFilter.isEmpty()) { match &= m_dayFilter.contains(QDateTime(info.dateTime().date(), QTime())); } //-- Filter by rating --------------------------------------------------------- if (m_ratingFilter >= 0) { // for now we treat -1 (no rating) just like a rating of 0. int rating = info.rating(); if (rating == -1) { rating = 0; } if (m_isUnratedExcluded && rating == 0) { match = false; } else { if (m_ratingCond == GreaterEqualCondition) { // If the rating is not >=, i.e it is <, then it does not match. if (rating < m_ratingFilter) { match = false; } } else if (m_ratingCond == EqualCondition) { // If the rating is not =, i.e it is !=, then it does not match. if (rating != m_ratingFilter) { match = false; } } else { // If the rating is not <=, i.e it is >, then it does not match. if (rating > m_ratingFilter) { match = false; } } } } // -- Filter by mime type ----------------------------------------------------- switch (m_mimeTypeFilter) { // info.format is a standardized string: Only one possibility per mime type case MimeFilter::ImageFiles: { if (info.category() != DatabaseItem::Image) { match = false; } break; } case MimeFilter::JPGFiles: { if (info.format() != QLatin1String("JPG")) { match = false; } break; } case MimeFilter::PNGFiles: { if (info.format() != QLatin1String("PNG")) { match = false; } break; } case MimeFilter::HEIFFiles: { if (info.format() != QLatin1String("HEIF")) { match = false; } break; } case MimeFilter::PGFFiles: { if (info.format() != QLatin1String("PGF")) { match = false; } break; } case MimeFilter::TIFFiles: { if (info.format() != QLatin1String("TIFF")) { match = false; } break; } case MimeFilter::DNGFiles: { if (info.format() != QLatin1String("RAW-DNG")) { match = false; } break; } case MimeFilter::NoRAWFiles: { if (info.format().startsWith(QLatin1String("RAW"))) { match = false; } break; } case MimeFilter::RAWFiles: { if (!info.format().startsWith(QLatin1String("RAW"))) { match = false; } break; } case MimeFilter::MoviesFiles: { if (info.category() != DatabaseItem::Video) { match = false; } break; } case MimeFilter::AudioFiles: { if (info.category() != DatabaseItem::Audio) { match = false; } break; } case MimeFilter::RasterGraphics: { if ( info.format() != QLatin1String("PSD") && // Adobe Photoshop Document info.format() != QLatin1String("PSB") && // Adobe Photoshop Big info.format() != QLatin1String("XCF") && // Gimp info.format() != QLatin1String("KRA") && // Krita info.format() != QLatin1String("ORA") // Open Raster ) { match = false; } break; } default: { // All Files: do nothing... break; } } //-- Filter by geolocation ---------------------------------------------------- if (m_geolocationCondition != GeolocationNoFilter) { if (m_geolocationCondition == GeolocationNoCoordinates) { if (info.hasCoordinates()) { match = false; } } else if (m_geolocationCondition == GeolocationHasCoordinates) { if (!info.hasCoordinates()) { match = false; } } } //-- Filter by text ----------------------------------------------------------- if (!m_textFilterSettings.text.isEmpty()) { bool textMatch = false; // Image name if ((m_textFilterSettings.textFields & SearchTextFilterSettings::ImageName) && info.name().contains(m_textFilterSettings.text, m_textFilterSettings.caseSensitive)) { textMatch = true; } // Image title if ((m_textFilterSettings.textFields & SearchTextFilterSettings::ImageTitle) && info.title().contains(m_textFilterSettings.text, m_textFilterSettings.caseSensitive)) { textMatch = true; } // Image comment if ((m_textFilterSettings.textFields & SearchTextFilterSettings::ImageComment) && info.comment().contains(m_textFilterSettings.text, m_textFilterSettings.caseSensitive)) { textMatch = true; } // Tag names foreach (int id, info.tagIds()) { if ((m_textFilterSettings.textFields & SearchTextFilterSettings::TagName) && m_tagNameHash.value(id).contains(m_textFilterSettings.text, m_textFilterSettings.caseSensitive)) { textMatch = true; } } // Album names if ((m_textFilterSettings.textFields & SearchTextFilterSettings::AlbumName) && m_albumNameHash.value(info.albumId()).contains(m_textFilterSettings.text, m_textFilterSettings.caseSensitive)) { textMatch = true; } // Image Aspect Ratio if (m_textFilterSettings.textFields & SearchTextFilterSettings::ImageAspectRatio) { QRegExp expRatio (QLatin1String("^\\d+:\\d+$")); QRegExp expFloat (QLatin1String("^\\d+(.\\d+)?$")); if ((expRatio.indexIn(m_textFilterSettings.text) > -1) && m_textFilterSettings.text.contains(QRegExp(QLatin1String(":\\d+")))) { QString trimmedTextFilterSettingsText = m_textFilterSettings.text; QStringList numberStringList = trimmedTextFilterSettingsText.split(QLatin1Char(':'), QString::SkipEmptyParts); if (numberStringList.length() == 2) { QString numString = (QString)numberStringList.at(0), denomString = (QString)numberStringList.at(1); bool canConverseNum = false; bool canConverseDenom = false; int num = numString.toInt(&canConverseNum, 10), denom = denomString.toInt(&canConverseDenom, 10); if (canConverseNum && canConverseDenom) { if (fabs(info.aspectRatio() - (double)num / denom) < 0.1) { textMatch = true; } } } } else if (expFloat.indexIn(m_textFilterSettings.text) > -1) { QString trimmedTextFilterSettingsText = m_textFilterSettings.text; bool canConverse = false; double ratio = trimmedTextFilterSettingsText.toDouble(&canConverse); if (canConverse) { if (fabs(info.aspectRatio() - ratio) < 0.1) { textMatch = true; } } } } // Image Pixel Size // See bug #341053 for details. if (m_textFilterSettings.textFields & SearchTextFilterSettings::ImagePixelSize) { QSize size = info.dimensions(); int pixelSize = size.height()*size.width(); QString text = m_textFilterSettings.text; - if (text.contains(QRegExp(QLatin1String("^>\\d{1,15}$"))) && + if (text.contains(QRegExp(QLatin1String("^>\\d{1,15}$"))) && (pixelSize > (text.remove(0, 1)).toInt())) { textMatch = true; } else if (text.contains(QRegExp(QLatin1String("^<\\d{1,15}$"))) && (pixelSize < (text.remove(0, 1)).toInt())) { textMatch = true; } else if (text.contains(QRegExp(QLatin1String("^\\d+$"))) && (pixelSize == text.toInt())) { textMatch = true; } } match &= textMatch; if (foundText) { *foundText = textMatch; } } // -- filter by URL-whitelists ------------------------------------------------ // NOTE: whitelists are always AND for now. if (match) { const QUrl url = info.fileUrl(); for (QHash>::const_iterator it = m_urlWhitelists.constBegin() ; it != m_urlWhitelists.constEnd() ; ++it) { match = it->contains(url); if (!match) { break; } } } if (match) { const qlonglong id = info.id(); for (QHash >::const_iterator it = m_idWhitelists.constBegin() ; it != m_idWhitelists.constEnd() ; ++it) { match = it->contains(id); if (!match) { break; } } } return match; } // ------------------------------------------------------------------------------------------------- VersionItemFilterSettings::VersionItemFilterSettings() { m_includeTagFilter = 0; m_exceptionTagFilter = 0; } VersionItemFilterSettings::VersionItemFilterSettings(const VersionManagerSettings& settings) { setVersionManagerSettings(settings); } bool VersionItemFilterSettings::operator==(const VersionItemFilterSettings& other) const { return ((m_excludeTagFilter == other.m_excludeTagFilter) && (m_exceptionLists == other.m_exceptionLists)); } bool VersionItemFilterSettings::matches(const ItemInfo& info) const { if (!isFiltering()) { return true; } const qlonglong id = info.id(); for (QHash >::const_iterator it = m_exceptionLists.constBegin() ; it != m_exceptionLists.constEnd() ; ++it) { if (it->contains(id)) { return true; } } bool match = true; QList tagIds = info.tagIds(); if (!tagIds.contains(m_includeTagFilter)) { for (QList::const_iterator it = m_excludeTagFilter.begin() ; it != m_excludeTagFilter.end() ; ++it) { if (tagIds.contains(*it)) { match = false; break; } } } if (!match) { if (tagIds.contains(m_exceptionTagFilter)) { match = true; } } return match; } bool VersionItemFilterSettings::isHiddenBySettings(const ItemInfo& info) const { QList tagIds = info.tagIds(); foreach (int tagId, m_excludeTagFilter) { if (tagIds.contains(tagId)) { return true; } } return false; } bool VersionItemFilterSettings::isExemptedBySettings(const ItemInfo& info) const { return info.tagIds().contains(m_exceptionTagFilter); } void VersionItemFilterSettings::setVersionManagerSettings(const VersionManagerSettings& settings) { m_excludeTagFilter.clear(); if (!settings.enabled) { return; } if (!(settings.showInViewFlags & VersionManagerSettings::ShowOriginal)) { m_excludeTagFilter << TagsCache::instance()->getOrCreateInternalTag(InternalTagName::originalVersion()); } if (!(settings.showInViewFlags & VersionManagerSettings::ShowIntermediates)) { m_excludeTagFilter << TagsCache::instance()->getOrCreateInternalTag(InternalTagName::intermediateVersion()); } m_includeTagFilter = TagsCache::instance()->getOrCreateInternalTag(InternalTagName::currentVersion()); m_exceptionTagFilter = TagsCache::instance()->getOrCreateInternalTag(InternalTagName::versionAlwaysVisible()); } void VersionItemFilterSettings::setExceptionList(const QList& idList, const QString& id) { if (idList.isEmpty()) { m_exceptionLists.remove(id); } else { m_exceptionLists.insert(id, idList); } } bool VersionItemFilterSettings::isFiltering() const { return !m_excludeTagFilter.isEmpty(); } bool VersionItemFilterSettings::isFilteringByTags() const { return isFiltering(); } // ------------------------------------------------------------------------------------------------- GroupItemFilterSettings::GroupItemFilterSettings() : m_allOpen(false) { } bool GroupItemFilterSettings::operator==(const GroupItemFilterSettings& other) const { return ((m_allOpen == other.m_allOpen) && (m_openGroups == other.m_openGroups)); } bool GroupItemFilterSettings::matches(const ItemInfo& info) const { if (m_allOpen) { return true; } if (info.isGrouped()) { return m_openGroups.contains(info.groupImage().id()); } return true; } void GroupItemFilterSettings::setOpen(qlonglong group, bool open) { if (open) { m_openGroups << group; } else { m_openGroups.remove(group); } } bool GroupItemFilterSettings::isOpen(qlonglong group) const { return m_openGroups.contains(group); } void GroupItemFilterSettings::setAllOpen(bool open) { m_allOpen = open; } bool GroupItemFilterSettings::isAllOpen() const { return m_allOpen; } bool GroupItemFilterSettings::isFiltering() const { return !m_allOpen; } DatabaseFields::Set GroupItemFilterSettings::watchFlags() const { return DatabaseFields::Set(DatabaseFields::ImageRelations); } } // namespace Digikam diff --git a/core/libs/dialogs/dconfigdlgmngr.cpp b/core/libs/dialogs/dconfigdlgmngr.cpp index f617f0ee52..9a89ab6a42 100644 --- a/core/libs/dialogs/dconfigdlgmngr.cpp +++ b/core/libs/dialogs/dconfigdlgmngr.cpp @@ -1,746 +1,746 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2003-11-03 * Description : Automatic retrieving, saving and resetting skeleton based settings in a dialog. * * Copyright (C) 2019-2020 by Gilles Caulier * Copyright (C) 2003 by Benjamin C Meyer * Copyright (C) 2003 by Waldo Bastian * Copyright (C) 2017 by Friedrich W. H. Kossebau * * 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 "dconfigdlgmngr.h" // Qt includes #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" namespace Digikam { typedef QHash MyHash; Q_GLOBAL_STATIC(MyHash, s_propertyMap) Q_GLOBAL_STATIC(MyHash, s_changedMap) class DConfigDlgMngr::Private { public: Private(DConfigDlgMngr* const q) : q(q), conf(nullptr), dialog(nullptr), insideGroupBox(false), trackChanges(false) { } public: DConfigDlgMngr* const q; /** * Skeleton object used to store settings */ KConfigSkeleton* conf; /** * Dialog being managed */ QWidget* dialog; QHash knownWidget; QHash buddyWidget; QSet allExclusiveGroupBoxes; bool insideGroupBox; bool trackChanges; }; DConfigDlgMngr::DConfigDlgMngr(QWidget* const parent, KConfigSkeleton* const conf) : QObject(parent), d(new Private(this)) { d->conf = conf; d->dialog = parent; init(true); } DConfigDlgMngr::~DConfigDlgMngr() { delete d; } void DConfigDlgMngr::initMaps() { if (s_changedMap()->isEmpty()) { s_changedMap()->insert(QStringLiteral("QCheckBox"), SIGNAL(stateChanged(int))); s_changedMap()->insert(QStringLiteral("QPushButton"), SIGNAL(clicked(bool))); s_changedMap()->insert(QStringLiteral("QRadioButton"), SIGNAL(toggled(bool))); s_changedMap()->insert(QStringLiteral("QGroupBox"), SIGNAL(toggled(bool))); s_changedMap()->insert(QStringLiteral("QComboBox"), SIGNAL(activated(int))); s_changedMap()->insert(QStringLiteral("QDateEdit"), SIGNAL(dateChanged(QDate))); s_changedMap()->insert(QStringLiteral("QTimeEdit"), SIGNAL(timeChanged(QTime))); s_changedMap()->insert(QStringLiteral("QDateTimeEdit"), SIGNAL(dateTimeChanged(QDateTime))); s_changedMap()->insert(QStringLiteral("QDial"), SIGNAL(valueChanged(int))); s_changedMap()->insert(QStringLiteral("QDoubleSpinBox"), SIGNAL(valueChanged(double))); s_changedMap()->insert(QStringLiteral("QLineEdit"), SIGNAL(textChanged(QString))); s_changedMap()->insert(QStringLiteral("QSlider"), SIGNAL(valueChanged(int))); s_changedMap()->insert(QStringLiteral("QSpinBox"), SIGNAL(valueChanged(int))); s_changedMap()->insert(QStringLiteral("QTextEdit"), SIGNAL(textChanged())); s_changedMap()->insert(QStringLiteral("QTextBrowser"), SIGNAL(sourceChanged(QString))); s_changedMap()->insert(QStringLiteral("QPlainTextEdit"), SIGNAL(textChanged())); s_changedMap()->insert(QStringLiteral("QTabWidget"), SIGNAL(currentChanged(int))); } } QHash* DConfigDlgMngr::propertyMap() { initMaps(); return s_propertyMap(); } QHash* DConfigDlgMngr::changedMap() { initMaps(); return s_changedMap(); } void DConfigDlgMngr::init(bool trackChanges) { initMaps(); d->trackChanges = trackChanges; // Go through all of the children of the widgets and find all known widgets (void)parseChildren(d->dialog, trackChanges); } void DConfigDlgMngr::addWidget(QWidget* const widget) { (void)parseChildren(widget, true); } void DConfigDlgMngr::setupWidget(QWidget* widget, KConfigSkeletonItem* item) { QVariant minValue = item->minValue(); if (minValue.isValid()) { if (widget->metaObject()->indexOfProperty("minValue") != -1) { widget->setProperty("minValue", minValue); } if (widget->metaObject()->indexOfProperty("minimum") != -1) { widget->setProperty("minimum", minValue); } } QVariant maxValue = item->maxValue(); if (maxValue.isValid()) { if (widget->metaObject()->indexOfProperty("maxValue") != -1) { widget->setProperty("maxValue", maxValue); } if (widget->metaObject()->indexOfProperty("maximum") != -1) { widget->setProperty("maximum", maxValue); } } if (widget->whatsThis().isEmpty()) { QString whatsThis = item->whatsThis(); if (!whatsThis.isEmpty()) { widget->setWhatsThis(whatsThis); } } if (widget->toolTip().isEmpty()) { QString toolTip = item->toolTip(); if (!toolTip.isEmpty()) { widget->setToolTip(toolTip); } } // If it is a QGroupBox with only autoExclusive buttons // and has no custom property and the config item type // is an integer, assume we want to save the index // instead of if it is checked or not QGroupBox* const gb = qobject_cast(widget); if (gb && getCustomProperty(gb).isEmpty()) { const KConfigSkeletonItem* const item = d->conf->findItem(widget->objectName().mid(5)); if (item->property().type() == QVariant::Int) { QObjectList children = gb->children(); children.removeAll(gb->layout()); const QList buttons = gb->findChildren(); bool allAutoExclusiveDirectChildren = true; for (QAbstractButton* const button : buttons) { // cppcheck-suppress useStlAlgorithm allAutoExclusiveDirectChildren = ( allAutoExclusiveDirectChildren && button->autoExclusive() && (button->parent() == gb) ); } if (allAutoExclusiveDirectChildren) { d->allExclusiveGroupBoxes << widget; } } } if (!item->isEqual(property(widget))) { setProperty(widget, item->property()); } } bool DConfigDlgMngr::parseChildren(const QWidget* widget, bool trackChanges) { bool valueChanged = false; const QList listOfChildren = widget->children(); if (listOfChildren.isEmpty()) { return valueChanged; } const QMetaMethod widgetModifiedSignal = metaObject()->method(metaObject()->indexOfSignal("widgetModified()")); Q_ASSERT(widgetModifiedSignal.isValid() && (metaObject()->indexOfSignal("widgetModified()") >= 0)); for (QObject* const object : listOfChildren) { if (!object->isWidgetType()) { continue; // Skip non-widgets } QWidget* const childWidget = static_cast(object); QString widgetName = childWidget->objectName(); bool bParseChildren = true; bool bSaveInsideGroupBox = d->insideGroupBox; if (widgetName.startsWith(QLatin1String("kcfg_"))) { // This is one of our widgets! QString configId = widgetName.mid(5); KConfigSkeletonItem* const item = d->conf->findItem(configId); if (item) { d->knownWidget.insert(configId, childWidget); setupWidget(childWidget, item); if (trackChanges) { bool changeSignalFound = false; if (d->allExclusiveGroupBoxes.contains(childWidget)) { const QList buttons = childWidget->findChildren(); for (QAbstractButton* const button : buttons) { connect(button, &QAbstractButton::toggled, this, &DConfigDlgMngr::widgetModified); } } QByteArray propertyChangeSignal = getCustomPropertyChangedSignal(childWidget); if (propertyChangeSignal.isEmpty()) { propertyChangeSignal = getUserPropertyChangedSignal(childWidget); } if (propertyChangeSignal.isEmpty()) { // get the change signal from the meta object const QMetaObject* const metaObject = childWidget->metaObject(); QByteArray userproperty = getCustomProperty(childWidget); if (userproperty.isEmpty()) { userproperty = getUserProperty(childWidget); } if (!userproperty.isEmpty()) { const int indexOfProperty = metaObject->indexOfProperty(userproperty.constData()); if (indexOfProperty != -1) { const QMetaProperty property = metaObject->property(indexOfProperty); const QMetaMethod notifySignal = property.notifySignal(); if (notifySignal.isValid()) { connect(childWidget, notifySignal, this, widgetModifiedSignal); changeSignalFound = true; } } } else { qCWarning(DIGIKAM_GENERAL_LOG) << "Don't know how to monitor widget '" << childWidget->metaObject()->className() << "' for changes!"; } } else { connect(childWidget, propertyChangeSignal.constData(), this, SIGNAL(widgetModified())); changeSignalFound = true; } if (changeSignalFound) { QComboBox* const cb = qobject_cast(childWidget); if (cb && cb->isEditable()) { connect(cb, &QComboBox::editTextChanged, this, &DConfigDlgMngr::widgetModified); } } } QGroupBox* const gb = qobject_cast(childWidget); if (!gb) { bParseChildren = false; } else { d->insideGroupBox = true; } } else { - qCWarning(DIGIKAM_GENERAL_LOG) << "A widget named '" << widgetName - << "' was found but there is no setting named '" + qCWarning(DIGIKAM_GENERAL_LOG) << "A widget named '" << widgetName + << "' was found but there is no setting named '" << configId << "'"; } } else if (QLabel* const label = qobject_cast(childWidget)) { QWidget* const buddy = label->buddy(); if (!buddy) { continue; } QString buddyName = buddy->objectName(); if (buddyName.startsWith(QLatin1String("kcfg_"))) { // This is one of our widgets! QString configId = buddyName.mid(5); d->buddyWidget.insert(configId, childWidget); } } if (bParseChildren) { // this widget is not known as something we can store. // Maybe we can store one of its children. valueChanged |= parseChildren(childWidget, trackChanges); } d->insideGroupBox = bSaveInsideGroupBox; } return valueChanged; } void DConfigDlgMngr::updateWidgets() { bool changed = false; bool bSignalsBlocked = signalsBlocked(); blockSignals(true); QWidget* widget = nullptr; QHashIterator it(d->knownWidget); while (it.hasNext()) { it.next(); widget = it.value(); KConfigSkeletonItem* const item = d->conf->findItem(it.key()); if (!item) { qCWarning(DIGIKAM_GENERAL_LOG) << "The setting '" << it.key() << "' has disappeared!"; continue; } if (!item->isEqual(property(widget))) { setProperty(widget, item->property()); changed = true; } if (item->isImmutable()) { widget->setEnabled(false); QWidget* const buddy = d->buddyWidget.value(it.key(), nullptr); if (buddy) { buddy->setEnabled(false); } } } blockSignals(bSignalsBlocked); if (changed) { QTimer::singleShot(0, this, &DConfigDlgMngr::widgetModified); } } void DConfigDlgMngr::updateWidgetsDefault() { bool bUseDefaults = d->conf->useDefaults(true); updateWidgets(); d->conf->useDefaults(bUseDefaults); } void DConfigDlgMngr::updateSettings() { bool changed = false; QWidget* widget = nullptr; QHashIterator it(d->knownWidget); while (it.hasNext()) { it.next(); widget = it.value(); KConfigSkeletonItem* const item = d->conf->findItem(it.key()); if (!item) { qCWarning(DIGIKAM_GENERAL_LOG) << "The setting '" << it.key() << "' has disappeared!"; continue; } QVariant fromWidget = property(widget); if (!item->isEqual(fromWidget)) { item->setProperty(fromWidget); changed = true; } } if (changed) { d->conf->save(); emit settingsChanged(); } } QByteArray DConfigDlgMngr::getUserProperty(const QWidget* widget) const { if (!s_propertyMap()->contains(QString::fromUtf8(widget->metaObject()->className()))) { const QMetaObject* const metaObject = widget->metaObject(); const QMetaProperty user = metaObject->userProperty(); if (user.isValid()) { s_propertyMap()->insert(QString::fromUtf8(widget->metaObject()->className()), user.name()); } else { return QByteArray(); // no USER property } } const QComboBox* const cb = qobject_cast(widget); if (cb) { const char* const qcomboUserPropertyName = cb->QComboBox::metaObject()->userProperty().name(); const int qcomboUserPropertyIndex = qcomboUserPropertyName ? cb->QComboBox::metaObject()->indexOfProperty(qcomboUserPropertyName) : -1; const char* const widgetUserPropertyName = widget->metaObject()->userProperty().name(); const int widgetUserPropertyIndex = widgetUserPropertyName ? cb->metaObject()->indexOfProperty(widgetUserPropertyName) : -1; // no custom user property set on subclass of QComboBox? if (qcomboUserPropertyIndex == widgetUserPropertyIndex) { return QByteArray(); // use the QCombobox special code } } return s_propertyMap()->value(QString::fromUtf8(widget->metaObject()->className())); } QByteArray DConfigDlgMngr::getCustomProperty(const QWidget* widget) const { QVariant prop(widget->property("kcfg_property")); if (prop.isValid()) { if (!prop.canConvert(QVariant::ByteArray)) { qCWarning(DIGIKAM_GENERAL_LOG) << "Property on" << widget->metaObject()->className() << "is not of type ByteArray"; } else { return prop.toByteArray(); } } return QByteArray(); } QByteArray DConfigDlgMngr::getUserPropertyChangedSignal(const QWidget* widget) const { QHash::const_iterator changedIt = s_changedMap()->constFind(QString::fromUtf8(widget->metaObject()->className())); if (changedIt == s_changedMap()->constEnd()) { // If the class name of the widget wasn't in the monitored widgets map, then look for // it again using the super class name. if (widget->metaObject()->superClass()) { changedIt = s_changedMap()->constFind(QString::fromUtf8(widget->metaObject()->superClass()->className())); } } return ((changedIt == s_changedMap()->constEnd()) ? QByteArray() : *changedIt); } QByteArray DConfigDlgMngr::getCustomPropertyChangedSignal(const QWidget *widget) const { QVariant prop(widget->property("kcfg_propertyNotify")); if (prop.isValid()) { if (!prop.canConvert(QVariant::ByteArray)) { qCWarning(DIGIKAM_GENERAL_LOG) << "PropertyNotify on" << widget->metaObject()->className() << "is not of type ByteArray"; } else { return prop.toByteArray(); } } return QByteArray(); } void DConfigDlgMngr::setProperty(QWidget* w, const QVariant& v) { if (d->allExclusiveGroupBoxes.contains(w)) { const QList buttons = w->findChildren(); if (v.toInt() < buttons.count()) { buttons[v.toInt()]->setChecked(true); } return; } QByteArray userproperty = getCustomProperty(w); if (userproperty.isEmpty()) { userproperty = getUserProperty(w); } if (userproperty.isEmpty()) { QComboBox* const cb = qobject_cast(w); if (cb) { if (cb->isEditable()) { int i = cb->findText(v.toString()); if (i != -1) { cb->setCurrentIndex(i); } else { cb->setEditText(v.toString()); } } else { cb->setCurrentIndex(v.toInt()); } return; } } if (userproperty.isEmpty()) { qCWarning(DIGIKAM_GENERAL_LOG) << w->metaObject()->className() << " widget not handled!"; return; } w->setProperty(userproperty.constData(), v); } QVariant DConfigDlgMngr::property(QWidget* w) const { if (d->allExclusiveGroupBoxes.contains(w)) { const QList buttons = w->findChildren(); for (int i = 0 ; i < buttons.count() ; ++i) { if (buttons[i]->isChecked()) { return i; } } return -1; } QByteArray userproperty = getCustomProperty(w); if (userproperty.isEmpty()) { userproperty = getUserProperty(w); } if (userproperty.isEmpty()) { QComboBox* const cb = qobject_cast(w); if (cb) { if (cb->isEditable()) { return QVariant(cb->currentText()); } else { return QVariant(cb->currentIndex()); } } } if (userproperty.isEmpty()) { qCWarning(DIGIKAM_GENERAL_LOG) << w->metaObject()->className() << " widget not handled!"; return QVariant(); } return w->property(userproperty.constData()); } bool DConfigDlgMngr::hasChanged() const { QWidget* widget = nullptr; QHashIterator it(d->knownWidget); while (it.hasNext()) { it.next(); widget = it.value(); KConfigSkeletonItem* const item = d->conf->findItem(it.key()); if (!item) { qCWarning(DIGIKAM_GENERAL_LOG) << "The setting '" << it.key() << "' has disappeared!"; continue; } if (!item->isEqual(property(widget))) { return true; } } return false; } bool DConfigDlgMngr::isDefault() const { bool bUseDefaults = d->conf->useDefaults(true); bool result = !hasChanged(); d->conf->useDefaults(bUseDefaults); return result; } } // namespace Digikam diff --git a/core/libs/dialogs/webbrowserdlg.cpp b/core/libs/dialogs/webbrowserdlg.cpp index ca38dbd181..e9dd588690 100644 --- a/core/libs/dialogs/webbrowserdlg.cpp +++ b/core/libs/dialogs/webbrowserdlg.cpp @@ -1,274 +1,274 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2017-06-21 * Description : a simple web browser dialog based on Qt WebEngine. * * Copyright (C) 2017-2020 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 "webbrowserdlg.h" #include "digikam_config.h" // Qt includes #include #include #include #include #include #include #include #ifdef HAVE_QWEBENGINE # include # include # include # include #else # include #endif // KDE includes #include #include #include // Local includes #include "statusprogressbar.h" #include "searchtextbar.h" #include "dxmlguiwindow.h" #include "digikam_debug.h" namespace Digikam { class Q_DECL_HIDDEN WebBrowserDlg::Private { public: explicit Private() : browser(nullptr), toolbar(nullptr), progressbar(nullptr), searchbar(nullptr) { } public: QUrl home; #ifdef HAVE_QWEBENGINE QWebEngineView* browser; #else QWebView* browser; #endif QToolBar* toolbar; StatusProgressBar* progressbar; SearchTextBar* searchbar; }; WebBrowserDlg::WebBrowserDlg(const QUrl& url, QWidget* const parent, bool hideDeskBrowser) : QDialog(parent), d(new Private) { setModal(false); d->home = url; #ifdef HAVE_QWEBENGINE d->browser = new QWebEngineView(this); d->browser->page()->profile()->cookieStore()->deleteAllCookies(); #else d->browser = new QWebView(this); d->browser->settings()->setAttribute(QWebSettings::LocalStorageEnabled, true); d->browser->page()->networkAccessManager()->setCookieJar(new QNetworkCookieJar()); #endif // -------------------------- d->toolbar = new QToolBar(this); d->toolbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); #ifdef HAVE_QWEBENGINE d->toolbar->addAction(d->browser->pageAction(QWebEnginePage::Back)); d->toolbar->addAction(d->browser->pageAction(QWebEnginePage::Forward)); d->toolbar->addAction(d->browser->pageAction(QWebEnginePage::Reload)); d->toolbar->addAction(d->browser->pageAction(QWebEnginePage::Stop)); #else d->toolbar->addAction(d->browser->pageAction(QWebPage::Back)); d->toolbar->addAction(d->browser->pageAction(QWebPage::Forward)); d->toolbar->addAction(d->browser->pageAction(QWebPage::Reload)); d->toolbar->addAction(d->browser->pageAction(QWebPage::Stop)); #endif QAction* const gohome = new QAction(QIcon::fromTheme(QLatin1String("go-home")), i18n("Home"), this); gohome->setToolTip(i18n("Go back to Home page")); d->toolbar->addAction(gohome); QAction* const deskweb = new QAction(QIcon::fromTheme(QLatin1String("internet-web-browser")), i18n("Desktop Browser"), this); deskweb->setToolTip(i18n("Open Home page with default desktop Web browser")); if (!hideDeskBrowser) { d->toolbar->addAction(deskweb); } // -------------------------- d->searchbar = new SearchTextBar(this, QLatin1String("WebBrowserDlgSearchBar")); d->searchbar->setHighlightOnResult(true); d->progressbar = new StatusProgressBar(this); d->progressbar->setProgressTotalSteps(100); d->progressbar->setAlignment(Qt::AlignLeft); d->progressbar->setNotify(false); // ---------------------- QGridLayout* const grid = new QGridLayout(this); grid->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); grid->addWidget(d->toolbar, 0, 0, 1, 1); grid->addWidget(d->searchbar, 0, 2, 1, 1); grid->addWidget(d->browser, 1, 0, 1, 3); grid->addWidget(d->progressbar, 2, 0, 1, 3); grid->setColumnStretch(1, 10); grid->setRowStretch(1, 10); setLayout(grid); // ---------------------- /* #if QT_VERSION >= 0x050700 connect(d->browser, SIGNAL(iconChanged(const QIcon&)), this, SLOT(slotIconChanged(const QIcon&))); #endif */ connect(d->browser, SIGNAL(titleChanged(QString)), this, SLOT(slotTitleChanged(QString))); connect(d->browser, SIGNAL(urlChanged(QUrl)), this, SLOT(slotUrlChanged(QUrl))); connect(d->browser, SIGNAL(loadStarted()), this, SLOT(slotLoadingStarted())); connect(d->browser, SIGNAL(loadFinished(bool)), this, SLOT(slotLoadingFinished(bool))); connect(d->searchbar, SIGNAL(signalSearchTextSettings(SearchTextSettings)), this, SLOT(slotSearchTextChanged(SearchTextSettings))); connect(d->browser, SIGNAL(loadProgress(int)), d->progressbar, SLOT(setProgressValue(int))); connect(gohome, SIGNAL(triggered()), this, SLOT(slotGoHome())); connect(deskweb, SIGNAL(triggered()), this, SLOT(slotDesktopWebBrowser())); // ---------------------- KConfigGroup group = KSharedConfig::openConfig()->group("WebBrowserDlg"); winId(); windowHandle()->resize(800, 600); DXmlGuiWindow::restoreWindowSize(windowHandle(), group); resize(windowHandle()->size()); slotGoHome(); } WebBrowserDlg::~WebBrowserDlg() { delete d; } void WebBrowserDlg::closeEvent(QCloseEvent* e) { KConfigGroup group = KSharedConfig::openConfig()->group(QLatin1String("WebBrowserDlg")); DXmlGuiWindow::saveWindowSize(windowHandle(), group); emit closeView(false); e->accept(); } void WebBrowserDlg::slotUrlChanged(const QUrl& url) { d->progressbar->setText(url.toString()); emit urlChanged(url); } void WebBrowserDlg::slotTitleChanged(const QString& title) { setWindowTitle(title); } void WebBrowserDlg::slotIconChanged(const QIcon& icon) { setWindowIcon(icon); } void WebBrowserDlg::slotLoadingStarted() { d->progressbar->setProgressBarMode(StatusProgressBar::ProgressBarMode); } void WebBrowserDlg::slotLoadingFinished(bool b) { QString curUrl = d->browser->url().toString(); d->progressbar->setProgressBarMode(StatusProgressBar::TextMode, curUrl); if (!b) { d->progressbar->setText(i18n("Cannot load page %1", curUrl)); } } void WebBrowserDlg::slotSearchTextChanged(const SearchTextSettings& settings) { #ifdef HAVE_QWEBENGINE d->browser->findText(settings.text, (settings.caseSensitive == Qt::CaseSensitive) ? QWebEnginePage::FindCaseSensitively : QWebEnginePage::FindFlags(), [this](bool found) { d->searchbar->slotSearchResult(found); }); #else bool found = d->browser->findText( settings.text, - (settings.caseSensitive == Qt::CaseInsensitive) ? QWebPage::FindCaseSensitively + (settings.caseSensitive == Qt::CaseInsensitive) ? QWebPage::FindCaseSensitively : QWebPage::FindFlags()); d->searchbar->slotSearchResult(found); #endif } void WebBrowserDlg::slotGoHome() { d->browser->setUrl(d->home); } void WebBrowserDlg::slotDesktopWebBrowser() { QDesktopServices::openUrl(d->home); } } // namespace Digikam diff --git a/core/libs/dimg/dimg.h b/core/libs/dimg/dimg.h index 06361db97b..35e726b41c 100644 --- a/core/libs/dimg/dimg.h +++ b/core/libs/dimg/dimg.h @@ -1,799 +1,799 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2005-06-14 * Description : digiKam 8/16 bits image management API * * Copyright (C) 2005 by Renchi Raju * Copyright (C) 2005-2020 by Gilles Caulier * Copyright (C) 2006-2013 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. * * ============================================================ */ #ifndef DIGIKAM_DIMG_H #define DIGIKAM_DIMG_H // Qt includes #include #include #include #include #include #include #include // Local includes #include "digikam_export.h" #include "drawdecoding.h" #include "dcolor.h" #include "dcolorcomposer.h" #include "historyimageid.h" #include "iccprofile.h" #include "metaengine_data.h" class QImage; class QPixmap; namespace Digikam { class ExposureSettingsContainer; class DImageHistory; class FilterAction; class IccTransform; class DImgLoaderObserver; class DIGIKAM_EXPORT DImg { public: enum FORMAT { /** * NOTE: Order is important here: * See filesaveoptionbox.cpp which use these values to fill a stack of widgets. */ NONE = 0, JPEG, PNG, TIFF, JP2K, PGF, HEIF, // Others file formats. RAW, QIMAGE ///< QImage or ImageMagick }; enum ANGLE { ROT90 = 0, ROT180, ROT270, ROTNONE }; enum FLIP { HORIZONTAL = 0, VERTICAL }; enum COLORMODEL { COLORMODELUNKNOWN = 0, RGB, GRAYSCALE, MONOCHROME, INDEXED, YCBCR, CMYK, CIELAB, COLORMODELRAW }; public: /** * Identify file format */ static FORMAT fileFormat(const QString& filePath); static QString formatToMimeType(FORMAT frm); public: class Private; public: /** * Create null image */ DImg(); /** * Load image using QByteArray as file path */ explicit DImg(const QByteArray& filePath, DImgLoaderObserver* const observer = nullptr, const DRawDecoding& rawDecodingSettings = DRawDecoding()); /** * Load image using QString as file path */ explicit DImg(const QString& filePath, DImgLoaderObserver* const observer = nullptr, const DRawDecoding& rawDecodingSettings = DRawDecoding()); /** * Copy image: Creates a shallow copy that refers to the same shared data. * The two images will be equal. Call detach() or copy() to create deep copies. */ DImg(const DImg& image); /** * Copy image: Creates a copy of a QImage object. If the QImage is null, a * null DImg will be created. */ explicit DImg(const QImage& image); /** * Create image from data. * If data is 0, a new buffer will be allocated, otherwise the given data will be used: * If copydata is true, the data will be copied to a newly allocated buffer. * If copyData is false, this DImg object will take ownership of the data pointer. * If there is an alpha channel, the data shall be in non-premultiplied form (unassociated alpha). */ DImg(uint width, uint height, bool sixteenBit, bool alpha = false, uchar* const data = nullptr, bool copyData = true); ~DImg(); /** * Equivalent to the copy constructor */ DImg& operator=(const DImg& image); /** Detaches from shared data and makes sure that this image * is the only one referring to the data. * If multiple images share common data, this image makes a copy * of the data and detaches itself from the sharing mechanism. * Nothing is done if there is just a single reference. */ void detach(); /** Returns whether two images are equal. * Two images are equal if and only if they refer to the same shared data. * (Thus, DImg() == DImg() is not true, both instances refer two their * own shared data. image == DImg(image) is true.) * If two or more images refer to the same data, they have the same * image data, bits() returns the same data, they have the same metadata, * and a change to one image also affects the others. * Call detach() to split one image from the group of equal images. */ bool operator==(const DImg& image) const; /** * Replaces image data of this object. Metadata is unchanged. Parameters like constructor above. */ void putImageData(uint width, uint height, bool sixteenBit, bool alpha, uchar* const data, bool copyData = true); /** * Overloaded function, provided for convenience, behaves essentially * like the function above if data is not 0. * Uses current width, height, sixteenBit, and alpha values. * If data is 0, the current data is deleted and the image is set to null * (But metadata unchanged). */ void putImageData(uchar* const data, bool copyData = true); /** * Reset metadata and image data to null image */ void reset(); /** * Reset metadata, but do not change image data */ void resetMetaData(); /** * Returns the data of this image. * Ownership of the buffer is passed to the caller, this image will be null afterwards. */ uchar* stripImageData(); bool load(const QString& filePath, DImgLoaderObserver* const observer = nullptr, const DRawDecoding& rawDecodingSettings = DRawDecoding()); bool load(const QString& filePath, bool loadMetadata, bool loadICCData, bool loadUniqueHash, bool loadHistory, DImgLoaderObserver* const observer = nullptr, const DRawDecoding& rawDecodingSettings = DRawDecoding()); bool load(const QString& filePath, int loadFlags, DImgLoaderObserver* const observer, const DRawDecoding& rawDecodingSettings = DRawDecoding()); bool save(const QString& filePath, FORMAT frm, DImgLoaderObserver* const observer = nullptr); bool save(const QString& filePath, const QString& format, DImgLoaderObserver* const observer = nullptr); /** * It is common that images are not directly saved to the destination path. * For this reason, save() does not call addAsReferredImage(), and the stored * save path may be wrong. * Call this method after save() with the final destination path. * This path will be stored in the image history as well. */ void imageSavedAs(const QString& savePath); /** * Loads most parts of the meta information, but never the image data. * If loadMetadata is true, the metadata will be available with getComments, getExif, getIptc, getXmp . * If loadICCData is true, the ICC profile will be available with getICCProfile. */ bool loadItemInfo(const QString& filePath, bool loadMetadata = true, bool loadICCData = true, bool loadUniqueHash = true, bool loadImageHistory = true); bool isNull() const; uint width() const; uint height() const; QSize size() const; uchar* copyBits() const; uchar* bits() const; uchar* scanLine(uint i) const; bool hasAlpha() const; bool sixteenBit() const; uint numBytes() const; uint numPixels() const; /** * Return the number of bytes depth of one pixel : 4 (non sixteenBit) or 8 (sixteen) */ int bytesDepth() const; /** * Return the number of bits depth of one color component for one pixel : 8 (non sixteenBit) or 16 (sixteen) */ int bitsDepth() const; /** * Returns the file path from which this DImg was originally loaded. * Returns a null string if the DImg was not loaded from a file. */ QString originalFilePath() const; /** * Returns the file path to which this DImg was saved. * Returns the file path set with imageSavedAs(), if that was not called, * save(), if that was not called, a null string. */ QString lastSavedFilePath() const; /** * Returns the color model in which the image was stored in the file. * The color space of the loaded image data is always RGB. */ COLORMODEL originalColorModel() const; /** * Returns the bit depth (in bits per channel, e.g. 8 or 16) of the original file. */ int originalBitDepth() const; /** * Returns the size of the original file. */ QSize originalSize() const; /** * Returns the file format in form of the FORMAT enum that was detected in the load() * method. Other than the format attribute which is written by the DImgLoader, * this can include the QIMAGE or NONE values. * Returns NONE for images that have not been loaded. * For unknown image formats, a value of QIMAGE can be returned to indicate that the * QImage-based loader will have been used. To find out if this has worked, check * the return value you got from load(). */ FORMAT detectedFormat() const; /** * Returns the format string as written by the image loader this image was originally * loaded from. Format strings used include JPEG, PNG, TIFF, PGF, JP2K, RAW, PPM. * For images loaded with the platform QImage loader, the file suffix is used. * Returns null if this DImg was not loaded from a file, but created in memory. */ QString format() const; /** * Returns the format string of the format that this image was last saved to. * An image can be loaded from a file - retrieve that format with fileFormat() * and loadedFormat() - and can the multiple times be saved to different formats. * Format strings used include JPG, PGF, PNG, TIFF and JP2K. * If this file was not save, a null string is returned. */ QString savedFormat() const; /** * Returns the DRawDecoding options that this DImg was loaded with. * If this is not a RAW image or no options were specified, returns DRawDecoding(). */ DRawDecoding rawDecodingSettings() const; /** * Access a single pixel of the image. * These functions add some safety checks and then use the methods from DColor. * In optimized code working directly on the data, * better use the inline methods from DColor. */ DColor getPixelColor(uint x, uint y) const; void setPixelColor(uint x, uint y, const DColor& color); void prepareSubPixelAccess(); DColor getSubPixelColor(float x, float y) const; DColor getSubPixelColorFast(float x, float y) const; /** * If the image has an alpha channel, check if there exist pixels * which actually have non-opaque color, that is alpha < 1.0. * Note that all pixels are scanned to reach a return value of "false". * If hasAlpha() is false, always returns false. */ bool hasTransparentPixels() const; /** * Return true if the original image file format cannot be saved. * This is depending of DImgLoader::save() implementation. For example * RAW file formats are supported by DImg using dcraw than cannot support * writing operations. */ bool isReadOnly() const; /** * Metadata manipulation methods */ MetaEngineData getMetadata() const; IccProfile getIccProfile() const; void setMetadata(const MetaEngineData& data); void setIccProfile(const IccProfile& profile); void setAttribute(const QString& key, const QVariant& value); QVariant attribute(const QString& key) const; bool hasAttribute(const QString& key) const; void removeAttribute(const QString& key); void setEmbeddedText(const QString& key, const QString& text); QString embeddedText(const QString& key) const; const DImageHistory& getItemHistory() const; DImageHistory& getItemHistory(); void setItemHistory(const DImageHistory& history); bool hasImageHistory() const; DImageHistory getOriginalImageHistory() const; void addFilterAction(const FilterAction& action); /** * Sets a step in the history to constitute the beginning of a branch. * Use setHistoryBranch() to take getOriginalImageHistory() and set the first added step as a branch. * Use setHistoryBranchForLastSteps(n) to start the branch before the last n steps in the history. * (Assume the history had 3 steps and you added 2, call setHistoryBranchForLastSteps(2)) * Use setHistoryBranchAfter() if have a copy of the history before branching, * the first added step on top of that history will be made a branch. */ void setHistoryBranchAfter(const DImageHistory& historyBeforeBranch, bool isBranch = true); void setHistoryBranchForLastSteps(int numberOfLastHistorySteps, bool isBranch = true); void setHistoryBranch(bool isBranch = true); /** * When saving, several changes to the image metadata are necessary * before it can safely be written to the new file. * This method updates the stored DMetadata object in preparation to a subsequent * call to save() with the same target file. * 'intendedDestPath' is the finally intended file name. Do not give the temporary * file name if you are going to save() to a temp file. * 'destMimeType' is destination type mime. In some cases, metadata is updated depending on this value. * 'originalFileName' is the original file's name, for simplistic history tracking in metadata. * This is completely independent from the DImageHistory framework. * For the 'flags' see below. * Not all steps are optional and can be controlled with flags. */ enum PrepareMetadataFlag { /** * A small preview can be stored in the metadata. * Remove old preview entries */ RemoveOldMetadataPreviews = 1 << 0, /** * Create a new preview from current image data. */ CreateNewMetadataPreview = 1 << 1, /** * Set the exif orientation tag to "normal" * Applicable if the image data was rotated according to the tag */ ResetExifOrientationTag = 1 << 2, /** * Creates a new UUID for the image history. * Applicable if the file was changed. */ CreateNewImageHistoryUUID = 1 << 3, PrepareMetadataFlagsAll = RemoveOldMetadataPreviews | CreateNewMetadataPreview | ResetExifOrientationTag | CreateNewImageHistoryUUID }; Q_DECLARE_FLAGS(PrepareMetadataFlags, PrepareMetadataFlag) void prepareMetadataToSave(const QString& intendedDestPath, const QString& destMimeType, const QString& originalFileName = QString(), PrepareMetadataFlags flags = PrepareMetadataFlagsAll); /** * For convenience: Including all flags, except for ResetExifOrientationTag which can be selected. * Uses originalFilePath() to fill the original file name. */ void prepareMetadataToSave(const QString& intendedDestPath, const QString& destMimeType, bool resetExifOrientationTag); /** * Create a HistoryImageId for _this_ image _already_ saved at the given file path. */ HistoryImageId createHistoryImageId(const QString& filePath, HistoryImageId::Type type); /** * If you have saved this DImg to filePath, and want to continue using this DImg object * to add further changes to the image history, you can call this method to add to the image history * a reference to the just saved image. * First call updateMetadata(), then call save(), then call addAsReferredImage(). * Do not call this directly after loading, before applying any changes: * The history is correctly initialized when loading. * If you need to insert the referred file to an entry which is not the last entry, * which may happen if the added image was saved after this image's history was created, * you can use insertAsReferredImage. * The added id is returned. */ HistoryImageId addAsReferredImage(const QString& filePath, HistoryImageId::Type type = HistoryImageId::Intermediate); void addAsReferredImage(const HistoryImageId& id); void insertAsReferredImage(int afterHistoryStep, const HistoryImageId& otherImagesId); /** * In the history, adjusts the UUID of the ImageHistoryId of the current file. * Call this if you have associated a UUID with this file which is not written to the metadata. * If there is already a UUID present, read from metadata, it will not be replaced. */ void addCurrentUniqueImageId(const QString& uuid); /** * Retrieves the Exif orientation, either from the LoadSaveThread info provider if available, * or from the metadata */ int exifOrientation(const QString& filePath); - /** + /** * When loaded from a file, some attributes like format and isReadOnly still depend on this * originating file. When saving in a different format to a different file, * you may wish to switch these attributes to the new file. * - fileOriginData() returns the current origin data, bundled in the returned QVariant. * - setFileOriginData() takes such a variant and adjusts the properties * - lastSavedFileOriginData() returns the origin data as if the image was loaded from * the last saved image. * - switchOriginToLastSaved is equivalent to setting origin data returned from lastSavedFileOriginData() * * Example: an image loaded from a RAW and saved to PNG will be read-only and format RAW. * After calling switchOriginToLastSaved, it will not be read-only, format will be PNG, * and rawDecodingSettings will be null. detectedFormat() will not change. * In the history, the last referred image that was added (as intermediate) is made * the new Current image. * NOTE: Set the saved image path with imageSavedAs() before! */ QVariant fileOriginData() const; void setFileOriginData(const QVariant& data); QVariant lastSavedFileOriginData() const; void switchOriginToLastSaved(); /** * Return a deep copy of full image */ DImg copy() const; /** * Return a deep copy of the image, but do not include metadata. */ DImg copyImageData() const; /** * Return an image that contains a deep copy of * this image's metadata and the information associated * with the image data (width, height, hasAlpha, sixteenBit), * but no image data, i.e. isNull() is true. */ DImg copyMetaData() const; /** * Return a region of image */ DImg copy(const QRect& rect) const; DImg copy(const QRectF& relativeRect) const; DImg copy(int x, int y, int w, int h) const; /** * Copy a region of pixels from a source image to this image. * Parameters: * sx|sy Coordinates in the source image of the rectangle to be copied * w h Width and height of the rectangle (Default, or when both are -1: whole source image) * dx|dy Coordinates in this image of the rectangle in which the region will be copied * (Default: 0|0) * The bit depth of source and destination must be identical. */ void bitBltImage(const DImg* const src, int dx, int dy); void bitBltImage(const DImg* const src, int sx, int sy, int dx, int dy); void bitBltImage(const DImg* const src, int sx, int sy, int w, int h, int dx, int dy); void bitBltImage(const uchar* const src, int sx, int sy, int w, int h, int dx, int dy, uint swidth, uint sheight, int sdepth); /** * Blend src image on this image (this is dest) with the specified composer * and multiplication flags. See documentation of DColorComposer for more info. * For the other arguments, see documentation of bitBltImage above. */ void bitBlendImage(DColorComposer* const composer, const DImg* const src, int sx, int sy, int w, int h, int dx, int dy, DColorComposer::MultiplicationFlags multiplicationFlags = DColorComposer::NoMultiplication); /** * For the specified region, blend this image on the given color with the specified * composer and multiplication flags. See documentation of DColorComposer for more info. * Note that the result pixel is again written to this image, which is, for the blending, source. */ void bitBlendImageOnColor(DColorComposer* const composer, const DColor& color, int x, int y, int w, int h, DColorComposer::MultiplicationFlags multiplicationFlags = DColorComposer::NoMultiplication); void bitBlendImageOnColor(const DColor& color, int x, int y, int w, int h); void bitBlendImageOnColor(const DColor& color); /** * QImage wrapper methods */ QImage copyQImage() const; QImage copyQImage(const QRect& rect) const; QImage copyQImage(const QRectF& relativeRect) const; QImage copyQImage(int x, int y, int w, int h) const; /** * Crop image to the specified region */ void crop(const QRect& rect); void crop(int x, int y, int w, int h); /** * Set width and height of this image, smoothScale it to the given size */ void resize(int w, int h); /** * If the image has an alpha channel and transparent pixels, * it will be blended on the specified color and the alpha channel will be removed. * This is a no-op if hasTransparentPixels() is false, but this method can be expensive, * therefore it is _not_ checked inside removeAlphaChannel(). * (the trivial hasAlpha() is checked) */ void removeAlphaChannel(const DColor& destColor); void removeAlphaChannel(); /** * Return a version of this image scaled to the specified size with the specified mode. * See QSize documentation for information on available modes */ DImg smoothScale(int width, int height, Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio) const; DImg smoothScale(const QSize& destSize, Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio) const; /** * Executes the same scaling as smoothScale(width, height), but from the result of this call, * returns only the section specified by clipx, clipy, clipwidth, clipheight. * This is thus equivalent to calling * Dimg scaled = smoothScale(width, height); scaled.crop(clipx, clipy, clipwidth, clipheight); * but potentially much faster. * In smoothScaleSection, you specify the source region, here, the result region. * It will often not be possible to find _integer_ source coordinates for a result region! */ DImg smoothScaleClipped(int width, int height, int clipx, int clipy, int clipwidth, int clipheight) const; DImg smoothScaleClipped(const QSize& destSize, const QRect& clip) const; /** * Take the region specified by the rectangle sx|sy, width and height sw * sh, * and scale it to an image with size dw * dh */ DImg smoothScaleSection(int sx, int sy, int sw, int sh, int dw, int dh) const; DImg smoothScaleSection(const QRect& sourceRect, const QSize& destSize) const; void rotate(ANGLE angle); void flip(FLIP direction); /** * Rotates and/or flip the DImg according to the given DMetadata::Orientation, * so that the current state is orientation and the resulting step is normal orientation. * Returns true if the image was actually rotated or flipped (e.g. if ORIENTATION_NORMAL * is given, returns false, because no action is taken). */ bool rotateAndFlip(int orientation); /** * Reverses the previous function. */ bool reverseRotateAndFlip(int orientation); /** * Utility to make sure that an image is rotated according to Exif tag. * Detects if an image has previously already been rotated: You can * call this method more than one time on the same image. * Returns true if the image has actually been rotated or flipped. * Returns false if a rotation was not needed. */ bool wasExifRotated(); bool exifRotate(const QString& filePath); /** * Reverses the previous function */ bool reverseExifRotate(const QString& filePath); /** * Returns current DMetadata::Orientation from DImg */ int orientation() const; /** Rotates and/or flip the DImg according to the given transform action, * which is a MetaEngineRotation::TransformAction. * Returns true if the image was actually rotated or flipped. */ bool transform(int transformAction); QPixmap convertToPixmap() const; QPixmap convertToPixmap(IccTransform& monitorICCtrans) const; /** * Return a mask image where pure white and pure black pixels are over-colored. * This way is used to identify over and under exposed pixels. */ QImage pureColorMask(ExposureSettingsContainer* const expoSettings) const; /** * Convert depth of image. Depth is bytesDepth * bitsDepth. * If depth is 32, converts to 8 bits, * if depth is 64, converts to 16 bits. */ void convertDepth(int depth); /** * Wrapper methods for convertDepth */ void convertToSixteenBit(); void convertToEightBit(); void convertToDepthOfImage(const DImg* const otherImage); /** * Fill whole image with specified color. * The bit depth of the color must be identical to the depth of this image. */ void fill(const DColor& color); /** * This methods return a 128-bit MD5 hex digest which is meant to uniquely identify * the file. The hash is calculated on parts of the file and the file metadata. * It cannot be used to find similar images. It is not calculated from the image data. * The hash will be returned as a 32-byte hexadecimal string. * * If you already have a DImg object of the file, use the member method. * The object does not need to have the full image data loaded, but it shall at least * have been loaded with loadItemInfo with loadMetadata = true, or have the metadata * set later with setComments, setExif, setIptc, setXmp. * If the object does not have the metadata loaded, a non-null, but invalid hash will * be returned! In this case, use the static method. * If the image has been loaded with loadUniqueHash = true, the hash can be retrieved * with the member method. * * You do not need a DImg object of the file to retrieve the unique hash; * Use the static method and pass just the file path. */ QByteArray getUniqueHash(); static QByteArray getUniqueHash(const QString& filePath); /** * This methods return a 128-bit MD5 hex digest which is meant to uniquely identify * the file. The hash is calculated on parts of the file. * It cannot be used to find similar images. It is not calculated from the image data. * The hash will be returned as a 32-byte hexadecimal string. * * If you already have a DImg object loaded from the file, use the member method. * If the image has been loaded with loadUniqueHash = true, the hash will already * be available. * * You do not need a DImg object of the file to retrieve the unique hash; * Use the static method and pass just the file path. */ QByteArray getUniqueHashV2(); static QByteArray getUniqueHashV2(const QString& filePath); /** * This method creates a new 256-bit UUID meant to be globally unique. * The UUID will be returned as a 64-byte hexadecimal string. * At least 128bits of the UUID will be created by the platform random number * generator. The rest may be created from a content-based hash similar to the uniqueHash, see above. * This method only generates a new UUID for this image without in any way changing this image object * or saving the UUID anywhere. */ QByteArray createImageUniqueId(); /** * Helper method to translate enum values to user presentable strings */ static QString colorModelToString(COLORMODEL colorModel); /** * Return true if image file is an animation, as GIFa or NMG */ static bool isAnimatedImage(const QString& filePath); private: DImg(const DImg& image, int w, int h); void copyMetaData(const QExplicitlySharedDataPointer& src); void copyImageData(const QExplicitlySharedDataPointer& src); void setImageData(bool null, uint width, uint height, bool sixteenBit, bool alpha); void setImageDimension(uint width, uint height); size_t allocateData() const; bool clipped(int& x, int& y, int& w, int& h, uint width, uint height) const; QDateTime creationDateFromFilesystem(const QFileInfo& fileInfo) const; static QByteArray createUniqueHash(const QString& filePath, const QByteArray& ba); static QByteArray createUniqueHashV2(const QString& filePath); void bitBlt(const uchar* const src, uchar* const dest, int sx, int sy, int w, int h, int dx, int dy, uint swidth, uint sheight, uint dwidth, uint dheight, bool sixteenBit, int sdepth, int ddepth); void bitBlend(DColorComposer* const composer, uchar* const src, uchar* const dest, int sx, int sy, int w, int h, int dx, int dy, uint swidth, uint sheight, uint dwidth, uint dheight, bool sixteenBit, int sdepth, int ddepth, DColorComposer::MultiplicationFlags multiplicationFlags); void bitBlendOnColor(DColorComposer* const composer, const DColor& color, uchar* data, int x, int y, int w, int h, uint width, uint height, bool sixteenBit, int depth, DColorComposer::MultiplicationFlags multiplicationFlags); bool normalizeRegionArguments(int& sx, int& sy, int& w, int& h, int& dx, int& dy, uint swidth, uint sheight, uint dwidth, uint dheight) const; private: QExplicitlySharedDataPointer m_priv; friend class DImgLoader; }; } // namespace Digikam Q_DECLARE_OPERATORS_FOR_FLAGS(Digikam::DImg::PrepareMetadataFlags) #endif // DIGIKAM_DIMG_H diff --git a/core/libs/dimg/filters/bw/infraredfilter.h b/core/libs/dimg/filters/bw/infraredfilter.h index 05e43c51c6..77c79a4e13 100644 --- a/core/libs/dimg/filters/bw/infraredfilter.h +++ b/core/libs/dimg/filters/bw/infraredfilter.h @@ -1,116 +1,116 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2005-05-25 * Description : Infrared threaded image filter. * * Copyright (C) 2005-2020 by Gilles Caulier * Copyright (C) 2006-2010 by Marcel Wiesweg * Copyright (C) 2010 by Martin Klapetek * * 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. * * ============================================================ */ #ifndef DIGIKAM_INFRARED_FILTER_H #define DIGIKAM_INFRARED_FILTER_H // Local includes #include "dimgthreadedfilter.h" #include "digikam_export.h" namespace Digikam { class DIGIKAM_EXPORT InfraredContainer { public: explicit InfraredContainer() : sensibility(200), redGain(0.4), greenGain(2.1), blueGain(-0.8) { }; ~InfraredContainer() { }; public: /// Sensibility: 200..2600 ISO int sensibility; double redGain; double greenGain; double blueGain; }; // --------------------------------------------------------------------------- class DIGIKAM_EXPORT InfraredFilter : public DImgThreadedFilter { public: explicit InfraredFilter(QObject* const parent = nullptr); explicit InfraredFilter(DImg* const orgImage, - QObject* const parent=nullptr, + QObject* const parent=nullptr, const InfraredContainer& settings=InfraredContainer()); ~InfraredFilter(); static QString FilterIdentifier() { return QLatin1String("digikam:InfraredFilter"); } static QString DisplayableName(); static QList SupportedVersions() { return QList() << 1; } static int CurrentVersion() { return 1; } virtual QString filterIdentifier() const override { return FilterIdentifier(); } virtual FilterAction filterAction() override; void readParameters(const FilterAction& action) override; private: void filterImage() override; inline int intMult8(uint a, uint b); inline int intMult16(uint a, uint b); private: InfraredContainer m_settings; }; } // namespace Digikam #endif // DIGIKAM_INFRARED_FILTER_H diff --git a/core/libs/dimg/history/dimagehistory.cpp b/core/libs/dimg/history/dimagehistory.cpp index 29948d3793..064a20db82 100644 --- a/core/libs/dimg/history/dimagehistory.cpp +++ b/core/libs/dimg/history/dimagehistory.cpp @@ -1,807 +1,807 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-06-02 * Description : class for manipulating modifications changeset for non-destruct. editing * * Copyright (C) 2010 by Marcel Wiesweg * Copyright (C) 2010 by Martin Klapetek * * 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 "dimagehistory.h" // Qt includes #include #include #include #include #include #include // Local includes #include "digikam_debug.h" namespace Digikam { class Q_DECL_HIDDEN DImageHistory::Private : public QSharedData { public: explicit Private() { } QList entries; }; // ----------------------------------------------------------------------------------------------- class Q_DECL_HIDDEN PrivateSharedNull : public QSharedDataPointer { public: PrivateSharedNull() : QSharedDataPointer(new DImageHistory::Private) { } }; Q_GLOBAL_STATIC(PrivateSharedNull, imageHistoryPrivSharedNull) // ----------------------------------------------------------------------------------------------- DImageHistory::DImageHistory() : d(*imageHistoryPrivSharedNull) { } DImageHistory::DImageHistory(const DImageHistory& other) { d = other.d; } DImageHistory::~DImageHistory() { } DImageHistory& DImageHistory::operator=(const DImageHistory& other) { d = other.d; return *this; } bool DImageHistory::isNull() const { return (d == *imageHistoryPrivSharedNull); } bool DImageHistory::isEmpty() const { return d->entries.isEmpty(); } bool DImageHistory::isValid() const { if (d->entries.isEmpty()) { return false; } else if ((d->entries.count() == 1) && (d->entries.first().referredImages.count() == 1) && d->entries.first().referredImages.first().isCurrentFile()) { return false; } else { foreach (const Entry& e, d->entries) { if (!e.action.isNull()) { return true; } foreach (const HistoryImageId& id, e.referredImages) { if (id.isValid() && !id.isCurrentFile()) { return true; } } } } return false; } int DImageHistory::size() const { return d->entries.size(); } static bool operator==(const DImageHistory::Entry& e1, const DImageHistory::Entry& e2) { return ((e1.action == e2.action) && (e1.referredImages == e2.referredImages)); } bool DImageHistory::operator==(const DImageHistory& other) const { return (d->entries == other.d->entries); } bool DImageHistory::operator<(const Digikam::DImageHistory& other) const { if (d->entries.size() < other.size()) { return true; } return false; } bool DImageHistory::operator>(const Digikam::DImageHistory& other) const { if (d->entries.size() > other.size()) { return true; } return false; } QList &DImageHistory::entries() { return d->entries; } const QList &DImageHistory::entries() const { return d->entries; } DImageHistory::Entry& DImageHistory::operator[](int i) { return d->entries[i]; } const DImageHistory::Entry& DImageHistory::operator[](int i) const { return d->entries.at(i); } DImageHistory& DImageHistory::operator<<(const FilterAction& action) { if (action.isNull()) { return *this; } Entry entry; entry.action = action; d->entries << entry; //qCDebug(DIGIKAM_DIMG_LOG) << "Entry added, total count " << d->entries.count(); return *this; } DImageHistory& DImageHistory::operator<<(const HistoryImageId& id) { appendReferredImage(id); return *this; } void DImageHistory::appendReferredImage(const HistoryImageId& id) { insertReferredImage(d->entries.size() - 1, id); } void DImageHistory::insertReferredImage(int index, const HistoryImageId& id) { if (!id.isValid()) { qCWarning(DIGIKAM_DIMG_LOG) << "Attempt to add an invalid HistoryImageId"; return; } index = qBound(0, index, d->entries.size() - 1); if (id.isCurrentFile()) { // enforce to have exactly one Current id adjustReferredImages(); } if (d->entries.isEmpty()) { d->entries << Entry(); } d->entries[index].referredImages << id; } void DImageHistory::removeLast() { if (!d->entries.isEmpty()) { d->entries.removeLast(); } } const FilterAction& DImageHistory::action(int i) const { return d->entries.at(i).action; } QList DImageHistory::allActions() const { QList actions; foreach (const Entry& entry, d->entries) { if (!entry.action.isNull()) { actions << entry.action; } } return actions; } int DImageHistory::actionCount() const { int count = 0; foreach (const Entry& entry, d->entries) { if (!entry.action.isNull()) { ++count; } } return count; } bool DImageHistory::hasActions() const { foreach (const Entry& entry, d->entries) { if (!entry.action.isNull()) { return true; } } return false; } QList &DImageHistory::referredImages(int i) { return d->entries[i].referredImages; } const QList &DImageHistory::referredImages(int i) const { return d->entries.at(i).referredImages; } QList DImageHistory::allReferredImages() const { QList ids; foreach (const Entry& entry, d->entries) { ids << entry.referredImages; } return ids; } bool DImageHistory::hasReferredImages() const { foreach (const Entry& entry, d->entries) { if (!entry.referredImages.isEmpty()) { return true; } } return false; } bool DImageHistory::hasReferredImageOfType(HistoryImageId::Type type) const { foreach (const Entry& entry, d->entries) { foreach (const HistoryImageId& id, entry.referredImages) { if (id.m_type == type) { return true; } } } return false; } bool DImageHistory::hasCurrentReferredImage() const { return hasReferredImageOfType(HistoryImageId::Current); } bool DImageHistory::hasOriginalReferredImage() const { return hasReferredImageOfType(HistoryImageId::Original); } QList DImageHistory::referredImagesOfType(HistoryImageId::Type type) const { QList ids; foreach (const Entry& entry, d->entries) { foreach (const HistoryImageId& id, entry.referredImages) { if (id.m_type == type) { ids << id; } } } return ids; } HistoryImageId DImageHistory::currentReferredImage() const { foreach (const Entry& entry, d->entries) { foreach (const HistoryImageId& id, entry.referredImages) { if (id.isCurrentFile()) { return id; } } } return HistoryImageId(); } HistoryImageId DImageHistory::originalReferredImage() const { foreach (const Entry& entry, d->entries) { foreach (const HistoryImageId& id, entry.referredImages) { if (id.isOriginalFile()) { return id; } } } return HistoryImageId(); } void DImageHistory::clearReferredImages() { for (int i = 0 ; i < d->entries.size() ; ++i) { d->entries[i].referredImages.clear(); } } void DImageHistory::adjustReferredImages() { for (int i = 0 ; i < d->entries.size() ; ++i) { Entry& entry = d->entries[i]; for (int e = 0 ; e < entry.referredImages.size() ; ++e) { HistoryImageId& id = entry.referredImages[e]; if (id.isCurrentFile()) { - id.m_type = (i == 0) ? HistoryImageId::Original + id.m_type = (i == 0) ? HistoryImageId::Original : HistoryImageId::Intermediate; } } } } void DImageHistory::adjustCurrentUuid(const QString& uuid) { for (int i = 0 ; i < d->entries.size() ; ++i) { Entry& entry = d->entries[i]; for (int e = 0 ; e < entry.referredImages.size() ; ++e) { HistoryImageId& id = entry.referredImages[e]; if (id.isCurrentFile()) { if (id.m_uuid.isNull()) { id.m_uuid = uuid; } } } } } void DImageHistory::purgePathFromReferredImages(const QString& path, const QString& fileName) { for (int i = 0 ; i < d->entries.size() ; ++i) { Entry& entry = d->entries[i]; for (int e = 0 ; e < entry.referredImages.size() ; ++e) { HistoryImageId& id = entry.referredImages[e]; { if ((id.m_filePath == path) && (id.m_fileName == fileName)) { id.m_filePath.clear(); id.m_fileName.clear(); } } } } } void DImageHistory::moveCurrentReferredImage(const QString& newPath, const QString& newFileName) { for (int i = 0 ; i < d->entries.size() ; ++i) { Entry& entry = d->entries[i]; for (int e = 0 ; e < entry.referredImages.size() ; ++e) { HistoryImageId& id = entry.referredImages[e]; if (id.isCurrentFile()) { id.setPath(newPath); id.setFileName(newFileName); } } } } QString DImageHistory::toXml() const { QString xmlHistory; QXmlStreamWriter stream(&xmlHistory); stream.setAutoFormatting(true); stream.writeStartDocument(); stream.writeStartElement(QLatin1String("history")); stream.writeAttribute(QLatin1String("version"), QString::number(1)); for (int i = 0 ; i < entries().count() ; ++i) { const Entry& step = entries().at(i); if (!step.action.isNull()) { stream.writeStartElement(QLatin1String("filter")); stream.writeAttribute(QLatin1String("filterName"), step.action.identifier()); stream.writeAttribute(QLatin1String("filterDisplayName"), step.action.displayableName()); stream.writeAttribute(QLatin1String("filterVersion"), QString::number(step.action.version())); switch (step.action.category()) { case FilterAction::ReproducibleFilter: stream.writeAttribute(QLatin1String("filterCategory"), QLatin1String("reproducible")); break; case FilterAction::ComplexFilter: stream.writeAttribute(QLatin1String("filterCategory"), QLatin1String("complex")); break; case FilterAction::DocumentedHistory: stream.writeAttribute(QLatin1String("filterCategory"), QLatin1String("documentedHistory")); break; } if (step.action.flags() & FilterAction::ExplicitBranch) { stream.writeAttribute(QLatin1String("branch"), QLatin1String("true")); } stream.writeStartElement(QLatin1String("params")); const QHash& params = step.action.parameters(); if (!params.isEmpty()) { QList keys = params.keys(); std::sort(keys.begin(), keys.end()); foreach (const QString& key, keys) { QHash::const_iterator it; for (it = params.find(key) ; it != params.end() && it.key() == key ; ++it) { stream.writeStartElement(QLatin1String("param")); stream.writeAttribute(QLatin1String("name"), key); stream.writeAttribute(QLatin1String("value"), it.value().toString()); stream.writeEndElement(); //param } } } stream.writeEndElement(); //params stream.writeEndElement(); //filter } if (!step.referredImages.isEmpty()) { foreach (const HistoryImageId& imageId, step.referredImages) { if (!imageId.isValid()) { continue; } if (imageId.isCurrentFile()) { continue; } stream.writeStartElement(QLatin1String("file")); if (!imageId.m_uuid.isNull()) { stream.writeAttribute(QLatin1String("uuid"), imageId.m_uuid); } if (imageId.isOriginalFile()) { stream.writeAttribute(QLatin1String("type"), QLatin1String("original")); } else if (imageId.isSourceFile()) { stream.writeAttribute(QLatin1String("type"), QLatin1String("source")); } stream.writeStartElement(QLatin1String("fileParams")); if (!imageId.m_fileName.isNull()) { stream.writeAttribute(QLatin1String("fileName"), imageId.m_fileName); } if (!imageId.m_filePath.isNull()) { stream.writeAttribute(QLatin1String("filePath"), imageId.m_filePath); } if (!imageId.m_uniqueHash.isNull()) { stream.writeAttribute(QLatin1String("fileHash"), imageId.m_uniqueHash); } if (imageId.m_fileSize) { stream.writeAttribute(QLatin1String("fileSize"), QString::number(imageId.m_fileSize)); } if (imageId.isOriginalFile() && !imageId.m_creationDate.isNull()) { stream.writeAttribute(QLatin1String("creationDate"), imageId.m_creationDate.toString(Qt::ISODate)); } stream.writeEndElement(); //fileParams stream.writeEndElement(); //file } } } stream.writeEndElement(); //history stream.writeEndDocument(); //qCDebug(DIGIKAM_DIMG_LOG) << xmlHistory; return xmlHistory; } DImageHistory DImageHistory::fromXml(const QString& xml) //DImageHistory { //qCDebug(DIGIKAM_DIMG_LOG) << "Parsing image history XML"; DImageHistory h; if (xml.isEmpty()) { return h; } QXmlStreamReader stream(xml); if (!stream.readNextStartElement()) { return h; } if (stream.name() != QLatin1String("history")) { return h; } QString originalUUID; QDateTime originalCreationDate; while (stream.readNextStartElement()) { if (stream.name() == QLatin1String("file")) { //qCDebug(DIGIKAM_DIMG_LOG) << "Parsing file tag"; HistoryImageId imageId(stream.attributes().value(QLatin1String("uuid")).toString()); if (stream.attributes().value(QLatin1String("type")) == QLatin1String("original")) { imageId.m_type = HistoryImageId::Original; } else if (stream.attributes().value(QLatin1String("type")) == QLatin1String("source")) { imageId.m_type = HistoryImageId::Source; } else { imageId.m_type = HistoryImageId::Intermediate; } while (stream.readNextStartElement()) { if (stream.name() == QLatin1String("fileParams")) { imageId.setFileName(stream.attributes().value(QLatin1String("fileName")).toString()); imageId.setPath(stream.attributes().value(QLatin1String("filePath")).toString()); QString date = stream.attributes().value(QLatin1String("creationDate")).toString(); if (!date.isNull()) { imageId.setCreationDate(QDateTime::fromString(date, Qt::ISODate)); } QString size = stream.attributes().value(QLatin1String("fileSize")).toString(); if (stream.attributes().hasAttribute(QLatin1String("fileHash")) && !size.isNull()) { imageId.setUniqueHash(stream.attributes().value(QLatin1String("fileHash")).toString(), size.toInt()); } stream.skipCurrentElement(); } else { stream.skipCurrentElement(); } } if (imageId.isOriginalFile()) { originalUUID = imageId.m_uuid; originalCreationDate = imageId.m_creationDate; } else { imageId.m_originalUUID = originalUUID; if (imageId.m_creationDate.isNull()) { imageId.m_creationDate = originalCreationDate; } } if (imageId.isValid()) { h << imageId; } } else if (stream.name() == QLatin1String("filter")) { //qCDebug(DIGIKAM_DIMG_LOG) << "Parsing filter tag"; FilterAction::Category c = FilterAction::ComplexFilter; QStringRef categoryString = stream.attributes().value(QLatin1String("filterCategory")); if (categoryString == QLatin1String("reproducible")) { c = FilterAction::ReproducibleFilter; } else if (categoryString == QLatin1String("complex")) { c = FilterAction::ComplexFilter; } else if (categoryString == QLatin1String("documentedHistory")) { c = FilterAction::DocumentedHistory; } FilterAction action(stream.attributes().value(QLatin1String("filterName")).toString(), stream.attributes().value(QLatin1String("filterVersion")).toString().toInt(), c); action.setDisplayableName(stream.attributes().value(QLatin1String("filterDisplayName")).toString()); if (stream.attributes().value(QLatin1String("branch")) == QLatin1String("true")) { action.addFlag(FilterAction::ExplicitBranch); } while (stream.readNextStartElement()) { if (stream.name() == QLatin1String("params")) { while (stream.readNextStartElement()) { if ((stream.name() == QLatin1String("param")) && stream.attributes().hasAttribute(QLatin1String("name"))) { action.addParameter(stream.attributes().value(QLatin1String("name")).toString(), stream.attributes().value(QLatin1String("value")).toString()); stream.skipCurrentElement(); } else { stream.skipCurrentElement(); } } } else { stream.skipCurrentElement(); } } h << action; } else { stream.skipCurrentElement(); } } if (stream.hasError()) { //TODO: error handling qCDebug(DIGIKAM_DIMG_LOG) << "An error occurred during parsing: " << stream.errorString(); } //qCDebug(DIGIKAM_DIMG_LOG) << "Parsing done"; return h; } } // namespace digikam diff --git a/core/libs/dplugins/webservices/wsitem.h b/core/libs/dplugins/webservices/wsitem.h index 0bbea3d776..745a4a95e5 100644 --- a/core/libs/dplugins/webservices/wsitem.h +++ b/core/libs/dplugins/webservices/wsitem.h @@ -1,116 +1,116 @@ /* ============================================================ - * + * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2018-07-26 * Description : common items needed for web services * * Copyright (C) 2018 by Thanh Trung Dinh * * 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. * * ============================================================ */ #ifndef DIGIKAM_WS_ITEM_H #define DIGIKAM_WS_ITEM_H // Qt includes #include #include // Local includes #include "digikam_export.h" namespace Digikam { class DIGIKAM_EXPORT WSAlbum { public: explicit WSAlbum() : parentID(QLatin1String("")), isRoot(true), description(QLatin1String("")), url(QLatin1String("")), uploadable(true) { } /** * This method is used by derived class of WSAblum, * to set the attributes inherited from WSAlbum, knowing * a WSAlbum. */ void setBaseAlbum(const WSAlbum& album) { id = album.id; parentID = album.parentID; isRoot = album.isRoot; title = album.title; description = album.description; location = album.location; url = album.url; uploadable = album.uploadable; } QString id; QString parentID; bool isRoot; QString title; QString description; QString location; QString url; bool uploadable; }; /** * This class is used when parsing response of listAlbums(). * It contains only the most important attributes of an album, * which is needed for further usage (e.g upload photos, create new album). */ class DIGIKAM_EXPORT AlbumSimplified { public: explicit AlbumSimplified() : uploadable(true) { } explicit AlbumSimplified(const QString& title) : title(title), uploadable(true) { } explicit AlbumSimplified(const QString& title, bool uploadable) : title(title), uploadable(uploadable) { } public: QString title; QStringList childrenIDs; bool uploadable; }; } // namespace Digikam #endif // DIGIKAM_WS_ITEM_H diff --git a/core/libs/facesengine/recognition/opencv-dnn/facerec_dnnborrowed.cpp b/core/libs/facesengine/recognition/opencv-dnn/facerec_dnnborrowed.cpp index 3846175800..ff81391837 100644 --- a/core/libs/facesengine/recognition/opencv-dnn/facerec_dnnborrowed.cpp +++ b/core/libs/facesengine/recognition/opencv-dnn/facerec_dnnborrowed.cpp @@ -1,245 +1,245 @@ /* ============================================================ * * This file is a part of digiKam * * Date : 2017-07-13 * Description : Face recognition using deep learning * * Copyright (C) 2017 by Yingjie Liu * Copyright (C) 2017-2020 by Gilles Caulier * Copyright (C) 2019 by Thanh Trung Dinh * * 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 "facerec_dnnborrowed.h" // C++ includes #include #include #include #include #include #include // Qt includes #include #include #include // Local includes #include "digikam_debug.h" using namespace cv; /** * This compute cosine distance between 2 vectors with formula: * cos(a) = (v1*v2) / (||v1||*||v2||) */ static double cosineDistance(std::vector v1, std::vector v2) { assert(v1.size() == v2.size()); double scalarProduct = std::inner_product(v1.begin(), v1.end(), v2.begin(), 0.0); double normV1 = sqrt(std::inner_product(v1.begin(), v1.end(), v1.begin(), 0.0)); double normV2 = sqrt(std::inner_product(v2.begin(), v2.end(), v2.begin(), 0.0)); return (scalarProduct / (normV1 * normV2)); } namespace Digikam { void DNNFaceRecognizer::train(std::vector > _in_src, InputArray _inm_labels) { this->train(_in_src, _inm_labels, false); } void DNNFaceRecognizer::update(std::vector > _in_src, InputArray _inm_labels) { // got no data, just return if (_in_src.size() == 0) { return; } this->train(_in_src, _inm_labels, true); } /** * This train function is used to store the face vectors, not training */ void DNNFaceRecognizer::train(std::vector > _in_src, InputArray _inm_labels, bool preserveData) { // get the vector of matrices std::vector > src = _in_src; // get the label matrix cv::Mat labels = _inm_labels.getMat(); // check if data is well- aligned if (labels.total() != src.size()) { qCCritical(DIGIKAM_FACESENGINE_LOG) << "The number of samples (src) must equal the number of labels (labels). " "Was len(samples)=" << src.size() << ", len(labels)=" << m_labels.total(); } // if this model should be trained without preserving old data, delete old model data if (!preserveData) { m_labels.release(); m_src.clear(); } // append labels to m_labels matrix for (size_t labelIdx = 0 ; labelIdx < labels.total() ; ++labelIdx) { m_labels.push_back(labels.at((int)labelIdx)); m_src.push_back(src[(int)labelIdx]); } } void DNNFaceRecognizer::predict(cv::InputArray _src, int& label, double& dist, DNNFaceExtractor* const extractor) const { qCWarning(DIGIKAM_FACESENGINE_LOG) << "Predicting face image"; cv::Mat src = _src.getMat(); // 254*254 std::vector vecdata; extractor->getFaceEmbedding(src, vecdata); qCWarning(DIGIKAM_FACESENGINE_LOG) << "m_threshold " << m_threshold; qCWarning(DIGIKAM_FACESENGINE_LOG) << "vecdata: " << vecdata[vecdata.size()-2] << " " << vecdata[vecdata.size()-1]; dist = -1; label = -1; // find nearest neighbor QMap distMap; QMap countDist; for (size_t sampleIdx = 0 ; sampleIdx < m_src.size() ; ++sampleIdx) { /* double dist = 0; for (size_t i = 0 ; i < m_src[sampleIdx].size() ; ++i) { dist += (vecdata[i]-m_src[sampleIdx][i])*(vecdata[i]-m_src[sampleIdx][i]); } dist = std::sqrt(dist); if ((dist < minDist) && (dist < m_threshold)) { minDist = dist; minClass = m_labels.at((int) sampleIdx); } */ /* // The codes below only compute maximum similarity between the new face with all the old faces. // Then the label is assigned according to the label of the face the most similar. double newDist = cosineDistance(vecdata, m_src[sampleIdx]); if (newDist > dist) { dist = newDist; label = m_labels.at((int) sampleIdx); } */ - // The codes below compute the average similarity between the new face and the old faces of + // The codes below compute the average similarity between the new face and the old faces of // each ID (label). Then, it finds out the group the most similar to that face. double newDist = cosineDistance(vecdata, m_src[sampleIdx]); int inspectedLabel = m_labels.at((int) sampleIdx); if (distMap.contains(inspectedLabel)) { distMap[inspectedLabel] += newDist; countDist[inspectedLabel]++; } else { distMap.insert(inspectedLabel, newDist); countDist.insert(inspectedLabel, 1); } } // The label is eventually assigned according to the label of that group. for (QMap::const_iterator it = distMap.constBegin() ; it != distMap.constEnd() ; ++it) { double newDist = it.value() / countDist[it.key()]; if (newDist > dist) { dist = newDist; label = it.key(); } } } int DNNFaceRecognizer::predict(InputArray /*_src*/) const { int label = 0; /* double dummy = 0.0; predict(_src, label, dummy); */ return label; } // Static method ---------------------------------------------------- Ptr DNNFaceRecognizer::create(double threshold) { Ptr ptr; DNNFaceRecognizer* const fr = new DNNFaceRecognizer(threshold); if (!fr) { qCWarning(DIGIKAM_FACESENGINE_LOG) << "Cannot create DNNFaceRecognizer instance"; return ptr; } ptr = Ptr(fr); if (ptr.empty()) { qCWarning(DIGIKAM_FACESENGINE_LOG) << "DNNFaceRecognizer instance is empty"; } return ptr; } } // namespace Digikam diff --git a/core/libs/facesengine/recognition/opencv-fisherfaces/facerec_fisherborrowed.cpp b/core/libs/facesengine/recognition/opencv-fisherfaces/facerec_fisherborrowed.cpp index a7bbd98151..e0aec67e1d 100644 --- a/core/libs/facesengine/recognition/opencv-fisherfaces/facerec_fisherborrowed.cpp +++ b/core/libs/facesengine/recognition/opencv-fisherfaces/facerec_fisherborrowed.cpp @@ -1,311 +1,311 @@ /* ============================================================ * * This file is a part of digiKam * * Date : 2017-06-10 * Description : Face Recognition based on Fisherfaces * https://docs.opencv.org/2.4/modules/contrib/doc/facerec/facerec_tutorial.html#Fisherfaces - * Turk, Matthew A and Pentland, Alex P. "Face recognition using Fisherfaces." + * Turk, Matthew A and Pentland, Alex P. "Face recognition using Fisherfaces." * Computer Vision and Pattern Recognition, 1991. Proceedings {CVPR'91.}, * {IEEE} Computer Society Conference on 1991. * * Copyright (C) 2017 by Yingjie Liu * Copyright (C) 2017-2020 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 "facerec_fisherborrowed.h" // C++ includes #include #include #include // Local includes #include "digikam_debug.h" using namespace cv; namespace Digikam { //------------------------------------------------------------------------------ // Fisherfaces //------------------------------------------------------------------------------ inline Mat asRowMatrix(std::vector src, int rtype, double alpha=1, double beta=0) { // number of samples size_t n = src.size(); // return empty matrix if no matrices given if (n == 0) { return Mat(); } // dimensionality of (reshaped) samples size_t d = src[0].total(); // create data matrix Mat data((int)n, (int)d, rtype); // now copy data for (unsigned int i = 0 ; i < n ; ++i) { Mat xi = data.row(i); // make reshape happy by cloning for non-continuous matrices if (src[i].isContinuous()) { src[i].reshape(1, 1).convertTo(xi, rtype, alpha, beta); } else { src[i].clone().reshape(1, 1).convertTo(xi, rtype, alpha, beta); } } return data; } // Removes duplicate elements in a given vector. template inline std::vector<_Tp> remove_dups(const std::vector<_Tp>& src) { typedef typename std::set<_Tp>::const_iterator constSetIterator; typedef typename std::vector<_Tp>::const_iterator constVecIterator; std::set<_Tp> set_elems; for (constVecIterator it = src.begin() ; it != src.end() ; ++it) { set_elems.insert(*it); } std::vector<_Tp> elems; for (constSetIterator it = set_elems.begin() ; it != set_elems.end() ; ++it) { elems.push_back(*it); } return elems; } void FisherFaceRecognizer::train(InputArrayOfArrays _in_src, InputArray _inm_labels) { this->train(_in_src, _inm_labels, false); } void FisherFaceRecognizer::update(InputArrayOfArrays _in_src, InputArray _inm_labels) { // got no data, just return if (_in_src.total() == 0) { return; } this->train(_in_src, _inm_labels, true); } void FisherFaceRecognizer::train(InputArrayOfArrays _in_src, InputArray _inm_labels, bool preserveData) { if (_in_src.kind() != _InputArray::STD_VECTOR_MAT && _in_src.kind() != _InputArray::STD_VECTOR_VECTOR) { qCCritical(DIGIKAM_FACESENGINE_LOG) << "The images are expected as InputArray::STD_VECTOR_MAT (a std::vector) " "or _InputArray::STD_VECTOR_VECTOR (a std::vector< std::vector<...> >)."; } if (_in_src.total() == 0) { qCCritical(DIGIKAM_FACESENGINE_LOG) << "Empty training data was given. You'll need more than one sample to learn a model."; } else if (_inm_labels.getMat().type() != CV_32SC1) { qCCritical(DIGIKAM_FACESENGINE_LOG) << "Labels must be given as integer (CV_32SC1). " " Expected" << CV_32SC1 << ", but was" << _inm_labels.type(); } // get the vector of matrices std::vector src; _in_src.getMatVector(src); // get the label matrix Mat labels = _inm_labels.getMat(); // check if data is well- aligned if (labels.total() != src.size()) { qCCritical(DIGIKAM_FACESENGINE_LOG) << "The number of samples (src) must equal the number of labels " "(labels). Was len(samples)=" << src.size() << ", len(labels)=" << m_labels.total(); } // if this model should be trained without preserving old data, delete old model data if (!preserveData) { m_labels.release(); m_src.clear(); } // append labels to m_labels matrix for (size_t labelIdx = 0 ; labelIdx < labels.total() ; ++labelIdx) { m_labels.push_back(labels.at((int)labelIdx)); m_src.push_back(src[(int)labelIdx]); } // observations in row Mat data = asRowMatrix(m_src, CV_64FC1); // number of samples int n = data.rows; /** * LDA needs more than one class * We have to check the labels first */ bool label_flag = false; for (int i = 1 ; i < m_labels.rows ; ++i) { if (m_labels.at(i, 0) != m_labels.at(i-1, 0)) { label_flag = true; break; } } if (!label_flag) { qCCritical(DIGIKAM_FACESENGINE_LOG) << "The labels should contain more than one types."; } // clear existing model data m_projections.clear(); std::vector ll; for (unsigned int i = 0 ; i < m_labels.total() ; ++i) { ll.push_back(m_labels.at(i)); } // get the number of unique classes int C = (int) remove_dups(ll).size(); // clip number of components to be valid m_num_components = (C-1); // perform the PCA PCA pca(data, Mat(), PCA::DATA_AS_ROW, (n-C)); LDA lda(pca.project(data),m_labels, m_num_components); // Now calculate the projection matrix as pca.eigenvectors * lda.eigenvectors. // Note: OpenCV stores the eigenvectors by row, so we need to transpose it! gemm(pca.eigenvectors, lda.eigenvectors(), 1.0, Mat(), 0.0, m_eigenvectors, GEMM_1_T); // store the projections of the original data for (int sampleIdx = 0 ; sampleIdx < data.rows ; ++sampleIdx) { Mat p = LDA::subspaceProject(m_eigenvectors, m_mean, data.row(sampleIdx)); m_projections.push_back(p); } } void FisherFaceRecognizer::predict(cv::InputArray _src, cv::Ptr collector) const { qCWarning(DIGIKAM_FACESENGINE_LOG) << "Predicting face image using fisherfaces"; if (m_projections.empty()) { // throw error if no data (or simply return -1?) qCCritical(DIGIKAM_FACESENGINE_LOG) << "This Fisherfaces model is not computed yet. Did you call the train method?"; } Mat src = _src.getMat();//254*254 // Make sure the size of input image is the same as training image if ((m_src.size() >= 1) && ((src.rows != m_src[0].rows) || (src.cols != m_src[0].cols))) { //resize(src, src, Size(m_src[0].rows, m_src[0].cols), (0, 0), (0, 0), INTER_LINEAR); resize(src, src, Size(m_src[0].rows, m_src[0].cols)); } collector->init(0); // here need to confirm Mat q = LDA::subspaceProject(m_eigenvectors, m_mean, src.reshape(1, 1)); // find nearest neighbor for (size_t sampleIdx = 0 ; sampleIdx < m_projections.size() ; ++sampleIdx) { double dist = norm(m_projections[sampleIdx], q, NORM_L2); int label = m_labels.at((int) sampleIdx); if (!collector->collect(label, dist)) { return; } } } // Static method ---------------------------------------------------- Ptr FisherFaceRecognizer::create(double threshold) { Ptr ptr; FisherFaceRecognizer* const fr = new FisherFaceRecognizer(threshold); if (!fr) { qCWarning(DIGIKAM_FACESENGINE_LOG) << "Cannot create FisherFaceRecognizer instance"; return ptr; } ptr = Ptr(fr); if (ptr.empty()) { qCWarning(DIGIKAM_FACESENGINE_LOG) << "FisherFaceRecognizer instance is empty"; } return ptr; } } // namespace Digikam diff --git a/core/libs/facesengine/recognition/opencv-fisherfaces/facerec_fisherborrowed.h b/core/libs/facesengine/recognition/opencv-fisherfaces/facerec_fisherborrowed.h index 4bc5095dde..2d94c983d5 100644 --- a/core/libs/facesengine/recognition/opencv-fisherfaces/facerec_fisherborrowed.h +++ b/core/libs/facesengine/recognition/opencv-fisherfaces/facerec_fisherborrowed.h @@ -1,143 +1,143 @@ /* ============================================================ * * This file is a part of digiKam * * Date : 2017-06-10 * Description : Face Recognition based on Fisherfaces * https://docs.opencv.org/2.4/modules/contrib/doc/facerec/facerec_tutorial.html#Fisherfaces - * Turk, Matthew A and Pentland, Alex P. "Face recognition using Fisherfaces." + * Turk, Matthew A and Pentland, Alex P. "Face recognition using Fisherfaces." * Computer Vision and Pattern Recognition, 1991. Proceedings {CVPR'91.}, * {IEEE} Computer Society Conference on 1991. * * Copyright (C) 2017 by Yingjie Liu * Copyright (C) 2017-2020 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. * * ============================================================ */ #ifndef DIGIKAM_FACE_REC_FISHER_BORROWED_H #define DIGIKAM_FACE_REC_FISHER_BORROWED_H // C++ includes #include // Local includes #include "digikam_opencv.h" #include "opencv_face.h" namespace Digikam { class FisherFaceRecognizer : public Face::FaceRecognizer { public: /** * Initializes this Fisherfaces Model. */ explicit FisherFaceRecognizer(double threshold = DBL_MAX) : m_threshold(threshold), m_num_components(0) { } /** * Initializes and computes this Fisherfaces Model. */ FisherFaceRecognizer(cv::InputArrayOfArrays src, cv::InputArray labels, double threshold = DBL_MAX) : m_threshold(threshold), m_num_components(0) { train(src, labels); } ~FisherFaceRecognizer() { } static cv::Ptr create(double threshold = DBL_MAX); /** * Computes a Fisherfaces model with images in src and * corresponding labels in labels. */ void train(cv::InputArrayOfArrays src, cv::InputArray labels) override; /** * Updates this Fisherfaces model with images in src and * corresponding labels in labels. */ void update(cv::InputArrayOfArrays src, cv::InputArray labels) override; /** * Predict */ using Face::FaceRecognizer::predict; void predict(cv::InputArray src, cv::Ptr collector) const override; /** * Getter functions. */ int getNumComponents() const { return m_num_components; } void setNumComponents(int _num_com_ponents) { m_num_components = _num_com_ponents; } double getThreshold() const override { return m_threshold; } void setThreshold(double _threshold) { m_threshold = _threshold; } std::vector getSrc() const { return m_src; } void setSrc(const std::vector& _src) { m_src = _src; } std::vector getProjections() const { return m_projections; } void setProjections(const std::vector& _projections) { m_projections = _projections; } cv::Mat getLabels() const { return m_labels; } void setLabels(cv::Mat _labels) { m_labels = _labels; } cv::Mat getEigenvectors() const { return m_eigenvectors; } void setEigenvectors(cv::Mat _eigenvectors) { m_eigenvectors = _eigenvectors; } cv::Mat getMean() const { return m_mean; } void setMean(cv::Mat _mean) { m_mean = _mean; } private: /** * Computes a Fisherfaces model with images in src and * corresponding labels in labels, possibly preserving * old training data. */ void train(cv::InputArrayOfArrays src, cv::InputArray labels, bool preserveData); private: // NOTE: Do not use a d private internal container, this will crash OpenCV in cv::Algorithm::set() double m_threshold; int m_num_components; std::vector m_src; std::vector m_projections; cv::Mat m_labels; cv::Mat m_eigenvectors; cv::Mat m_mean; }; } // namespace Digikam #endif // DIGIKAM_FACE_REC_FISHER_BORROWED_H diff --git a/core/libs/facesengine/recognition/opencv-fisherfaces/fisherfacemodel.cpp b/core/libs/facesengine/recognition/opencv-fisherfaces/fisherfacemodel.cpp index d5110236c7..a9c9cfb786 100644 --- a/core/libs/facesengine/recognition/opencv-fisherfaces/fisherfacemodel.cpp +++ b/core/libs/facesengine/recognition/opencv-fisherfaces/fisherfacemodel.cpp @@ -1,184 +1,184 @@ /* ============================================================ * * This file is a part of digiKam * * Date : 2017-06-10 * Description : Face Recognition based on Fisherfaces * https://docs.opencv.org/2.4/modules/contrib/doc/facerec/facerec_tutorial.html#Fisherfaces - * Turk, Matthew A and Pentland, Alex P. "Face recognition using Fisherfaces." + * Turk, Matthew A and Pentland, Alex P. "Face recognition using Fisherfaces." * Computer Vision and Pattern Recognition, 1991. Proceedings {CVPR'91.}, * {IEEE} Computer Society Conference on 1991. * * Copyright (C) 2017 by Yingjie Liu * Copyright (C) 2017-2020 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 "fisherfacemodel.h" // Qt includes #include // local includes #include "digikam_debug.h" namespace Digikam { FisherFaceMatMetadata::FisherFaceMatMetadata() : databaseId(-1), identity(0), storageStatus(Created) { } FisherFaceMatMetadata::~FisherFaceMatMetadata() { } // ------------------------------------------------------------------------------------ FisherFaceModel::FisherFaceModel() : cv::Ptr(FisherFaceRecognizer::create()) { ptr()->setThreshold(25000.0); } FisherFaceModel::~FisherFaceModel() { } FisherFaceRecognizer* FisherFaceModel::ptr() { FisherFaceRecognizer* const ptr = cv::Ptr::operator Digikam::FisherFaceRecognizer*(); if (!ptr) { qCWarning(DIGIKAM_FACESENGINE_LOG) << "FisherFaceRecognizer pointer is null"; } return ptr; } const FisherFaceRecognizer* FisherFaceModel::ptr() const { const FisherFaceRecognizer* const ptr = cv::Ptr::operator Digikam::FisherFaceRecognizer*(); if (!ptr) { qCWarning(DIGIKAM_FACESENGINE_LOG) << "FisherFaceRecognizer pointer is null"; } return ptr; } std::vector FisherFaceModel::getSrc() const { return ptr()->getSrc(); } void FisherFaceModel::setSrc(std::vector new_src) { ptr()->setSrc(new_src); } cv::Mat FisherFaceModel::getLabels() const { return ptr()->getLabels(); } void FisherFaceModel::setLabels(cv::Mat new_labels) { ptr()->setLabels(new_labels); } OpenCVMatData FisherFaceModel::matData(int index) const { return OpenCVMatData(ptr()->getSrc().at(index)); } QList FisherFaceModel::matMetadata() const { return m_matMetadata; } void FisherFaceModel::setWrittenToDatabase(int index, int id) { m_matMetadata[index].databaseId = id; m_matMetadata[index].storageStatus = FisherFaceMatMetadata::InDatabase; } void FisherFaceModel::setMats(const QList& mats, const QList& matMetadata) { /* * Does not work with standard OpenCV, as these two params are declared read-only in OpenCV. * One reason why we copied the code. */ std::vector newSrc; cv::Mat newLabels; newSrc.reserve(mats.size()); newLabels.reserve(matMetadata.size()); foreach (const OpenCVMatData& mat, mats) { newSrc.push_back(mat.toMat()); } m_matMetadata.clear(); foreach (const FisherFaceMatMetadata& metadata, matMetadata) { newLabels.push_back(metadata.identity); m_matMetadata << metadata; } std::vector currentSrcs = ptr()->getSrc(); cv::Mat currentLabels = ptr()->getLabels(); currentSrcs.insert(currentSrcs.end(), newSrc.begin(), newSrc.end()); currentLabels.push_back(newLabels); //ptr()->setSrc(currentSrcs); //ptr()->setLabels(currentLabels); // make sure that there exits training data if (currentSrcs.size() > 0) { ptr()->train(currentSrcs, currentLabels); } } void FisherFaceModel::update(const std::vector& images, const std::vector& labels, const QString& context) { ptr()->update(images, labels); // Update local information // We assume new labels are simply appended cv::Mat currentLabels = ptr()->getLabels(); for (int i = m_matMetadata.size() ; i < currentLabels.rows ; ++i) { FisherFaceMatMetadata metadata; metadata.storageStatus = FisherFaceMatMetadata::Created; metadata.identity = currentLabels.at(i); metadata.context = context; m_matMetadata << metadata; } } } // namespace Digikam diff --git a/core/libs/facesengine/recognition/opencv-fisherfaces/fisherfacemodel.h b/core/libs/facesengine/recognition/opencv-fisherfaces/fisherfacemodel.h index 503877da11..9de1749c3b 100644 --- a/core/libs/facesengine/recognition/opencv-fisherfaces/fisherfacemodel.h +++ b/core/libs/facesengine/recognition/opencv-fisherfaces/fisherfacemodel.h @@ -1,104 +1,104 @@ /* ============================================================ * * This file is a part of digiKam * * Date : 2017-06-10 * Description : Face Recognition based on Fisherfaces * https://docs.opencv.org/2.4/modules/contrib/doc/facerec/facerec_tutorial.html#Fisherfaces - * Turk, Matthew A and Pentland, Alex P. "Face recognition using Fisherfaces." + * Turk, Matthew A and Pentland, Alex P. "Face recognition using Fisherfaces." * Computer Vision and Pattern Recognition, 1991. Proceedings {CVPR'91.}, * {IEEE} Computer Society Conference on 1991. * * Copyright (C) 2017 by Yingjie Liu * Copyright (C) 2017-2020 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. * * ============================================================ */ #ifndef DIGIKAM_FACESENGINE_FISHER_FACE_MODEL_H #define DIGIKAM_FACESENGINE_FISHER_FACE_MODEL_H // Qt include #include // Local includes #include "digikam_opencv.h" #include "opencvmatdata.h" #include "facerec_fisherborrowed.h" namespace Digikam { class FisherFaceMatMetadata { public: enum StorageStatus { Created, InDatabase }; public: explicit FisherFaceMatMetadata(); ~FisherFaceMatMetadata(); public: int databaseId; int identity; QString context; StorageStatus storageStatus; }; // ------------------------------------------------------------------------------------------------------------------------------------- class FisherFaceModel : public cv::Ptr { public: explicit FisherFaceModel(); ~FisherFaceModel(); FisherFaceRecognizer* ptr(); const FisherFaceRecognizer* ptr() const; // Getter function std::vector getSrc() const; void setSrc(std::vector new_src); cv::Mat getLabels() const; void setLabels(cv::Mat new_labels); QList matMetadata() const; OpenCVMatData matData(int index) const; void setWrittenToDatabase(int index, int databaseId); void setMats(const QList& mats, const QList& matMetadata); /// Make sure to call this instead of FaceRecognizer::update directly! void update(const std::vector& images, const std::vector& labels, const QString& context); protected: QList m_matMetadata; }; } // namespace Digikam #endif // DIGIKAM_FACESENGINE_FISHER_FACE_MODEL_H diff --git a/core/libs/facesengine/recognition/opencv-fisherfaces/opencvfisherfacerecognizer.cpp b/core/libs/facesengine/recognition/opencv-fisherfaces/opencvfisherfacerecognizer.cpp index 68d81d722b..728f48aaf1 100644 --- a/core/libs/facesengine/recognition/opencv-fisherfaces/opencvfisherfacerecognizer.cpp +++ b/core/libs/facesengine/recognition/opencv-fisherfaces/opencvfisherfacerecognizer.cpp @@ -1,167 +1,167 @@ /* ============================================================ * * This file is a part of digiKam * * Date : 2017-06-10 * Description : Face Recognition based on Fisherfaces * https://docs.opencv.org/2.4/modules/contrib/doc/facerec/facerec_tutorial.html#Fisherfaces - * Turk, Matthew A and Pentland, Alex P. "Face recognition using Fisherfaces." + * Turk, Matthew A and Pentland, Alex P. "Face recognition using Fisherfaces." * Computer Vision and Pattern Recognition, 1991. Proceedings {CVPR'91.}, * {IEEE} Computer Society Conference on 1991. * * Copyright (C) 2017 by Yingjie Liu * Copyright (C) 2017-2020 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 "opencvfisherfacerecognizer.h" // local includes #include "digikam_opencv.h" #include "facedbaccess.h" #include "facedb.h" #include "fisherfacemodel.h" #include "digikam_debug.h" namespace Digikam { class Q_DECL_HIDDEN OpenCVFISHERFaceRecognizer::Private { public: explicit Private() : m_threshold(25000.0), m_loaded(false) { } public: FisherFaceModel& fisher() { if (!m_loaded) { m_fisher = FaceDbAccess().db()->fisherFaceModel(); m_loaded = true; } return m_fisher; } public: float m_threshold; private: FisherFaceModel m_fisher; bool m_loaded; }; OpenCVFISHERFaceRecognizer::OpenCVFISHERFaceRecognizer() : d(new Private) { setThreshold(25000.0); } OpenCVFISHERFaceRecognizer::~OpenCVFISHERFaceRecognizer() { delete d; } void OpenCVFISHERFaceRecognizer::setThreshold(float threshold) const { d->m_threshold = threshold; } namespace { enum { TargetInputSize = 256 }; } cv::Mat OpenCVFISHERFaceRecognizer::prepareForRecognition(const QImage& inputImage) { QImage image(inputImage); if ((inputImage.width() > TargetInputSize) || (inputImage.height() > TargetInputSize)) { image = inputImage.scaled(TargetInputSize, TargetInputSize, Qt::IgnoreAspectRatio); } cv::Mat cvImage = cv::Mat(image.height(), image.width(), CV_8UC1); cv::Mat cvImageWrapper; switch (image.format()) { case QImage::Format_RGB32: case QImage::Format_ARGB32: case QImage::Format_ARGB32_Premultiplied: // I think we can ignore premultiplication when converting to grayscale cvImageWrapper = cv::Mat(image.height(), image.width(), CV_8UC4, image.scanLine(0), image.bytesPerLine()); cvtColor(cvImageWrapper, cvImage, CV_RGBA2GRAY); break; default: image = image.convertToFormat(QImage::Format_RGB888); cvImageWrapper = cv::Mat(image.height(), image.width(), CV_8UC3, image.scanLine(0), image.bytesPerLine()); cvtColor(cvImageWrapper, cvImage, CV_RGB2GRAY); break; } //resize(cvImage, cvImage, Size(256, 256), (0, 0), (0, 0), INTER_LINEAR); equalizeHist(cvImage, cvImage); return cvImage; } int OpenCVFISHERFaceRecognizer::recognize(const cv::Mat& inputImage) { int predictedLabel = -1; double confidence = 0; d->fisher()->predict(inputImage, predictedLabel, confidence); qCDebug(DIGIKAM_FACESENGINE_LOG) << predictedLabel << confidence; if (confidence > d->m_threshold) { return -1; } return predictedLabel; } void OpenCVFISHERFaceRecognizer::train(const std::vector& images, const std::vector& labels, const QString& context) { if (images.empty() || (labels.size() != images.size())) { qCDebug(DIGIKAM_FACESENGINE_LOG) << "Fisherfaces Train: nothing to train..."; return; } d->fisher().update(images, labels, context); qCDebug(DIGIKAM_FACESENGINE_LOG) << "Fisherfaces Train: Adding model to Facedb"; /* * TODO: OpenCVFISHERFaceRecognizer do not register the model to database yet ! * This is why it cannot be used in production or for testing to compare with other algorithm. */ } } // namespace Digikam diff --git a/core/libs/facesengine/recognition/opencv-fisherfaces/opencvfisherfacerecognizer.h b/core/libs/facesengine/recognition/opencv-fisherfaces/opencvfisherfacerecognizer.h index 712b3f0edb..9c932d6ebe 100644 --- a/core/libs/facesengine/recognition/opencv-fisherfaces/opencvfisherfacerecognizer.h +++ b/core/libs/facesengine/recognition/opencv-fisherfaces/opencvfisherfacerecognizer.h @@ -1,86 +1,86 @@ /* ============================================================ * * This file is a part of digiKam * * Date : 2017-06-10 * Description : Face Recognition based on Fisherfaces * https://docs.opencv.org/2.4/modules/contrib/doc/facerec/facerec_tutorial.html#Fisherfaces - * Turk, Matthew A and Pentland, Alex P. "Face recognition using Fisherfaces." + * Turk, Matthew A and Pentland, Alex P. "Face recognition using Fisherfaces." * Computer Vision and Pattern Recognition, 1991. Proceedings {CVPR'91.}, * {IEEE} Computer Society Conference on 1991. * * Copyright (C) 2017 by Yingjie Liu * Copyright (C) 2017-2020 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. * * ============================================================ */ #ifndef DIGIKAM_OPENCV_FISHERFACE_RECOGNIZER_H #define DIGIKAM_OPENCV_FISHERFACE_RECOGNIZER_H // Qt include #include // Local includes #include "digikam_opencv.h" namespace Digikam { class OpenCVFISHERFaceRecognizer { public: /** * @brief FaceRecognizer:Master class to control entire recognition using Fisherfaces algorithm */ explicit OpenCVFISHERFaceRecognizer(); ~OpenCVFISHERFaceRecognizer(); void setThreshold(float threshold) const; /** * Returns a cvMat created from the inputImage, optimized for recognition */ cv::Mat prepareForRecognition(const QImage& inputImage); /** * Try to recognize the given image. * Returns the identity id. * If the identity cannot be recognized, returns -1. */ int recognize(const cv::Mat& inputImage); /** * Trains the given images, representing faces of the given matched identities. */ void train(const std::vector& images, const std::vector& labels, const QString& context); private: // Hidden copy constructor and assignment operator. OpenCVFISHERFaceRecognizer(const OpenCVFISHERFaceRecognizer&); OpenCVFISHERFaceRecognizer& operator=(const OpenCVFISHERFaceRecognizer&); class Private; Private* const d; }; } // namespace Digikam #endif // DIGIKAM_OPENCV_FISHERFACE_RECOGNIZER_H diff --git a/core/libs/jpegutils/jpegwin.cpp b/core/libs/jpegutils/jpegwin.cpp index e93e477481..b0ef6d5864 100644 --- a/core/libs/jpegutils/jpegwin.cpp +++ b/core/libs/jpegutils/jpegwin.cpp @@ -1,100 +1,100 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2008-11-22 * Description : some workaround functions to read jpeg * files without relying on libjpeg * * Copyright (C) 2008 by Patrick Spendrin * * 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 "jpegwin.h" // Qt includes #include #include namespace Digikam { namespace JPEGUtils { void init_source (j_decompress_ptr /*cinfo*/) { } boolean fill_input_buffer (j_decompress_ptr cinfo) { digikam_source_mgr* const src = (digikam_source_mgr*) cinfo->src; // Create a fake EOI marker src->eoi[0] = (JOCTET) 0xFF; src->eoi[1] = (JOCTET) JPEG_EOI; src->pub.next_input_byte = src->eoi; src->pub.bytes_in_buffer = 2; return true; } void skip_input_data (j_decompress_ptr cinfo, long nbytes) { digikam_source_mgr* const src = (digikam_source_mgr*) cinfo->src; if (nbytes > 0) { while (nbytes > (long) src->pub.bytes_in_buffer) { nbytes -= (long) src->pub.bytes_in_buffer; (void) fill_input_buffer(cinfo); } src->pub.next_input_byte += (size_t) nbytes; src->pub.bytes_in_buffer -= (size_t) nbytes; } } void term_source (j_decompress_ptr /*cinfo*/) { } void jpeg_memory_src (j_decompress_ptr cinfo, const JOCTET* buffer, size_t bufsize) { digikam_source_mgr* src = 0; if (cinfo->src == NULL) { - cinfo->src = (struct jpeg_source_mgr*) (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, + cinfo->src = (struct jpeg_source_mgr*) (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof(digikam_source_mgr)); } src = (digikam_source_mgr*) cinfo->src; src->pub.init_source = init_source; src->pub.fill_input_buffer = fill_input_buffer; src->pub.skip_input_data = skip_input_data; src->pub.resync_to_restart = jpeg_resync_to_restart; // default src->pub.term_source = term_source; src->pub.next_input_byte = buffer; src->pub.bytes_in_buffer = bufsize; } } // namespace JPEGUtils } // namespace Digikam diff --git a/core/libs/metadataengine/dmetadata/dmetadata_photo.cpp b/core/libs/metadataengine/dmetadata/dmetadata_photo.cpp index fbfcd03912..73034ad19f 100644 --- a/core/libs/metadataengine/dmetadata/dmetadata_photo.cpp +++ b/core/libs/metadataengine/dmetadata/dmetadata_photo.cpp @@ -1,442 +1,442 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-02-23 * Description : item metadata interface - photo info helpers. * * Copyright (C) 2006-2020 by Gilles Caulier * Copyright (C) 2006-2013 by Marcel Wiesweg * Copyright (C) 2011 by Leif Huhn * * 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 "dmetadata.h" // C++ includes #include // Qt includes #include // Local includes #include "metaenginesettings.h" #include "digikam_version.h" #include "digikam_globals.h" #include "digikam_debug.h" namespace Digikam { PhotoInfoContainer DMetadata::getPhotographInformation() const { PhotoInfoContainer photoInfo; if (hasExif() || hasXmp()) { photoInfo.dateTime = getItemDateTime(); // ----------------------------------------------------------------------------------- photoInfo.make = getExifTagString("Exif.Image.Make"); if (photoInfo.make.isEmpty()) { photoInfo.make = getXmpTagString("Xmp.tiff.Make"); } // ----------------------------------------------------------------------------------- photoInfo.model = getExifTagString("Exif.Image.Model"); if (photoInfo.model.isEmpty()) { photoInfo.model = getXmpTagString("Xmp.tiff.Model"); } // ----------------------------------------------------------------------------------- photoInfo.lens = getLensDescription(); // ----------------------------------------------------------------------------------- photoInfo.aperture = getExifTagString("Exif.Photo.FNumber"); if (photoInfo.aperture.isEmpty()) { photoInfo.aperture = getExifTagString("Exif.Photo.ApertureValue"); } if (photoInfo.aperture.isEmpty()) { photoInfo.aperture = getXmpTagString("Xmp.exif.FNumber"); } if (photoInfo.aperture.isEmpty()) { photoInfo.aperture = getXmpTagString("Xmp.exif.ApertureValue"); } // ----------------------------------------------------------------------------------- photoInfo.exposureTime = getExifTagString("Exif.Photo.ExposureTime"); if (photoInfo.exposureTime.isEmpty()) { photoInfo.exposureTime = getExifTagString("Exif.Photo.ShutterSpeedValue"); } if (photoInfo.exposureTime.isEmpty()) { photoInfo.exposureTime = getXmpTagString("Xmp.exif.ExposureTime"); } if (photoInfo.exposureTime.isEmpty()) { photoInfo.exposureTime = getXmpTagString("Xmp.exif.ShutterSpeedValue"); } // ----------------------------------------------------------------------------------- photoInfo.exposureMode = getExifTagString("Exif.Photo.ExposureMode"); if (photoInfo.exposureMode.isEmpty()) { photoInfo.exposureMode = getXmpTagString("Xmp.exif.ExposureMode"); } if (photoInfo.exposureMode.isEmpty()) { photoInfo.exposureMode = getExifTagString("Exif.CanonCs.MeteringMode"); } // ----------------------------------------------------------------------------------- photoInfo.exposureProgram = getExifTagString("Exif.Photo.ExposureProgram"); if (photoInfo.exposureProgram.isEmpty()) { photoInfo.exposureProgram = getXmpTagString("Xmp.exif.ExposureProgram"); } if (photoInfo.exposureProgram.isEmpty()) { photoInfo.exposureProgram = getExifTagString("Exif.CanonCs.ExposureProgram"); } // ----------------------------------------------------------------------------------- photoInfo.focalLength = getExifTagString("Exif.Photo.FocalLength"); if (photoInfo.focalLength.isEmpty()) { photoInfo.focalLength = getXmpTagString("Xmp.exif.FocalLength"); } if (photoInfo.focalLength.isEmpty()) { photoInfo.focalLength = getExifTagString("Exif.Canon.FocalLength"); } // ----------------------------------------------------------------------------------- photoInfo.focalLength35mm = getExifTagString("Exif.Photo.FocalLengthIn35mmFilm"); if (photoInfo.focalLength35mm.isEmpty()) { photoInfo.focalLength35mm = getXmpTagString("Xmp.exif.FocalLengthIn35mmFilm"); } // ----------------------------------------------------------------------------------- QStringList ISOSpeedTags; ISOSpeedTags << QLatin1String("Exif.Photo.ISOSpeedRatings"); ISOSpeedTags << QLatin1String("Exif.Photo.ExposureIndex"); ISOSpeedTags << QLatin1String("Exif.Image.ISOSpeedRatings"); ISOSpeedTags << QLatin1String("Xmp.exif.ISOSpeedRatings"); ISOSpeedTags << QLatin1String("Xmp.exif.ExposureIndex"); ISOSpeedTags << QLatin1String("Exif.CanonSi.ISOSpeed"); ISOSpeedTags << QLatin1String("Exif.CanonCs.ISOSpeed"); ISOSpeedTags << QLatin1String("Exif.Nikon1.ISOSpeed"); ISOSpeedTags << QLatin1String("Exif.Nikon2.ISOSpeed"); ISOSpeedTags << QLatin1String("Exif.Nikon3.ISOSpeed"); ISOSpeedTags << QLatin1String("Exif.NikonIi.ISO"); ISOSpeedTags << QLatin1String("Exif.NikonIi.ISO2"); ISOSpeedTags << QLatin1String("Exif.MinoltaCsNew.ISOSetting"); ISOSpeedTags << QLatin1String("Exif.MinoltaCsOld.ISOSetting"); ISOSpeedTags << QLatin1String("Exif.MinoltaCs5D.ISOSpeed"); ISOSpeedTags << QLatin1String("Exif.MinoltaCs7D.ISOSpeed"); ISOSpeedTags << QLatin1String("Exif.Sony1Cs.ISOSetting"); ISOSpeedTags << QLatin1String("Exif.Sony2Cs.ISOSetting"); ISOSpeedTags << QLatin1String("Exif.Sony1Cs2.ISOSetting"); ISOSpeedTags << QLatin1String("Exif.Sony2Cs2.ISOSetting"); ISOSpeedTags << QLatin1String("Exif.Sony1MltCsA100.ISOSetting"); ISOSpeedTags << QLatin1String("Exif.Pentax.ISO"); ISOSpeedTags << QLatin1String("Exif.Olympus.ISOSpeed"); ISOSpeedTags << QLatin1String("Exif.Samsung2.ISO"); photoInfo.sensitivity = getExifTagStringFromTagsList(ISOSpeedTags); // ----------------------------------------------------------------------------------- photoInfo.flash = getExifTagString("Exif.Photo.Flash"); if (photoInfo.flash.isEmpty()) { photoInfo.flash = getXmpTagString("Xmp.exif.Flash"); } if (photoInfo.flash.isEmpty()) { photoInfo.flash = getExifTagString("Exif.CanonCs.FlashActivity"); } // ----------------------------------------------------------------------------------- photoInfo.whiteBalance = getExifTagString("Exif.Photo.WhiteBalance"); if (photoInfo.whiteBalance.isEmpty()) { photoInfo.whiteBalance = getXmpTagString("Xmp.exif.WhiteBalance"); } // ----------------------------------------------------------------------------------- double l, L, a; photoInfo.hasCoordinates = getGPSInfo(a, l, L); } return photoInfo; } QString DMetadata::getLensDescription() const { QString lens; QStringList lensExifTags; // In first, try to get Lens information from makernotes. lensExifTags.append(QLatin1String("Exif.CanonCs.LensType")); // Canon Cameras Makernote. lensExifTags.append(QLatin1String("Exif.CanonCs.Lens")); // Canon Cameras Makernote. lensExifTags.append(QLatin1String("Exif.Canon.0x0095")); // Alternative Canon Cameras Makernote. lensExifTags.append(QLatin1String("Exif.NikonLd1.LensIDNumber")); // Nikon Cameras Makernote. lensExifTags.append(QLatin1String("Exif.NikonLd2.LensIDNumber")); // Nikon Cameras Makernote. lensExifTags.append(QLatin1String("Exif.NikonLd3.LensIDNumber")); // Nikon Cameras Makernote. lensExifTags.append(QLatin1String("Exif.Minolta.LensID")); // Minolta Cameras Makernote. lensExifTags.append(QLatin1String("Exif.Photo.LensModel")); // Sony Cameras Makernote (and others?). lensExifTags.append(QLatin1String("Exif.Sony1.LensID")); // Sony Cameras Makernote. lensExifTags.append(QLatin1String("Exif.Sony2.LensID")); // Sony Cameras Makernote. lensExifTags.append(QLatin1String("Exif.SonyMinolta.LensID")); // Sony Cameras Makernote. lensExifTags.append(QLatin1String("Exif.Pentax.LensType")); // Pentax Cameras Makernote. lensExifTags.append(QLatin1String("Exif.PentaxDng.LensType")); // Pentax Cameras Makernote. lensExifTags.append(QLatin1String("Exif.Panasonic.0x0051")); // Panasonic Cameras Makernote. lensExifTags.append(QLatin1String("Exif.Panasonic.0x0310")); // Panasonic Cameras Makernote. lensExifTags.append(QLatin1String("Exif.Sigma.LensRange")); // Sigma Cameras Makernote. lensExifTags.append(QLatin1String("Exif.Samsung2.LensType")); // Samsung Cameras Makernote. lensExifTags.append(QLatin1String("Exif.Photo.0xFDEA")); // Non-standard Exif tag set by Camera Raw. lensExifTags.append(QLatin1String("Exif.OlympusEq.LensModel")); // Olympus Cameras Makernote. // Olympus Cameras Makernote. FIXME is this necessary? exiv2 returns complete name, // which doesn't match with lensfun information, see bug #311295 //lensExifTags.append("Exif.OlympusEq.LensType"); // TODO : add Fuji camera Makernotes. // ------------------------------------------------------------------- // Try to get Lens Data information from Exif. for (QStringList::const_iterator it = lensExifTags.constBegin() ; it != lensExifTags.constEnd() ; ++it) { lens = getExifTagString((*it).toLatin1().constData()); // To prevent undecoded tag values from Exiv2 as "(65535)" // or the value "----" from Exif.Photo.LensModel if (!lens.isEmpty() && (lens != QLatin1String("----")) && !(lens.startsWith(QLatin1Char('(')) && lens.endsWith(QLatin1Char(')')) ) ) { return lens; } } // ------------------------------------------------------------------- // Try to get Lens Data information from XMP. // XMP aux tags. - + lens = getXmpTagString("Xmp.aux.Lens"); if (lens.isEmpty()) { // XMP M$ tags (Lens Maker + Lens Model). - + lens = getXmpTagString("Xmp.MicrosoftPhoto.LensManufacturer"); if (!lens.isEmpty()) { lens.append(QLatin1Char(' ')); } lens.append(getXmpTagString("Xmp.MicrosoftPhoto.LensModel")); } return lens; } double DMetadata::apexApertureToFNumber(double aperture) { // convert from APEX. See Exif spec, Annex C. - + if (aperture == 0.0) { return 1; } else if (aperture == 1.0) { return 1.4; } else if (aperture == 2.0) { return 2; } else if (aperture == 3.0) { return 2.8; } else if (aperture == 4.0) { return 4; } else if (aperture == 5.0) { return 5.6; } else if (aperture == 6.0) { return 8; } else if (aperture == 7.0) { return 11; } else if (aperture == 8.0) { return 16; } else if (aperture == 9.0) { return 22; } else if (aperture == 10.0) { return 32; } return exp(log(2) * aperture / 2.0); } double DMetadata::apexShutterSpeedToExposureTime(double shutterSpeed) { // convert from APEX. See Exif spec, Annex C. if (shutterSpeed == -5.0) { return 30; } else if (shutterSpeed == -4.0) { return 15; } else if (shutterSpeed == -3.0) { return 8; } else if (shutterSpeed == -2.0) { return 4; } else if (shutterSpeed == -1.0) { return 2; } else if (shutterSpeed == 0.0) { return 1; } else if (shutterSpeed == 1.0) { return 0.5; } else if (shutterSpeed == 2.0) { return 0.25; } else if (shutterSpeed == 3.0) { return 0.125; } else if (shutterSpeed == 4.0) { return 1.0 / 15.0; } else if (shutterSpeed == 5.0) { return 1.0 / 30.0; } else if (shutterSpeed == 6.0) { return 1.0 / 60.0; } else if (shutterSpeed == 7.0) { return 0.008; // 1/125 } else if (shutterSpeed == 8.0) { return 0.004; // 1/250 } else if (shutterSpeed == 9.0) { return 0.002; // 1/500 } else if (shutterSpeed == 10.0) { return 0.001; // 1/1000 } else if (shutterSpeed == 11.0) { return 0.0005; // 1/2000 } // additions by me else if (shutterSpeed == 12.0) { return 0.00025; // 1/4000 } else if (shutterSpeed == 13.0) { return 0.000125; // 1/8000 } return exp( - log(2) * shutterSpeed); } } // namespace Digikam diff --git a/core/libs/metadataengine/dmetadata/dmetadata_video.cpp b/core/libs/metadataengine/dmetadata/dmetadata_video.cpp index 1d466f33c1..3731371327 100644 --- a/core/libs/metadataengine/dmetadata/dmetadata_video.cpp +++ b/core/libs/metadataengine/dmetadata/dmetadata_video.cpp @@ -1,1764 +1,1764 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2018-02-26 * Description : item metadata interface - video helpers. * * References : * * FFMpeg metadata review: https://wiki.multimedia.cx/index.php/FFmpeg_Metadata * FFMpeg MP4 parser : https://github.com/FFmpeg/FFmpeg/blob/master/libavformat/mov.c#L298 * Exiv2 XMP video : https://github.com/Exiv2/exiv2/blob/master/src/properties.cpp#L1331 * Exiv2 RIFF tags : https://github.com/Exiv2/exiv2/blob/master/src/riffvideo.cpp#L83 * Apple metadata desc : https://developer.apple.com/library/content/documentation/QuickTime/QTFF/Metadata/Metadata.html * Matroska metadata desc: https://matroska.org/technical/specs/tagging/index.html * FFMpeg metadata writer: https://github.com/kritzikratzi/ofxAvCodec/blob/master/src/ofxAvUtils.cpp#L61 * * FFMpeg tags names origin: * * Generic : common tags generated by FFMpeg codecs. * RIFF files : Resource Interchange File Format tags (as AVI). * MKV files : Matroska container tags. * QT files : Quicktime container tags (Apple). * * Copyright (C) 2019-2020 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 "dmetadata.h" // C Ansi includes #include // Qt includes #include #include #include #include // KDE includes #include // Local includes #include "captionvalues.h" #include "digikam_debug.h" #include "digikam_config.h" #ifdef HAVE_MEDIAPLAYER // Libav includes extern "C" { #include #include #include #include } #endif namespace Digikam { /** * Search first occurrence of string in 'map' with keys given by 'lst'. * Return the string match. * If 'xmpTags' is not empty, register XMP tags value with string. */ QString s_setXmpTagStringFromEntry(DMetadata* const meta, const QStringList& lst, const DMetadata::MetaDataMap& map, const QStringList& xmpTags=QStringList()) { foreach (const QString& tag, lst) { DMetadata::MetaDataMap::const_iterator it = map.find(tag); if (it != map.end()) { if (meta && // Protection. !xmpTags.isEmpty()) // If xmpTags is empty, we only return the matching value from the map. { foreach (const QString& tag, xmpTags) { // Only register the tag value if it doesn't exists yet. - if (meta->getXmpTagString(tag.toLatin1().data()).isNull()) + if (meta->getXmpTagString(tag.toLatin1().data()).isNull()) { meta->setXmpTagString(tag.toLatin1().data(), it.value()); } } } return it.value(); } } return QString(); } QStringList s_keywordsSeparation(const QString& data) { QStringList keywords = data.split(QLatin1Char('/')); if (keywords.isEmpty()) { keywords = data.split(QLatin1Char(',')); if (keywords.isEmpty()) { keywords = data.split(QLatin1Char(' ')); } } return keywords; } qint64 s_secondsSinceJanuary1904(const QDateTime dt) { QDateTime dt1904(QDate(1904, 1, 1), QTime(0, 0, 0)); return dt1904.secsTo(dt); } #ifdef HAVE_MEDIAPLAYER QString s_convertFFMpegFormatToXMP(int format) { QString data; switch (format) { case AV_SAMPLE_FMT_U8: case AV_SAMPLE_FMT_U8P: data = QLatin1String("8Int"); break; case AV_SAMPLE_FMT_S16: case AV_SAMPLE_FMT_S16P: data = QLatin1String("16Int"); break; case AV_SAMPLE_FMT_S32: case AV_SAMPLE_FMT_S32P: data = QLatin1String("32Int"); break; case AV_SAMPLE_FMT_FLT: case AV_SAMPLE_FMT_FLTP: data = QLatin1String("32Float"); break; case AV_SAMPLE_FMT_DBL: // Not supported by XMP spec. case AV_SAMPLE_FMT_DBLP: // Not supported by XMP spec. case AV_SAMPLE_FMT_S64: // Not supported by XMP spec. case AV_SAMPLE_FMT_S64P: // Not supported by XMP spec. case AV_SAMPLE_FMT_NONE: case AV_SAMPLE_FMT_NB: default: data = QLatin1String("Other"); break; // NOTE: where are 'Compressed' and 'Packed' type from XMP spec into FFMPEG ? } return data; } DMetadata::MetaDataMap s_extractFFMpegMetadataEntriesFromDictionary(AVDictionary* const dict) { AVDictionaryEntry* entry = nullptr; DMetadata::MetaDataMap meta; do { entry = av_dict_get(dict, "", entry, AV_DICT_IGNORE_SUFFIX); if (entry) { QString entryValue = QString::fromUtf8(entry->value); if (QString::fromUtf8(entry->key) == QLatin1String("creation_time")) { if (QDateTime::fromString(entryValue, Qt::ISODate).toMSecsSinceEpoch() == 0) { continue; } } meta.insert(QString::fromUtf8(entry->key), entryValue); } } while (entry); return meta; } #endif bool DMetadata::loadUsingFFmpeg(const QString& filePath) { #ifdef HAVE_MEDIAPLAYER qCDebug(DIGIKAM_METAENGINE_LOG) << "Parse metadada with FFMpeg:" << filePath; #if LIBAVFORMAT_VERSION_MAJOR < 58 av_register_all(); #endif AVFormatContext* fmt_ctx = avformat_alloc_context(); int ret = avformat_open_input(&fmt_ctx, filePath.toUtf8().data(), nullptr, nullptr); if (ret < 0) { qCDebug(DIGIKAM_METAENGINE_LOG) << "avformat_open_input error: " << ret; return false; } ret = avformat_find_stream_info(fmt_ctx, nullptr); if (ret < 0) { qCDebug(DIGIKAM_METAENGINE_LOG) << "avform_find_stream_info error: " << ret; return false; } QString data; setXmpTagString("Xmp.video.duration", QString::number((int)(1000.0 * (double)fmt_ctx->duration / (double)AV_TIME_BASE))); setXmpTagString("Xmp.xmpDM.duration", QString::number((int)(1000.0 * (double)fmt_ctx->duration / (double)AV_TIME_BASE))); if (fmt_ctx->bit_rate > 0) { setXmpTagString("Xmp.video.MaxBitRate", QString::number(fmt_ctx->bit_rate)); } setXmpTagString("Xmp.video.StreamCount", QString::number(fmt_ctx->nb_streams)); // To only register one video, one audio stream, and one subtitle stream in XMP metadata. bool vstream = false; bool astream = false; bool sstream = false; for (uint i = 0 ; i < fmt_ctx->nb_streams ; ++i) { const AVStream* const stream = fmt_ctx->streams[i]; if (!stream) { continue; } AVCodecParameters* const codec = stream->codecpar; if (!codec) { continue; } const char* cname = avcodec_get_name(codec->codec_id); if (QLatin1String(cname) == QLatin1String("none")) { if (codec->codec_type == AVMEDIA_TYPE_AUDIO) { setXmpTagString("Xmp.audio.Codec", QString::fromUtf8(cname)); } else if (codec->codec_type == AVMEDIA_TYPE_VIDEO) { setXmpTagString("Xmp.video.Codec", QString::fromUtf8(cname)); } continue; } // ----------------------------------------- // Audio stream parsing // ----------------------------------------- if (!astream && codec->codec_type == AVMEDIA_TYPE_AUDIO) { astream = true; setXmpTagString("Xmp.audio.Codec", QString::fromUtf8(cname)); setXmpTagString("Xmp.audio.CodecDescription", QString::fromUtf8(avcodec_descriptor_get_by_name(cname)->long_name)); setXmpTagString("Xmp.audio.SampleRate", QString::number(codec->sample_rate)); setXmpTagString("Xmp.xmpDM.audioSampleRate", QString::number(codec->sample_rate)); // See XMP Dynamic Media properties from Adobe. - // Audio Channel type is a limited untranslated string choice depending of amount of audio channels + // Audio Channel type is a limited untranslated string choice depending of amount of audio channels data = QString(); switch (codec->channels) { case 0: break; case 1: data = QLatin1String("Mono"); break; case 2: data = QLatin1String("Stereo"); break; case 6: data = QLatin1String("5.1"); break; case 8: data = QLatin1String("7.1"); break; case 16: data = QLatin1String("16 Channel"); break; default: data = QLatin1String("Other"); break; } if (!data.isEmpty()) { setXmpTagString("Xmp.audio.ChannelType", data); setXmpTagString("Xmp.xmpDM.audioChannelType", data); } setXmpTagString("Xmp.audio.Format", QString::fromUtf8(av_get_sample_fmt_name((AVSampleFormat)codec->format))); // See XMP Dynamic Media properties from Adobe. - // Audio Sample type is a limited untranslated string choice depending of amount of audio samples + // Audio Sample type is a limited untranslated string choice depending of amount of audio samples data = s_convertFFMpegFormatToXMP(codec->format); if (!data.isEmpty()) { setXmpTagString("Xmp.audio.SampleType", data); setXmpTagString("Xmp.xmpDM.audioSampleType", data); } // -------------- MetaDataMap ameta = s_extractFFMpegMetadataEntriesFromDictionary(stream->metadata); qCDebug(DIGIKAM_METAENGINE_LOG) << "-- FFMpeg audio stream metadata entries :"; qCDebug(DIGIKAM_METAENGINE_LOG) << ameta; qCDebug(DIGIKAM_METAENGINE_LOG) << "-----------------------------------------"; // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("language"), // Generic. ameta, QStringList() << QLatin1String("Xmp.audio.TrackLang")); // -------------- data = s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("creation_time"), // Generic. ameta); if (!data.isEmpty()) { QDateTime dt = QDateTime::fromString(data, Qt::ISODate).toLocalTime(); setXmpTagString("Xmp.audio.TrackCreateDate", QString::number(s_secondsSinceJanuary1904(dt))); } // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("handler_name"), // Generic. ameta, QStringList() << QLatin1String("Xmp.audio.HandlerDescription")); } // ----------------------------------------- // Video stream parsing // ----------------------------------------- if (!vstream && codec->codec_type == AVMEDIA_TYPE_VIDEO) { vstream = true; setXmpTagString("Xmp.video.Codec", QString::fromUtf8(cname)); setXmpTagString("Xmp.video.CodecDescription", QString::fromUtf8(avcodec_descriptor_get_by_name(cname)->long_name)); setXmpTagString("Xmp.video.Format", QString::fromUtf8(av_get_pix_fmt_name((AVPixelFormat)codec->format))); // Store in this tag the full description off FFMPEG video color space. setXmpTagString("Xmp.video.ColorMode", QString::fromUtf8(av_color_space_name((AVColorSpace)codec->color_space))); VIDEOCOLORMODEL cm = VIDEOCOLORMODEL_OTHER; switch (codec->color_space) { case AVCOL_SPC_RGB: cm = VIDEOCOLORMODEL_SRGB; break; case AVCOL_SPC_BT470BG: case AVCOL_SPC_SMPTE170M: case AVCOL_SPC_SMPTE240M: cm = VIDEOCOLORMODEL_BT601; break; case AVCOL_SPC_BT709: cm = VIDEOCOLORMODEL_BT709; break; case AVCOL_SPC_UNSPECIFIED: case AVCOL_SPC_RESERVED: case AVCOL_SPC_NB: cm = VIDEOCOLORMODEL_UNKNOWN; break; default: break; } // See XMP Dynamic Media properties from Adobe. // Video Color Space is a limited untranslated string choice depending of video color space value. data = videoColorModelToString(cm); if (!data.isEmpty()) { setXmpTagString("Xmp.video.ColorSpace", data); setXmpTagString("Xmp.xmpDM.videoColorSpace", data); } // ---------- QString fo; switch (codec->field_order) { case AV_FIELD_PROGRESSIVE: fo = QLatin1String("Progressive"); break; case AV_FIELD_TT: // Top coded first, top displayed first case AV_FIELD_BT: // Bottom coded first, top displayed first fo = QLatin1String("Upper"); break; case AV_FIELD_BB: // Bottom coded first, bottom displayed first case AV_FIELD_TB: // Top coded first, bottom displayed first fo = QLatin1String("Lower"); break; default: break; } if (!fo.isEmpty()) { setXmpTagString("Xmp.xmpDM.FieldOrder", fo); } // ---------- QString aspectRatio; int frameRate = -1.0; if (codec->sample_aspect_ratio.num != 0) // Check if undefined by ffmpeg { AVRational displayAspectRatio; av_reduce(&displayAspectRatio.num, &displayAspectRatio.den, codec->width * (int64_t)codec->sample_aspect_ratio.num, codec->height * (int64_t)codec->sample_aspect_ratio.den, 1024 * 1024); aspectRatio = QString::fromLatin1("%1/%2").arg(displayAspectRatio.num) .arg(displayAspectRatio.den); } else if (codec->height) { aspectRatio = QString::fromLatin1("%1/%2").arg(codec->width) .arg(codec->height); } if (stream->avg_frame_rate.den) { frameRate = (double)stream->avg_frame_rate.num / (double)stream->avg_frame_rate.den; } setXmpTagString("Xmp.video.Width", QString::number(codec->width)); setXmpTagString("Xmp.video.FrameWidth", QString::number(codec->width)); setXmpTagString("Xmp.video.SourceImageWidth", QString::number(codec->width)); setXmpTagString("Xmp.video.Height", QString::number(codec->height)); setXmpTagString("Xmp.video.FrameHeight", QString::number(codec->height)); setXmpTagString("Xmp.video.SourceImageHeight", QString::number(codec->height)); setXmpTagString("Xmp.video.FrameSize", QString::fromLatin1("w:%1, h:%2, unit:pixels").arg(codec->width).arg(codec->height)); setXmpTagString("Xmp.xmpDM.videoFrameSize", QString::fromLatin1("w:%1, h:%2, unit:pixels").arg(codec->width).arg(codec->height)); // Backport size in Exif and Iptc setItemDimensions(QSize(codec->width, codec->height)); if (!aspectRatio.isEmpty()) { setXmpTagString("Xmp.video.AspectRatio", aspectRatio); setXmpTagString("Xmp.xmpDM.videoPixelAspectRatio", aspectRatio); } if (frameRate != -1.0) { setXmpTagString("Xmp.video.FrameRate", QString::number(frameRate)); // See XMP Dynamic Media properties from Adobe. // Video Color Space is a limited untranslated string choice depending of video frame rate. // https://documentation.apple.com/en/finalcutpro/usermanual/index.html#chapter=D%26section=4%26tasks=true data = QLatin1String("Other"); if (frameRate == 24.0) { data = QLatin1String("24"); } else if ((frameRate == 23.98) || (frameRate == 29.97) || (frameRate == 30.0) || (frameRate == 59.94)) { data = QLatin1String("NTSC"); } else if (frameRate == 25 || frameRate == 50) { data = QLatin1String("PAL"); } setXmpTagString("Xmp.xmpDM.videoFrameRate", data); } setXmpTagString("Xmp.video.BitDepth", QString::number(codec->bits_per_coded_sample)); // See XMP Dynamic Media properties from Adobe. // Video Pixel Depth is a limited untranslated string choice depending of amount of samples format. data = s_convertFFMpegFormatToXMP(codec->format); if (!data.isEmpty()) { setXmpTagString("Xmp.xmpDM.videoPixelDepth", data); } // ----------------------------------------- MetaDataMap vmeta = s_extractFFMpegMetadataEntriesFromDictionary(stream->metadata); qCDebug(DIGIKAM_METAENGINE_LOG) << "-- FFMpeg video stream metadata entries :"; qCDebug(DIGIKAM_METAENGINE_LOG) << vmeta; qCDebug(DIGIKAM_METAENGINE_LOG) << "-----------------------------------------"; // -------------- data = s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("rotate"), // Generic. vmeta); if (!data.isEmpty()) { bool b = false; int val = data.toInt(&b); ImageOrientation ori = ORIENTATION_UNSPECIFIED; if (b) { switch (val) { case 0: ori = ORIENTATION_NORMAL; break; case 90: ori = ORIENTATION_ROT_90; break; case 180: ori = ORIENTATION_ROT_180; break; case 270: ori = ORIENTATION_ROT_270; break; default: break; } setXmpTagString("Xmp.video.Orientation", QString::number(ori)); // Backport orientation in Exif setItemOrientation(ori); } } // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("language") // Generic. << QLatin1String("ILNG") // RIFF files. << QLatin1String("LANG"), // RIFF files. vmeta, QStringList() << QLatin1String("Xmp.video.Language")); // -------------- data = s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("creation_time") // Generic. << QLatin1String("_STATISTICS_WRITING_DATE_UTC"), // MKV files. vmeta); if (!data.isEmpty()) { QDateTime dt = QDateTime::fromString(data, Qt::ISODate).toLocalTime(); setXmpTagString("Xmp.video.TrackCreateDate", QString::number(s_secondsSinceJanuary1904(dt))); setXmpTagString("Xmp.xmpDM.shotDate", dt.toString()); } // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("handler_name"), // Generic. vmeta, QStringList() << QLatin1String("Xmp.video.HandlerDescription")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("TVER") // RIFF files. << QLatin1String("_STATISTICS_WRITING_APP"), // MKV files. vmeta, QStringList() << QLatin1String("Xmp.video.SoftwareVersion")); } // ----------------------------------------- // Subtitle stream parsing // ----------------------------------------- if (!sstream && codec->codec_type == AVMEDIA_TYPE_SUBTITLE) { sstream = true; setXmpTagString("Xmp.video.SubTCodec", QString::fromUtf8(cname)); setXmpTagString("Xmp.video.SubTCodecInfo", QString::fromUtf8(avcodec_descriptor_get_by_name(cname)->long_name)); // ----------------------------------------- MetaDataMap smeta = s_extractFFMpegMetadataEntriesFromDictionary(stream->metadata); qCDebug(DIGIKAM_METAENGINE_LOG) << "-- FFMpeg subtitle stream metadata entries :"; qCDebug(DIGIKAM_METAENGINE_LOG) << smeta; qCDebug(DIGIKAM_METAENGINE_LOG) << "--------------------------------------------"; // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("subtitle") // Generic. << QLatin1String("title"), // Generic. smeta, QStringList() << QLatin1String("Xmp.video.Subtitle")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("language"), // Generic. smeta, QStringList() << QLatin1String("Xmp.video.SubTLang")); } } // ----------------------------------------- // Root container parsing // ----------------------------------------- MetaDataMap rmeta = s_extractFFMpegMetadataEntriesFromDictionary(fmt_ctx->metadata); qCDebug(DIGIKAM_METAENGINE_LOG) << "-- FFMpeg root container metadata entries :"; qCDebug(DIGIKAM_METAENGINE_LOG) << rmeta; qCDebug(DIGIKAM_METAENGINE_LOG) << "------------------------------------------"; // ---------------------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("major_brand"), // Generic. rmeta, QStringList() << QLatin1String("Xmp.video.MajorBrand")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("compatible_brands"), // Generic. rmeta, QStringList() << QLatin1String("Xmp.video.CompatibleBrands")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("minor_version"), // Generic. rmeta, QStringList() << QLatin1String("Xmp.video.MinorVersion")); // -------------- data = s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("keywords") // Generic. << QLatin1String("IMIT") // RIFF files. << QLatin1String("KEYWORDS") // MKV files. << QLatin1String("com.apple.quicktime.keywords"), // QT files. rmeta, QStringList() << QLatin1String("Xmp.video.InfoText")); if (!data.isEmpty()) { QStringList keywords = s_keywordsSeparation(data); if (!keywords.isEmpty()) { setXmpKeywords(keywords); setIptcKeywords(QStringList(), keywords); } } // -------------- data = s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("category") // Generic. << QLatin1String("ISBJ") // RIFF files. << QLatin1String("SUBJECT"), // MKV files. rmeta, QStringList() << QLatin1String("Xmp.video.Subject")); if (!data.isEmpty()) { QStringList categories = s_keywordsSeparation(data); if (!categories.isEmpty()) { setXmpSubCategories(categories); setIptcSubCategories(QStringList(), categories); } } // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("premiere_version") // Generic. << QLatin1String("quicktime_version") // Generic. << QLatin1String("ISFT") // Riff files << QLatin1String("com.apple.quicktime.software"), // QT files. rmeta, QStringList() << QLatin1String("Xmp.video.SoftwareVersion")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("firmware") // Generic. << QLatin1String("com.apple.proapps.serialno"), // QT files. rmeta, QStringList() << QLatin1String("Xmp.video.FirmwareVersion")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("composer") // Generic. << QLatin1String("COMPOSER"), // MKV files rmeta, QStringList() << QLatin1String("Xmp.video.Composer") << QLatin1String("Xmp.xmpDM.composer")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("com.apple.quicktime.displayname"), // QT files. rmeta, QStringList() << QLatin1String("Xmp.video.Name")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("playback_requirements"), // Generic. rmeta, QStringList() << QLatin1String("Xmp.video.Requirements")); // -------------- - s_setXmpTagStringFromEntry(this, + s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("lyrics"), // Generic. rmeta, QStringList() << QLatin1String("Xmp.video.Lyrics")); // -------------- - s_setXmpTagStringFromEntry(this, + s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("filename"), // Generic. rmeta, QStringList() << QLatin1String("Xmp.video.FileName")); // -------------- - s_setXmpTagStringFromEntry(this, + s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("disk"), // Generic. rmeta, QStringList() << QLatin1String("Xmp.xmpDM.discNumber")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("performers"), // Generic. rmeta, QStringList() << QLatin1String("Xmp.video.Performers")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("producer") // Generic. << QLatin1String("PRODUCER") // MKV files. << QLatin1String("com.apple.quicktime.producer"), // QT files. rmeta, QStringList() << QLatin1String("Xmp.video.Producer")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("artist") // Generic. << QLatin1String("album_artist") // Generic. << QLatin1String("original_artist") // Generic. << QLatin1String("com.apple.quicktime.artist") // QT files. << QLatin1String("IART") // RIFF files. << QLatin1String("ARTIST") // MKV files. << QLatin1String("author") // Generic. << QLatin1String("com.apple.quicktime.author"), // QT files. rmeta, QStringList() << QLatin1String("Xmp.video.Artist") << QLatin1String("Xmp.xmpDM.artist")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("director") // Generic. << QLatin1String("DIRC") // RIFF files. << QLatin1String("DIRECTOR") // MKV files. << QLatin1String("com.apple.quicktime.director"), // QT files. rmeta, QStringList() << QLatin1String("Xmp.video.Director") << QLatin1String("Xmp.xmpDM.director")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("media_type") // Generic. << QLatin1String("IMED") // RIFF files. << QLatin1String("ORIGINAL_MEDIA_TYPE"), // MKV files. rmeta, QStringList() << QLatin1String("Xmp.video.Medium")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("grouping"), // Generic. rmeta, QStringList() << QLatin1String("Xmp.video.Grouping")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("BPS"), // MKV files rmeta, QStringList() << QLatin1String("Xmp.video.MaxBitRate")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("ISRC"), // MKV files rmeta, QStringList() << QLatin1String("Xmp.video.ISRCCode")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("CONTENT_TYPE"), // MKV files rmeta, QStringList() << QLatin1String("Xmp.video.ExtendedContentDescription")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("FPS"), // MKV files. rmeta, QStringList() << QLatin1String("Xmp.video.videoFrameRate") << QLatin1String("Xmp.xmpDM.FrameRate")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("encoder") // Generic. << QLatin1String("ENCODER"), // MKV files. rmeta, QStringList() << QLatin1String("Xmp.video.Encoder")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("com.apple.proapps.clipID"), // QT files. rmeta, QStringList() << QLatin1String("Xmp.video.FileID")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("original_source") // Generic. << QLatin1String("ISRC") // Riff files << QLatin1String("com.apple.proapps.cameraName"), // QT files. rmeta, QStringList() << QLatin1String("Xmp.video.Source")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("original_format") // Generic. << QLatin1String("com.apple.proapps.originalFormat"), // QT files. rmeta, QStringList() << QLatin1String("Xmp.video.Format")); // -------------- data = s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("rating") // Generic. << QLatin1String("IRTD") // RIFF files. << QLatin1String("RATE") // RIFF files. << QLatin1String("RATING") // MKV files. << QLatin1String("com.apple.quicktime.rating.user"), // QT files. rmeta, QStringList() << QLatin1String("Xmp.video.Rating") << QLatin1String("Xmp.video.Rate")); if (!data.isEmpty()) { // Backport rating in Exif and Iptc bool b = false; int rating = data.toInt(&b); if (b) { setItemRating(rating); } } // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("make") // Generic. << QLatin1String("com.apple.quicktime.make"), // QT files. rmeta, QStringList() << QLatin1String("Xmp.video.Make")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("model") // Generic. << QLatin1String("com.apple.quicktime.model"), // QT files. rmeta, QStringList() << QLatin1String("Xmp.video.Model") << QLatin1String("Xmp.xmpDM.cameraModel")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("URL") // Generic. << QLatin1String("TURL"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.URL")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("title") // Generic. << QLatin1String("INAM") // RIFF files. << QLatin1String("TITL") // RIFF files. << QLatin1String("TITLE") // MKV files. << QLatin1String("com.apple.quicktime.title"), // QT files. rmeta, QStringList() << QLatin1String("Xmp.video.Title") << QLatin1String("Xmp.xmpDM.shotName")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("copyright") // Generic. << QLatin1String("ICOP") // RIFF files. << QLatin1String("COPYRIGHT") // MKV files. << QLatin1String("com.apple.quicktime.copyright"), // QT files. rmeta, QStringList() << QLatin1String("Xmp.video.Copyright") << QLatin1String("Xmp.xmpDM.copyright")); // -------------- data = s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("comment") // Generic. << QLatin1String("description") // Generic. << QLatin1String("CMNT") // Riff Files. << QLatin1String("COMN") // Riff Files. << QLatin1String("ICMT") // Riff Files. << QLatin1String("COMMENT") // MKV Files. << QLatin1String("DESCRIPTION") // MKV Files. << QLatin1String("com.apple.quicktime.description"), // QT files. rmeta, QStringList() << QLatin1String("Xmp.video.Comment") << QLatin1String("Xmp.xmpDM.logComment")); if (!data.isEmpty()) { // Backport comment in Exif and Iptc CaptionsMap capMap; MetaEngine::AltLangMap comMap; comMap.insert(QLatin1String("x-default"), data); capMap.setData(comMap, MetaEngine::AltLangMap(), QString(), MetaEngine::AltLangMap()); setItemComments(capMap); } // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("synopsis") // Generic. << QLatin1String("SUMMARY") // MKV files. << QLatin1String("SYNOPSIS"), // MKV files. rmeta, QStringList() << QLatin1String("Xmp.video.Information")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("lyrics") // Generic. << QLatin1String("LYRICS"), // MKV files. rmeta, QStringList() << QLatin1String("Xmp.xmpDM.lyrics")); // -------------- for (int i = 1 ; i <= 9 ; ++i) { s_setXmpTagStringFromEntry(this, QStringList() << QString::fromLatin1("IAS%1").arg(i), // RIFF files. rmeta, QStringList() << QString::fromLatin1("Xmp.video.Edit%1").arg(i)); } // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("encoded_by") // Generic. << QLatin1String("CODE") // RIFF files. << QLatin1String("IECN") // RIFF files. << QLatin1String("ENCODED_BY"), // MKV files. rmeta, QStringList() << QLatin1String("Xmp.video.EncodedBy")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("DISP"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.SchemeTitle")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("AGES") // RIFF files. << QLatin1String("ICRA") // MKV files. << QLatin1String("LAW_RATING"), // MKV files. rmeta, QStringList() << QLatin1String("Xmp.video.Rated")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("IBSU"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.BaseURL")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("ICAS"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.DefaultStream")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("ICDS"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.CostumeDesigner")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("ICMS"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.Commissioned")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("ICNM"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.Cinematographer")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("ICNT"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.Country")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("IARL"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.ArchivalLocation")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("ICRP"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.Cropped")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("IDIM"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.Dimensions")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("IDPI"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.DotsPerInch")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("IDST") // RIFF files. << QLatin1String("DISTRIBUTED_BY"), // MKV files. rmeta, QStringList() << QLatin1String("Xmp.video.DistributedBy")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("IEDT"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.EditedBy")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("IENG"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.Engineer") << QLatin1String("Xmp.xmpDM.engineer")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("IKEY"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.PerformerKeywords")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("ILGT"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.Lightness")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("ILGU"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.LogoURL")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("ILIU"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.LogoIconURL")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("IMBI"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.InfoBannerImage")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("IMBU"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.InfoBannerURL")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("IMIU"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.InfoURL")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("IMUS"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.MusicBy")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("IPDS"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.ProductionDesigner")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("IPLT"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.NumOfColors")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("IPRD"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.Product")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("IPRO"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.ProducedBy")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("IRIP"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.RippedBy")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("ISGN"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.SecondaryGenre")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("ISHP"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.Sharpness")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("ISRF"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.SourceForm")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("ISTD"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.ProductionStudio")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("ISTR") // RIFF files. << QLatin1String("STAR"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.Starring")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("ITCH"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.Technician")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("IWMU"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.WatermarkURL")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("IWRI"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.WrittenBy")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("PRT1"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.Part")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("PRT2"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.NumOfParts")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("STAT"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.Statistics")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("TAPE"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.TapeName")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("TCDO"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.EndTimecode")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("TCOD"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.StartTimecode")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("TLEN"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.Length")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("TORG"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.Organization")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("VMAJ"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.VegasVersionMajor")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("VMIN"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.VegasVersionMinor")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("LOCA"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.LocationInfo") << QLatin1String("Xmp.xmpDM.shotLocation")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("album") // Generic. << QLatin1String("com.apple.quicktime.album"), // QT files. rmeta, QStringList() << QLatin1String("Xmp.video.Album") << QLatin1String("Xmp.xmpDM.album")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("genre") // Generic. << QLatin1String("GENR") // RIFF files. << QLatin1String("IGNR") // RIFF files. << QLatin1String("GENRE") // MKV files. << QLatin1String("com.apple.quicktime.genre"), // QT files. rmeta, QStringList() << QLatin1String("Xmp.video.Genre") << QLatin1String("Xmp.xmpDM.genre")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("track") // Generic. << QLatin1String("TRCK"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.TrackNumber") << QLatin1String("Xmp.xmpDM.trackNumber")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("year") // Generic. << QLatin1String("YEAR") // RIFF files. << QLatin1String("com.apple.quicktime.year"), rmeta, QStringList() << QLatin1String("Xmp.video.Year")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("ICRD") // Riff files << QLatin1String("DATE_DIGITIZED"), // MKV files rmeta, QStringList() << QLatin1String("Xmp.video.DateTimeDigitized")); // -------------- QStringList videoDateTimeOriginal; videoDateTimeOriginal << QLatin1String("creation_time") // Generic. << QLatin1String("DTIM") // RIFF files. << QLatin1String("DATE_RECORDED") // MKV files. << QLatin1String("com.apple.quicktime.creationdate"); // QT files. if (rmeta.contains(QLatin1String("creation_time"))) { if (rmeta.contains(QLatin1String("com.apple.quicktime.creationdate"))) { videoDateTimeOriginal.prepend(videoDateTimeOriginal.takeLast()); } else if (!rmeta.contains(QLatin1String("com.android.version"))) { if (rmeta[QLatin1String("creation_time")].endsWith(QLatin1Char('Z'))) { rmeta[QLatin1String("creation_time")].chop(1); } } } data = s_setXmpTagStringFromEntry(this, videoDateTimeOriginal, rmeta, QStringList() << QLatin1String("Xmp.video.DateTimeOriginal")); if (!data.isEmpty()) { // Backport date in Exif and Iptc. QDateTime dt = QDateTime::fromString(data, Qt::ISODate).toLocalTime(); setImageDateTime(dt, true); } // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("edit_date"), // Generic. rmeta, QStringList() << QLatin1String("Xmp.video.ModificationDate") << QLatin1String("Xmp.xmpDM.videoModDate")); // -------------- data = s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("date") // Generic. << QLatin1String("DATE_RELEASED"), // MKV files. rmeta); if (!data.isEmpty()) { QDateTime dt = QDateTime::fromString(data, Qt::ISODate).toLocalTime(); setXmpTagString("Xmp.video.MediaCreateDate", QString::number(s_secondsSinceJanuary1904(dt))); } // -------------- // GPS info as string. ex: "+44.8511-000.6229/" // Defined in ISO 6709:2008. // Notes: altitude can be passed as 3rd values. // each value is separated from others by '-' or '+'. // '/' is always the terminaison character. data = s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("location") // Generic. << QLatin1String("RECORDING_LOCATION") // MKV files. << QLatin1String("com.apple.quicktime.location.ISO6709"), // QT files. rmeta, QStringList() << QLatin1String("Xmp.video.GPSCoordinates") << QLatin1String("Xmp.xmpDM.shotLocation")); if (!data.isEmpty()) { // Backport location to Exif. QList digits; for (int i = 0 ; i < data.length() ; ++i) { QChar c = data[i]; if ((c == QLatin1Char('+')) || (c == QLatin1Char('-')) || (c == QLatin1Char('/'))) { digits << i; } } QString coord; double lattitude = 0.0; double longitude = 0.0; double altitude = 0.0; bool b1 = false; bool b2 = false; bool b3 = false; if (digits.size() > 1) { coord = data.mid(digits[0], digits[1] - digits[0]); lattitude = coord.toDouble(&b1); } if (digits.size() > 2) { coord = data.mid(digits[1], digits[2] - digits[1]); longitude = coord.toDouble(&b2); } if (digits.size() > 3) { coord = data.mid(digits[2], digits[3] - digits[2]); altitude = coord.toDouble(&b3); } if (b1 && b2) { if (b3) { // All GPS values are available. setGPSInfo(altitude, lattitude, longitude); setXmpTagString("Xmp.video.GPSAltitude", getXmpTagString("Xmp.exif.GPSAltitude")); setXmpTagString("Xmp.exif.GPSAltitude", getXmpTagString("Xmp.exif.GPSAltitude")); } else { // No altitude available. double* alt = nullptr; setGPSInfo(alt, lattitude, longitude); } setXmpTagString("Xmp.video.GPSLatitude", getXmpTagString("Xmp.exif.GPSLatitude")); setXmpTagString("Xmp.video.GPSLongitude", getXmpTagString("Xmp.exif.GPSLongitude")); setXmpTagString("Xmp.video.GPSMapDatum", getXmpTagString("Xmp.exif.GPSMapDatum")); setXmpTagString("Xmp.video.GPSVersionID", getXmpTagString("Xmp.exif.GPSVersionID")); setXmpTagString("Xmp.exif.GPSLatitude", getXmpTagString("Xmp.exif.GPSLatitude")); setXmpTagString("Xmp.exif.GPSLongitude", getXmpTagString("Xmp.exif.GPSLongitude")); setXmpTagString("Xmp.exif.GPSMapDatum", getXmpTagString("Xmp.exif.GPSMapDatum")); setXmpTagString("Xmp.exif.GPSVersionID", getXmpTagString("Xmp.exif.GPSVersionID")); } } avformat_close_input(&fmt_ctx); QFileInfo fi(filePath); if (getXmpTagString("Xmp.video.FileName").isNull()) { setXmpTagString("Xmp.video.FileName", fi.fileName()); } if (getXmpTagString("Xmp.video.FileSize").isNull()) { setXmpTagString("Xmp.video.FileSize", QString::number(fi.size() / (1024*1024))); } if (getXmpTagString("Xmp.video.FileType").isNull()) { setXmpTagString("Xmp.video.FileType", fi.suffix()); } if (getXmpTagString("Xmp.video.MimeType").isNull()) { setXmpTagString("Xmp.video.MimeType", QMimeDatabase().mimeTypeForFile(filePath).name()); } return true; #else Q_UNUSED(filePath); return false; #endif } QString DMetadata::videoColorModelToString(VIDEOCOLORMODEL videoColorModel) { QString cs; switch (videoColorModel) { case VIDEOCOLORMODEL_SRGB: cs = QLatin1String("sRGB"); break; case VIDEOCOLORMODEL_BT601: cs = QLatin1String("CCIR-601"); break; case VIDEOCOLORMODEL_BT709: cs = QLatin1String("CCIR-709"); break; case VIDEOCOLORMODEL_OTHER: cs = QLatin1String("Other"); break; default: // VIDEOCOLORMODEL_UNKNOWN break; } return cs; } VideoInfoContainer DMetadata::getVideoInformation() const { VideoInfoContainer videoInfo; if (hasXmp()) { if (videoInfo.aspectRatio.isEmpty()) { videoInfo.aspectRatio = getMetadataField(MetadataInfo::AspectRatio).toString(); } if (videoInfo.audioBitRate.isEmpty()) { videoInfo.audioBitRate = getXmpTagString("Xmp.audio.SampleRate"); } if (videoInfo.audioChannelType.isEmpty()) { videoInfo.audioChannelType = getXmpTagString("Xmp.audio.ChannelType"); } if (videoInfo.audioCodec.isEmpty()) { videoInfo.audioCodec = getXmpTagString("Xmp.audio.Codec"); } if (videoInfo.duration.isEmpty()) { videoInfo.duration = getXmpTagString("Xmp.video.duration"); } if (videoInfo.frameRate.isEmpty()) { videoInfo.frameRate = getXmpTagString("Xmp.video.FrameRate"); } if (videoInfo.videoCodec.isEmpty()) { videoInfo.videoCodec = getXmpTagString("Xmp.video.Codec"); } } return videoInfo; } } // namespace Digikam diff --git a/core/libs/models/abstractalbummodel.cpp b/core/libs/models/abstractalbummodel.cpp index ead40c1671..72440ec5f2 100644 --- a/core/libs/models/abstractalbummodel.cpp +++ b/core/libs/models/abstractalbummodel.cpp @@ -1,1249 +1,1249 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2009-03-23 * Description : Qt Model for Albums * * Copyright (C) 2008-2011 by Marcel Wiesweg * Copyright (C) 2010 by Andi Clemens * Copyright (C) 2012-2020 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 "abstractalbummodel.h" // Qt includes #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "albummanager.h" #include "albummodeldragdrophandler.h" #include "albumthumbnailloader.h" namespace Digikam { class Q_DECL_HIDDEN AbstractAlbumModel::Private { public: explicit Private() : rootAlbum(nullptr), addingAlbum(nullptr), type(Album::PHYSICAL), dragDropHandler(nullptr), rootBehavior(AbstractAlbumModel::IncludeRootAlbum), removingAlbum(0), itemDrag(true), itemDrop(true) { } Album* rootAlbum; Album* addingAlbum; Album::Type type; AlbumModelDragDropHandler* dragDropHandler; AbstractAlbumModel::RootAlbumBehavior rootBehavior; quintptr removingAlbum; bool itemDrag; bool itemDrop; }; AbstractAlbumModel::AbstractAlbumModel(Album::Type albumType, Album* const rootAlbum, RootAlbumBehavior rootBehavior, QObject* const parent) : QAbstractItemModel(parent), d(new Private) { d->type = albumType; d->rootAlbum = rootAlbum; d->rootBehavior = rootBehavior; // --- NOTE: use dynamic binding as all slots above are virtual methods which can be re-implemented in derived classes. connect(AlbumManager::instance(), &AlbumManager::signalAlbumAboutToBeAdded, this, &AbstractAlbumModel::slotAlbumAboutToBeAdded); connect(AlbumManager::instance(), &AlbumManager::signalAlbumAdded, this, &AbstractAlbumModel::slotAlbumAdded); connect(AlbumManager::instance(), &AlbumManager::signalAlbumAboutToBeDeleted, this, &AbstractAlbumModel::slotAlbumAboutToBeDeleted); connect(AlbumManager::instance(), &AlbumManager::signalAlbumHasBeenDeleted, this, &AbstractAlbumModel::slotAlbumHasBeenDeleted); connect(AlbumManager::instance(), &AlbumManager::signalAlbumsCleared, this, &AbstractAlbumModel::slotAlbumsCleared); connect(AlbumManager::instance(), &AlbumManager::signalAlbumIconChanged, this, &AbstractAlbumModel::slotAlbumIconChanged); connect(AlbumManager::instance(), &AlbumManager::signalAlbumRenamed, this, &AbstractAlbumModel::slotAlbumRenamed); // --- } AbstractAlbumModel::~AbstractAlbumModel() { delete d; } QVariant AbstractAlbumModel::data(const QModelIndex& index, int role) const { if (!index.isValid()) { return QVariant(); } Album* const a = static_cast(index.internalPointer()); return albumData(a, role); } QVariant AbstractAlbumModel::albumData(Album* a, int role) const { switch (role) { case Qt::DisplayRole: return a->title(); case Qt::ToolTipRole: return a->title(); case Qt::DecorationRole: // reimplement in subclasses return decorationRoleData(a); case AlbumTitleRole: return a->title(); case AlbumTypeRole: return a->type(); case AlbumPointerRole: return QVariant::fromValue(a); case AlbumIdRole: return a->id(); case AlbumGlobalIdRole: return a->globalID(); case AlbumSortRole: // reimplement in subclass return sortRoleData(a); default: return QVariant(); } } QVariant AbstractAlbumModel::headerData(int section, Qt::Orientation orientation, int role) const { Q_UNUSED(orientation) if ((section == 0) && (role == Qt::DisplayRole)) { return columnHeader(); } return QVariant(); } int AbstractAlbumModel::rowCount(const QModelIndex& parent) const { if (parent.isValid()) { Album* const a = static_cast(parent.internalPointer()); return a->childCount(); } else { if (!d->rootAlbum) { return 0; } if (d->rootBehavior == IncludeRootAlbum) { return 1; } else { return d->rootAlbum->childCount(); } } } int AbstractAlbumModel::columnCount(const QModelIndex& /*parent*/) const { return 1; } Qt::ItemFlags AbstractAlbumModel::flags(const QModelIndex& index) const { if (!index.isValid()) { return nullptr; } Album* const a = static_cast(index.internalPointer()); return itemFlags(a); } bool AbstractAlbumModel::hasChildren(const QModelIndex& parent) const { if (parent.isValid()) { Album* const a = static_cast(parent.internalPointer()); return a->firstChild(); } else { if (!d->rootAlbum) { return false; } if (d->rootBehavior == IncludeRootAlbum) { return 1; } else { return d->rootAlbum->firstChild(); } } } QModelIndex AbstractAlbumModel::index(int row, int column, const QModelIndex& parent) const { if ((column != 0) || (row < 0)) { return QModelIndex(); } if (parent.isValid()) { Album* const parentAlbum = static_cast(parent.internalPointer()); Album* const a = parentAlbum->childAtRow(row); if (a) { return createIndex(row, column, a); } } else { if (!d->rootAlbum) { return QModelIndex(); } if (d->rootBehavior == IncludeRootAlbum) { if (row == 0) { return createIndex(0, 0, d->rootAlbum); } } else { Album* const a = d->rootAlbum->childAtRow(row); if (a) { return createIndex(row, column, a); } } } return QModelIndex(); } QModelIndex AbstractAlbumModel::parent(const QModelIndex& index) const { if (index.isValid()) { Album* const a = static_cast(index.internalPointer()); return indexForAlbum(a->parent()); } return QModelIndex(); } Qt::DropActions AbstractAlbumModel::supportedDropActions() const { return Qt::CopyAction|Qt::MoveAction; } QStringList AbstractAlbumModel::mimeTypes() const { if (d->dragDropHandler) { return d->dragDropHandler->mimeTypes(); } return QStringList(); } bool AbstractAlbumModel::dropMimeData(const QMimeData*, Qt::DropAction, int, int, const QModelIndex&) { // we require custom solutions return false; } QMimeData* AbstractAlbumModel::mimeData(const QModelIndexList& indexes) const { if (!d->dragDropHandler) { return nullptr; } QList albums; foreach (const QModelIndex& index, indexes) { Album* const a = albumForIndex(index); if (a) { albums << a; } } return d->dragDropHandler->createMimeData(albums); } void AbstractAlbumModel::setEnableDrag(bool enable) { d->itemDrag = enable; } void AbstractAlbumModel::setEnableDrop(bool enable) { d->itemDrop = enable; } void AbstractAlbumModel::setDragDropHandler(AlbumModelDragDropHandler* handler) { d->dragDropHandler = handler; } AlbumModelDragDropHandler* AbstractAlbumModel::dragDropHandler() const { return d->dragDropHandler; } QModelIndex AbstractAlbumModel::indexForAlbum(Album* a) const { if (!a) { return QModelIndex(); } if (!filterAlbum(a)) { return QModelIndex(); } // a is root album? Decide on root behavior if (a == d->rootAlbum) { if (d->rootBehavior == IncludeRootAlbum) { // create top-level indexes return createIndex(0, 0, a); } else { // with this behavior, root album has no valid index return QModelIndex(); } } // Normal album. Get its row. return createIndex(a->rowFromAlbum(), 0, a); } Album* AbstractAlbumModel::albumForIndex(const QModelIndex& index) const { return (static_cast(index.internalPointer())); } Album* AbstractAlbumModel::retrieveAlbum(const QModelIndex& index) { return (index.data(AbstractAlbumModel::AlbumPointerRole).value()); } Album* AbstractAlbumModel::rootAlbum() const { return d->rootAlbum; } QModelIndex AbstractAlbumModel::rootAlbumIndex() const { return indexForAlbum(d->rootAlbum); } AbstractAlbumModel::RootAlbumBehavior AbstractAlbumModel::rootAlbumBehavior() const { return d->rootBehavior; } Album::Type AbstractAlbumModel::albumType() const { return d->type; } QVariant AbstractAlbumModel::decorationRoleData(Album*) const { return QVariant(); } QVariant AbstractAlbumModel::sortRoleData(Album* a) const { return a->title(); } QString AbstractAlbumModel::columnHeader() const { return i18n("Album"); } Qt::ItemFlags AbstractAlbumModel::itemFlags(Album*) const { Qt::ItemFlags f = Qt::ItemIsSelectable | Qt::ItemIsEnabled; if (d->itemDrag) { f |= Qt::ItemIsDragEnabled; } if (d->itemDrop) { f |= Qt::ItemIsDropEnabled; } return f; } bool AbstractAlbumModel::filterAlbum(Album* album) const { return (album && album->type() == d->type); } void AbstractAlbumModel::slotAlbumAboutToBeAdded(Album* album, Album* parent, Album* prev) { if (!filterAlbum(album)) { return; } if (album->isRoot() && d->rootBehavior == IgnoreRootAlbum) { d->rootAlbum = album; return; } // start inserting operation int row = prev ? prev->rowFromAlbum()+1 : 0; QModelIndex parentIndex = indexForAlbum(parent); beginInsertRows(parentIndex, row, row); // The root album will become available in time // when the model is instantiated before albums are initialized. // Set d->rootAlbum only after if (album->isRoot() && !d->rootAlbum) { d->rootAlbum = album; } // store album for slotAlbumAdded d->addingAlbum = album; } void AbstractAlbumModel::slotAlbumAdded(Album* album) { if (d->addingAlbum == album) { bool isRoot = (d->addingAlbum == d->rootAlbum); d->addingAlbum = nullptr; endInsertRows(); if (isRoot) { emit rootAlbumAvailable(); } } } void AbstractAlbumModel::slotAlbumAboutToBeDeleted(Album* album) { if (!filterAlbum(album)) { return; } if (album->isRoot() && (d->rootBehavior == IgnoreRootAlbum)) { albumCleared(album); d->rootAlbum = nullptr; return; } // begin removing operation int row = album->rowFromAlbum(); QModelIndex parent = indexForAlbum(album->parent()); beginRemoveRows(parent, row, row); albumCleared(album); // store album for slotAlbumHasBeenDeleted d->removingAlbum = reinterpret_cast(album); } void AbstractAlbumModel::slotAlbumHasBeenDeleted(quintptr p) { if (d->removingAlbum == p) { d->removingAlbum = 0; endRemoveRows(); } } void AbstractAlbumModel::slotAlbumsCleared() { d->rootAlbum = nullptr; beginResetModel(); allAlbumsCleared(); endResetModel(); } void AbstractAlbumModel::slotAlbumIconChanged(Album* album) { if (!filterAlbum(album)) { return; } QModelIndex index = indexForAlbum(album); emit dataChanged(index, index); } void AbstractAlbumModel::slotAlbumRenamed(Album* album) { if (!filterAlbum(album)) { return; } QModelIndex index = indexForAlbum(album); emit dataChanged(index, index); } // ------------------------------------------------------------------ AbstractSpecificAlbumModel::AbstractSpecificAlbumModel(Album::Type albumType, Album* const rootAlbum, RootAlbumBehavior rootBehavior, QObject* const parent) : AbstractAlbumModel(albumType, rootAlbum, rootBehavior, parent) { } void AbstractSpecificAlbumModel::setupThumbnailLoading() { AlbumThumbnailLoader* const loader = AlbumThumbnailLoader::instance(); connect(loader, SIGNAL(signalThumbnail(Album*,QPixmap)), this, SLOT(slotGotThumbnailFromIcon(Album*,QPixmap))); connect(loader, SIGNAL(signalFailed(Album*)), this, SLOT(slotThumbnailLost(Album*))); connect(loader, SIGNAL(signalReloadThumbnails()), this, SLOT(slotReloadThumbnails())); } QString AbstractSpecificAlbumModel::columnHeader() const { return m_columnHeader; } void AbstractSpecificAlbumModel::setColumnHeader(const QString& header) { m_columnHeader = header; emit headerDataChanged(Qt::Horizontal, 0, 0); } void AbstractSpecificAlbumModel::slotGotThumbnailFromIcon(Album* album, const QPixmap&) { // see decorationRole() method of subclasses if (!filterAlbum(album)) { return; } QModelIndex index = indexForAlbum(album); emit dataChanged(index, index); } void AbstractSpecificAlbumModel::slotThumbnailLost(Album*) { // ignore, use default thumbnail } void AbstractSpecificAlbumModel::slotReloadThumbnails() { // emit dataChanged() for all albums emitDataChangedForChildren(rootAlbum()); } void AbstractSpecificAlbumModel::emitDataChangedForChildren(Album* album) { if (!album) { return; } for (Album* child = album->firstChild() ; child ; child = child->next()) { if (filterAlbum(child)) { // recurse to children of children emitDataChangedForChildren(child); // emit signal for child QModelIndex index = indexForAlbum(child); emit dataChanged(index, index); } } } // ------------------------------------------------------------------ class Q_DECL_HIDDEN AbstractCountingAlbumModel::Private { public: explicit Private() : showCount(false) { } bool showCount; QMap countMap; QHash countHashReady; QSet includeChildrenAlbums; }; AbstractCountingAlbumModel::AbstractCountingAlbumModel(Album::Type albumType, Album* const rootAlbum, RootAlbumBehavior rootBehavior, QObject* const parent) : AbstractSpecificAlbumModel(albumType, rootAlbum, rootBehavior, parent), d(new Private) { } void AbstractCountingAlbumModel::setup() { connect(AlbumManager::instance(), SIGNAL(signalAlbumMoved(Album*)), this, SLOT(slotAlbumMoved(Album*))); } AbstractCountingAlbumModel::~AbstractCountingAlbumModel() { delete d; } void AbstractCountingAlbumModel::setShowCount(bool show) { if (d->showCount != show) { d->showCount = show; emitDataChangedForChildren(rootAlbum()); } } bool AbstractCountingAlbumModel::showCount() const { return d->showCount; } void AbstractCountingAlbumModel::excludeChildrenCount(const QModelIndex& index) { Album* const album = albumForIndex(index); if (!album) { return; } d->includeChildrenAlbums.remove(album->id()); updateCount(album); } void AbstractCountingAlbumModel::includeChildrenCount(const QModelIndex& index) { Album* const album = albumForIndex(index); if (!album) { return; } d->includeChildrenAlbums << album->id(); updateCount(album); } void AbstractCountingAlbumModel::setCountMap(const QMap& idCountMap) { d->countMap = idCountMap; QMap::const_iterator it = d->countMap.constBegin(); for ( ; it != d->countMap.constEnd() ; ++it) { updateCount(albumForId(it.key())); } } void AbstractCountingAlbumModel::updateCount(Album* album) { if (!album) { return; } // if the model does not contain the album, do nothing. QModelIndex index = indexForAlbum(album); if (!index.isValid()) { return; } QHash::iterator includeIt = d->countHashReady.find(album->id()); bool changed = false; // get count for album without children int count = d->countMap.value(album->id()); // if wanted, add up children's counts if (d->includeChildrenAlbums.contains(album->id())) { AlbumIterator it(album); while (it.current()) { count += d->countMap.value((*it)->id()); ++it; } } // insert or update if (includeIt == d->countHashReady.end()) { changed = true; d->countHashReady[album->id()] = count; } else { changed = (includeIt.value() != count); includeIt.value() = count; } // notify views if (changed) { emit dataChanged(index, index); } } void AbstractCountingAlbumModel::setCount(Album* album, int count) { if (!album) { return; } // if the model does not contain the album, do nothing. QModelIndex index = indexForAlbum(album); if (!index.isValid()) { return; } QHash::iterator includeIt = d->countHashReady.find(album->id()); bool changed = false; // insert or update if (includeIt == d->countHashReady.end()) { changed = true; d->countHashReady[album->id()] = count; } else { changed = (includeIt.value() != count); includeIt.value() = count; } // notify views if (changed) { emit dataChanged(index, index); } } QVariant AbstractCountingAlbumModel::albumData(Album* album, int role) const { if ((role == Qt::DisplayRole) && d->showCount && !album->isRoot()) { QHash::const_iterator it = d->countHashReady.constFind(album->id()); if (it != d->countHashReady.constEnd()) { return QString::fromUtf8("%1 (%2)").arg(albumName(album)).arg(it.value()); } } return AbstractSpecificAlbumModel::albumData(album, role); } int AbstractCountingAlbumModel::albumCount(Album* album) const { QHash::const_iterator it = d->countHashReady.constFind(album->id()); if (it != d->countHashReady.constEnd()) { return it.value(); } return -1; } QString AbstractCountingAlbumModel::albumName(Album* album) const { return album->title(); } void AbstractCountingAlbumModel::albumCleared(Album* album) { if (!AlbumManager::instance()->isMovingAlbum(album)) { d->countMap.remove(album->id()); d->countHashReady.remove(album->id()); d->includeChildrenAlbums.remove(album->id()); } } void AbstractCountingAlbumModel::allAlbumsCleared() { d->countMap.clear(); d->countHashReady.clear(); d->includeChildrenAlbums.clear(); } void AbstractCountingAlbumModel::slotAlbumMoved(Album*) { // need to update counts of all parents setCountMap(d->countMap); } // ------------------------------------------------------------------ class Q_DECL_HIDDEN AbstractCheckableAlbumModel::Private { public: explicit Private() : extraFlags(nullptr), rootIsCheckable(true), addExcludeTristate(false), staticVectorContainingCheckStateRole(1, Qt::CheckStateRole) - + { } Qt::ItemFlags extraFlags; bool rootIsCheckable; bool addExcludeTristate; QHash checkedAlbums; QVector staticVectorContainingCheckStateRole; }; AbstractCheckableAlbumModel::AbstractCheckableAlbumModel(Album::Type albumType, Album* const rootAlbum, RootAlbumBehavior rootBehavior, QObject* const parent) : AbstractCountingAlbumModel(albumType, rootAlbum, rootBehavior, parent), d(new Private) { setup(); } AbstractCheckableAlbumModel::~AbstractCheckableAlbumModel() { delete d; } void AbstractCheckableAlbumModel::setCheckable(bool isCheckable) { if (isCheckable) { d->extraFlags |= Qt::ItemIsUserCheckable; } else { d->extraFlags &= ~Qt::ItemIsUserCheckable; resetCheckedAlbums(); } } bool AbstractCheckableAlbumModel::isCheckable() const { return d->extraFlags & Qt::ItemIsUserCheckable; } void AbstractCheckableAlbumModel::setRootCheckable(bool isCheckable) { d->rootIsCheckable = isCheckable; Album* const root = rootAlbum(); if (!d->rootIsCheckable && root) { setChecked(root, false); } } bool AbstractCheckableAlbumModel::rootIsCheckable() const { return (d->rootIsCheckable && isCheckable()); } void AbstractCheckableAlbumModel::setTristate(bool isTristate) { if (isTristate) { d->extraFlags |= Qt::ItemIsTristate; } else { d->extraFlags &= ~Qt::ItemIsTristate; } } bool AbstractCheckableAlbumModel::isTristate() const { return d->extraFlags & Qt::ItemIsTristate; } void AbstractCheckableAlbumModel::setAddExcludeTristate(bool b) { d->addExcludeTristate = b; setCheckable(true); setTristate(b); } bool AbstractCheckableAlbumModel::isAddExcludeTristate() const { return (d->addExcludeTristate && isTristate()); } bool AbstractCheckableAlbumModel::isChecked(Album* album) const { return (d->checkedAlbums.value(album, Qt::Unchecked) == Qt::Checked); } Qt::CheckState AbstractCheckableAlbumModel::checkState(Album* album) const { return (d->checkedAlbums.value(album, Qt::Unchecked)); } void AbstractCheckableAlbumModel::setChecked(Album* album, bool isChecked) { setData(indexForAlbum(album), isChecked ? Qt::Checked : Qt::Unchecked, Qt::CheckStateRole); } void AbstractCheckableAlbumModel::setCheckState(Album* album, Qt::CheckState state) { setData(indexForAlbum(album), state, Qt::CheckStateRole); } void AbstractCheckableAlbumModel::toggleChecked(Album* album) { if (checkState(album) != Qt::PartiallyChecked) { setChecked(album, !isChecked(album)); } } QList AbstractCheckableAlbumModel::checkedAlbums() const { // return a list with all keys with value Qt::Checked return d->checkedAlbums.keys(Qt::Checked); } QList AbstractCheckableAlbumModel::partiallyCheckedAlbums() const { // return a list with all keys with value Qt::PartiallyChecked return d->checkedAlbums.keys(Qt::PartiallyChecked); } void AbstractCheckableAlbumModel::resetAllCheckedAlbums() { const QHash oldChecked = d->checkedAlbums; d->checkedAlbums.clear(); for (QHash::const_iterator it = oldChecked.begin() ; it != oldChecked.end() ; ++it) { if (it.value() != Qt::Unchecked) { QModelIndex index = indexForAlbum(it.key()); emit dataChanged(index, index, d->staticVectorContainingCheckStateRole); emit checkStateChanged(it.key(), Qt::Unchecked); } } } void AbstractCheckableAlbumModel::setDataForChildren(const QModelIndex& parent, const QVariant& value, int role) { setData(parent, value, role); for (int row = 0 ; row < rowCount(parent) ; ++row) { QModelIndex childIndex = index(row, 0, parent); setDataForChildren(childIndex, value, role); } } void AbstractCheckableAlbumModel::resetCheckedAlbums(const QModelIndex& parent) { if (parent == rootAlbumIndex()) { resetAllCheckedAlbums(); return; } setDataForChildren(parent, Qt::Unchecked, Qt::CheckStateRole); } void AbstractCheckableAlbumModel::setDataForParents(const QModelIndex& child, const QVariant& value, int role) { QModelIndex current = child; while (current.isValid() && (current != rootAlbumIndex())) { setData(current, value, role); current = parent(current); } } void AbstractCheckableAlbumModel::resetCheckedParentAlbums(const QModelIndex& child) { setDataForParents(child, Qt::Unchecked, Qt::CheckStateRole); } void AbstractCheckableAlbumModel::checkAllParentAlbums(const QModelIndex& child) { setDataForParents(child, Qt::Checked, Qt::CheckStateRole); } void AbstractCheckableAlbumModel::checkAllAlbums(const QModelIndex& parent) { setDataForChildren(parent, Qt::Checked, Qt::CheckStateRole); } void AbstractCheckableAlbumModel::invertCheckedAlbums(const QModelIndex& parent) { Album* const album = albumForIndex(parent); if (album) { toggleChecked(album); } for (int row = 0 ; row < rowCount(parent) ; ++row) { invertCheckedAlbums(index(row, 0, parent)); } } void AbstractCheckableAlbumModel::setCheckStateForChildren(Album* album, Qt::CheckState state) { QModelIndex index = indexForAlbum(album); setDataForChildren(index, state, Qt::CheckStateRole); } void AbstractCheckableAlbumModel::setCheckStateForParents(Album* album, Qt::CheckState state) { QModelIndex index = indexForAlbum(album); setDataForParents(index, state, Qt::CheckStateRole); } QVariant AbstractCheckableAlbumModel::albumData(Album* a, int role) const { if (role == Qt::CheckStateRole) { if ((d->extraFlags & Qt::ItemIsUserCheckable) && (!a->isRoot() || d->rootIsCheckable)) { // with Qt::Unchecked as default, albums not in the hash (initially all) // are simply regarded as unchecked Qt::CheckState state = d->checkedAlbums.value(a, Qt::Unchecked); if (d->addExcludeTristate) { // Use Qt::PartiallyChecked only internally, do not expose it to the TreeView return ((state == Qt::Unchecked) ? Qt::Unchecked : Qt::Checked); } return state; } } return AbstractCountingAlbumModel::albumData(a, role); } void AbstractCheckableAlbumModel::prepareAddExcludeDecoration(Album* a, QPixmap& icon) const { if (!d->addExcludeTristate) { return; } Qt::CheckState state = checkState(a); if (state != Qt::Unchecked) { int iconSize = qMax(icon.width(), icon.height()); int overlay_size = qMin(iconSize, qMax(16, iconSize * 2 / 3)); QPainter p(&icon); p.drawPixmap((icon.width() - overlay_size) / 2, (icon.height() - overlay_size) / 2, QIcon::fromTheme(state == Qt::PartiallyChecked ? QLatin1String("list-remove") : QLatin1String("list-add")).pixmap(overlay_size, overlay_size)); } } Qt::ItemFlags AbstractCheckableAlbumModel::flags(const QModelIndex& index) const { Qt::ItemFlags extraFlags = d->extraFlags; if (!d->rootIsCheckable) { QModelIndex root = rootAlbumIndex(); if (root.isValid() && (index == root)) { extraFlags &= ~Qt::ItemIsUserCheckable; } } return AbstractCountingAlbumModel::flags(index) | extraFlags; } bool AbstractCheckableAlbumModel::setData(const QModelIndex& index, const QVariant& value, int role) { if (role == Qt::CheckStateRole) { Qt::CheckState state = (Qt::CheckState)value.toInt(); Album* const album = albumForIndex(index); if (!album) { return false; } //qCDebug(DIGIKAM_GENERAL_LOG) << "Updating check state for album" << album->title() << "to" << value; d->checkedAlbums.insert(album, state); emit dataChanged(index, index); emit checkStateChanged(album, state); return true; } else { return AbstractCountingAlbumModel::setData(index, value, role); } } void AbstractCheckableAlbumModel::albumCleared(Album* album) { // preserve check state if album is only being moved if (!AlbumManager::instance()->isMovingAlbum(album)) { d->checkedAlbums.remove(album); } AbstractCountingAlbumModel::albumCleared(album); } void AbstractCheckableAlbumModel::allAlbumsCleared() { d->checkedAlbums.clear(); AbstractCountingAlbumModel::allAlbumsCleared(); } } // namespace Digikam diff --git a/core/libs/threadimageio/video/videodecoder.cpp b/core/libs/threadimageio/video/videodecoder.cpp index 139af5d8cd..eb1f9d1db3 100644 --- a/core/libs/threadimageio/video/videodecoder.cpp +++ b/core/libs/threadimageio/video/videodecoder.cpp @@ -1,272 +1,272 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2016-04-21 * Description : video thumbnails extraction based on ffmpeg * * Copyright (C) 2010 by Dirk Vanden Boer * Copyright (C) 2016-2018 by Maik Qualmann * Copyright (C) 2016-2020 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 "videodecoder.h" #include "videodecoder_p.h" // Local includes #include "digikam_debug.h" namespace Digikam { VideoDecoder::VideoDecoder(const QString& filename) : d(new Private) { initialize(filename); } VideoDecoder::~VideoDecoder() { destroy(); delete d; } void VideoDecoder::initialize(const QString& filename) { d->lastWidth = -1; d->lastHeight = -1; d->lastPixfmt = AV_PIX_FMT_NONE; #if LIBAVFORMAT_VERSION_MAJOR < 58 av_register_all(); #endif #if LIBAVCODEC_VERSION_MAJOR < 58 avcodec_register_all(); #endif if (avformat_open_input(&d->pFormatContext, filename.toUtf8().data(), nullptr, nullptr) != 0) { qDebug(DIGIKAM_GENERAL_LOG) << "Could not open input file: " << filename; return; } if (avformat_find_stream_info(d->pFormatContext, nullptr) < 0) { qDebug(DIGIKAM_GENERAL_LOG) << "Could not find stream information"; return; } if (!d->initializeVideo()) { return; } d->pFrame = av_frame_alloc(); if (d->pFrame) { d->initialized = true; } } bool VideoDecoder::getInitialized() const { return d->initialized; } void VideoDecoder::destroy() { d->deleteFilterGraph(); if (d->pVideoCodecContext) { avcodec_close(d->pVideoCodecContext); d->pVideoCodecContext = nullptr; } if (d->pFormatContext) { avformat_close_input(&d->pFormatContext); d->pFormatContext = nullptr; } if (d->pPacket) { av_packet_unref(d->pPacket); delete d->pPacket; d->pPacket = nullptr; } if (d->pFrame) { av_frame_free(&d->pFrame); d->pFrame = nullptr; } if (d->pFrameBuffer) { av_free(d->pFrameBuffer); d->pFrameBuffer = nullptr; } } -QString VideoDecoder::getCodec() const +QString VideoDecoder::getCodec() const { QString codecName; if (d->pVideoCodec) { codecName = QLatin1String(d->pVideoCodec->name); } return codecName; } int VideoDecoder::getWidth() const { if (d->pVideoCodecContext) { return d->pVideoCodecContext->width; } return -1; } int VideoDecoder::getHeight() const { if (d->pVideoCodecContext) { return d->pVideoCodecContext->height; } return -1; } int VideoDecoder::getDuration() const { if (d->pFormatContext) { return static_cast(d->pFormatContext->duration / AV_TIME_BASE); } return 0; } void VideoDecoder::seek(int timeInSeconds) { if (!d->allowSeek) { return; } qint64 timestamp = AV_TIME_BASE * static_cast(timeInSeconds); if (timestamp < 0) { timestamp = 0; } int ret = av_seek_frame(d->pFormatContext, -1, timestamp, AVSEEK_FLAG_FRAME | AVSEEK_FLAG_BACKWARD); if (ret >= 0) { avcodec_flush_buffers(d->pVideoCodecContext); } else { qDebug(DIGIKAM_GENERAL_LOG) << "Seeking in video failed"; return; } int keyFrameAttempts = 0; bool gotFrame = false; do { int count = 0; gotFrame = false; while (!gotFrame && (count < 20)) { d->getVideoPacket(); gotFrame = d->decodeVideoPacket(); count++; } keyFrameAttempts++; } while ((!gotFrame || !d->pFrame->key_frame) && (keyFrameAttempts < 200)); if (!gotFrame) { qDebug(DIGIKAM_GENERAL_LOG) << "Seeking in video failed"; } } bool VideoDecoder::decodeVideoFrame() const { bool frameFinished = false; while (!frameFinished && d->getVideoPacket()) { frameFinished = d->decodeVideoPacket(); } if (!frameFinished) { qDebug(DIGIKAM_GENERAL_LOG) << "decodeVideoFrame() failed: frame not finished"; } return frameFinished; } void VideoDecoder::getScaledVideoFrame(int scaledSize, bool maintainAspectRatio, VideoFrame& videoFrame) { if (d->pFrame->interlaced_frame) { d->processFilterGraph(d->pFrame, d->pFrame, d->pVideoCodecContext->pix_fmt, d->pVideoCodecContext->width, d->pVideoCodecContext->height); } int scaledWidth, scaledHeight; d->convertAndScaleFrame(AV_PIX_FMT_RGB24, scaledSize, maintainAspectRatio, scaledWidth, scaledHeight); videoFrame.width = scaledWidth; videoFrame.height = scaledHeight; videoFrame.lineSize = d->pFrame->linesize[0]; videoFrame.frameData.clear(); videoFrame.frameData.resize(videoFrame.lineSize * videoFrame.height); memcpy((&(videoFrame.frameData.front())), d->pFrame->data[0], videoFrame.lineSize * videoFrame.height); } } // namespace Digikam diff --git a/core/libs/widgets/graphicsview/dimgchilditem.cpp b/core/libs/widgets/graphicsview/dimgchilditem.cpp index f221e48d43..b9b1a7fdaf 100644 --- a/core/libs/widgets/graphicsview/dimgchilditem.cpp +++ b/core/libs/widgets/graphicsview/dimgchilditem.cpp @@ -1,324 +1,324 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-05-15 * Description : Graphics View item for a child item on a DImg item * * Copyright (C) 2010-2011 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. * * ============================================================ */ // Local includes #include "dimgchilditem.h" #include "graphicsdimgitem.h" #include "imagezoomsettings.h" namespace Digikam { /** * This is a simple example. Just create * new SimpleRectChildItem(item); * where item is a GrahpicsDImgItem, * and at the center of the image, * a rectangle of 1% the size of the image will be drawn. * * class SimpleRectChildItem : public DImgChildItem * { * public: - * + * * SimpleRectChildItem(QGraphicsItem* const parent) * : DImgChildItem(parent) * { * setRelativePos(0.5, 0.5); * setRelativeSize(0.01, 0.01); * } - * + * * void paint(QPainter* painter, const QStyleOptionGraphicsItem*, QWidget*) * { * painter->setPen(Qt::red); * painter->drawRect(boundingRect()); * } * }; */ class Q_DECL_HIDDEN DImgChildItem::Private { public: explicit Private(DImgChildItem* const q) : q(q) { } void connectParent(bool active = true); public: QPointF relativePos; QSizeF relativeSize; DImgChildItem* const q; }; void DImgChildItem::Private::connectParent(bool active) { GraphicsDImgItem* const parent = q->parentDImgItem(); if (parent) { if (active) { q->connect(parent, SIGNAL(imageSizeChanged(QSizeF)), q, SLOT(imageSizeChanged(QSizeF))); } else { q->disconnect(parent, SIGNAL(imageSizeChanged(QSizeF)), q, SLOT(imageSizeChanged(QSizeF))); } } } DImgChildItem::DImgChildItem(QGraphicsItem* const parent) : QGraphicsObject(parent), d(new Private(this)) { d->connectParent(); } DImgChildItem::~DImgChildItem() { delete d; } void DImgChildItem::setRelativePos(const QPointF& relativePos) { if (d->relativePos == relativePos) { return; } d->relativePos = relativePos; updatePos(); emit positionOnImageChanged(); emit geometryOnImageChanged(); } void DImgChildItem::setRelativeSize(const QSizeF& relativeSize) { if (d->relativeSize == relativeSize) { return; } d->relativeSize = relativeSize; updateSize(); emit sizeOnImageChanged(); emit geometryOnImageChanged(); } void DImgChildItem::setRelativeRect(const QRectF& rect) { setRelativePos(rect.topLeft()); setRelativeSize(rect.size()); } QRectF DImgChildItem::relativeRect() const { return QRectF(d->relativePos, d->relativeSize); } QPointF DImgChildItem::relativePos() const { return d->relativePos; } QSizeF DImgChildItem::relativeSize() const { return d->relativeSize; } void DImgChildItem::setOriginalPos(const QPointF& posInOriginal) { if (!parentItem()) { return; } QSizeF originalSize = parentDImgItem()->zoomSettings()->originalImageSize(); setRelativePos( QPointF(posInOriginal.x() / originalSize.width(), posInOriginal.y() / originalSize.height()) ); } void DImgChildItem::setOriginalSize(const QSizeF& sizeInOriginal) { if (!parentItem()) { return; } QSizeF originalSize = parentDImgItem()->zoomSettings()->originalImageSize(); setRelativeSize( QSizeF(sizeInOriginal.width() / originalSize.width(), sizeInOriginal.height() / originalSize.height()) ); } void DImgChildItem::setOriginalRect(const QRectF& rect) { setOriginalPos(rect.topLeft()); setOriginalSize(rect.size()); } QRect DImgChildItem::originalRect() const { return QRect(originalPos(), originalSize()); } QSize DImgChildItem::originalSize() const { QSizeF originalSize = parentDImgItem()->zoomSettings()->originalImageSize(); return QSizeF(d->relativeSize.width() * originalSize.width(), d->relativeSize.height() * originalSize.height()).toSize(); } QPoint DImgChildItem::originalPos() const { QSizeF originalSize = parentDImgItem()->zoomSettings()->originalImageSize(); return QPointF(d->relativePos.x() * originalSize.width(), d->relativePos.y() * originalSize.height()).toPoint(); } void DImgChildItem::setPos(const QPointF& pos) { if (!parentItem()) { return; } const QSizeF imageSize = parentItem()->boundingRect().size(); setRelativePos(QPointF(pos.x() / imageSize.width(), pos.y() / imageSize.height())); } void DImgChildItem::setSize(const QSizeF& size) { if (!parentItem()) { return; } const QSizeF imageSize = parentItem()->boundingRect().size(); setRelativeSize(QSizeF(size.width() / imageSize.width(), size.height() / imageSize.height())); } void DImgChildItem::setRect(const QRectF& rect) { setPos(rect.topLeft()); setSize(rect.size()); } void DImgChildItem::setRectInSceneCoordinates(const QRectF& rect) { if (!parentItem()) { return; } QRectF itemRect(parentItem()->mapFromScene(rect.topLeft()), parentItem()->mapFromScene(rect.bottomRight())); setRect(itemRect); } QRectF DImgChildItem::rect() const { return QRectF(pos(), size()); } QSizeF DImgChildItem::size() const { if (!parentItem()) { return QSizeF(); } const QSizeF imageSize = parentItem()->boundingRect().size(); return QSizeF(d->relativeSize.width() * imageSize.width(), d->relativeSize.height() * imageSize.height()); } GraphicsDImgItem* DImgChildItem::parentDImgItem() const { return dynamic_cast(parentItem()); } void DImgChildItem::updatePos() { if (!parentItem()) { return; } QSizeF imageSize = parentItem()->boundingRect().size(); QGraphicsObject::setPos(imageSize.width() * d->relativePos.x(), imageSize.height() * d->relativePos.y()); emit positionChanged(); emit geometryChanged(); } void DImgChildItem::updateSize() { prepareGeometryChange(); emit sizeChanged(); emit geometryChanged(); } void DImgChildItem::imageSizeChanged(const QSizeF&) { updateSize(); updatePos(); } QVariant DImgChildItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant& value) { if (change == QGraphicsItem::ItemParentChange) { d->connectParent(false); // disconnect old parent } else if (change == QGraphicsItem::ItemParentHasChanged) { d->connectParent(true); // connect new parent } return QGraphicsObject::itemChange(change, value); } QRectF DImgChildItem::boundingRect() const { if (!parentItem()) { return QRectF(); } return QRectF(QPointF(0, 0), size()); } } // namespace Digikam diff --git a/core/libs/widgets/graphicsview/graphicsdimgview.cpp b/core/libs/widgets/graphicsview/graphicsdimgview.cpp index fef72b5e67..679f1ea15e 100644 --- a/core/libs/widgets/graphicsview/graphicsdimgview.cpp +++ b/core/libs/widgets/graphicsview/graphicsdimgview.cpp @@ -1,546 +1,546 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-04-30 * Description : Graphics View for DImg preview * * Copyright (C) 2010-2012 by Marcel Wiesweg * Copyright (C) 2011-2020 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 "graphicsdimgview.h" // Qt includes #include #include #include #include #include // Local includes #include "digikam_debug.h" #include "dimgpreviewitem.h" #include "imagezoomsettings.h" #include "paniconwidget.h" #include "previewlayout.h" #include "dimgchilditem.h" namespace Digikam { class Q_DECL_HIDDEN GraphicsDImgView::Private { public: explicit Private() : scene(nullptr), item(nullptr), layout(nullptr), cornerButton(nullptr), panIconPopup(nullptr), movingInProgress(false), showText(true) { } QGraphicsScene* scene; GraphicsDImgItem* item; SinglePhotoPreviewLayout* layout; QToolButton* cornerButton; PanIconFrame* panIconPopup; QPoint mousePressPos; QPoint panningScrollPos; bool movingInProgress; bool showText; }; GraphicsDImgView::GraphicsDImgView(QWidget* const parent) : QGraphicsView(parent), d(new Private) { d->scene = new QGraphicsScene(this); d->scene->setItemIndexMethod(QGraphicsScene::NoIndex); setScene(d->scene); d->layout = new SinglePhotoPreviewLayout(this); d->layout->setGraphicsView(this); setViewportUpdateMode(QGraphicsView::MinimalViewportUpdate); horizontalScrollBar()->setSingleStep(1); horizontalScrollBar()->setPageStep(1); verticalScrollBar()->setSingleStep(1); verticalScrollBar()->setPageStep(1); connect(horizontalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(slotContentsMoved())); connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(slotContentsMoved())); } GraphicsDImgView::~GraphicsDImgView() { delete d; } void GraphicsDImgView::setItem(GraphicsDImgItem* const item) { d->item = item; d->scene->addItem(d->item); d->layout->addItem(d->item); } GraphicsDImgItem* GraphicsDImgView::item() const { return d->item; } DImgPreviewItem* GraphicsDImgView::previewItem() const { return dynamic_cast(item()); } SinglePhotoPreviewLayout* GraphicsDImgView::layout() const { return d->layout; } void GraphicsDImgView::installPanIcon() { d->cornerButton = PanIconWidget::button(); setCornerWidget(d->cornerButton); connect(d->cornerButton, SIGNAL(pressed()), this, SLOT(slotCornerButtonPressed())); } void GraphicsDImgView::drawForeground(QPainter* p, const QRectF& rect) { QGraphicsView::drawForeground(p, rect); QString text = d->item->userLoadingHint(); if (text.isNull() || !d->showText) { return; } QRect viewportRect = viewport()->rect(); QRect fontRect = p->fontMetrics().boundingRect(viewportRect, 0, text); QPoint drawingPoint(viewportRect.topRight().x() - fontRect.width() - 10, viewportRect.topRight().y() + 5); QPointF sceneDrawingPoint = mapToScene(drawingPoint); QRectF sceneDrawingRect(sceneDrawingPoint, fontRect.size()); if (!rect.intersects(sceneDrawingRect)) { return; } drawText(p, sceneDrawingRect, text); } void GraphicsDImgView::drawText(QPainter* p, const QRectF& rect, const QString& text) { p->save(); p->setRenderHint(QPainter::Antialiasing, true); p->setBackgroundMode(Qt::TransparentMode); // increase width by 5 and height by 2 QRectF textRect = rect.adjusted(0, 0, 5, 2); // Draw background p->setPen(Qt::black); QColor semiTransBg = palette().color(QPalette::Window); semiTransBg.setAlpha(190); p->setBrush(semiTransBg); /* p->translate(0.5, 0.5); */ p->drawRoundedRect(textRect, 10.0, 10.0); // Draw shadow and text p->setPen(palette().color(QPalette::Window).darker(115)); p->drawText(textRect.translated(3, 1), text); p->setPen(palette().color(QPalette::WindowText)); p->drawText(textRect.translated(2, 0), text); p->restore(); } void GraphicsDImgView::mouseDoubleClickEvent(QMouseEvent* e) { QGraphicsView::mouseDoubleClickEvent(e); if (!acceptsMouseClick(e)) { return; } if (e->button() == Qt::LeftButton) { emit leftButtonDoubleClicked(); if (!qApp->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick)) { emit activated(); } } } void GraphicsDImgView::mousePressEvent(QMouseEvent* e) { QGraphicsView::mousePressEvent(e); d->mousePressPos = QPoint(); d->movingInProgress = false; if (!acceptsMouseClick(e)) { return; } if (e->button() == Qt::LeftButton) { emit leftButtonClicked(); } if ((e->button() == Qt::LeftButton) || (e->button() == Qt::MidButton)) { d->mousePressPos = e->pos(); if (!qApp->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick) || (e->button() == Qt::MidButton)) { startPanning(e->pos()); } return; } if (e->button() == Qt::RightButton) { emit rightButtonClicked(); } } void GraphicsDImgView::mouseMoveEvent(QMouseEvent* e) { QGraphicsView::mouseMoveEvent(e); if (((e->buttons() & Qt::LeftButton) || (e->buttons() & Qt::MidButton)) && !d->mousePressPos.isNull()) { if (!d->movingInProgress && (e->buttons() & Qt::LeftButton)) { if ((d->mousePressPos - e->pos()).manhattanLength() > QApplication::startDragDistance()) { startPanning(d->mousePressPos); } } if (d->movingInProgress) { continuePanning(e->pos()); } } } void GraphicsDImgView::mouseReleaseEvent(QMouseEvent* e) { QGraphicsView::mouseReleaseEvent(e); // Do not call acceptsMouseClick() here, only on press. Seems that release event are accepted per default. if (((e->button() == Qt::LeftButton) || (e->button() == Qt::MidButton)) && !d->mousePressPos.isNull()) { if (!d->movingInProgress && (e->button() == Qt::LeftButton)) { if (qApp->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick)) { emit activated(); } } else { finishPanning(); } } d->movingInProgress = false; d->mousePressPos = QPoint(); } bool GraphicsDImgView::acceptsMouseClick(QMouseEvent* e) { // the basic condition is that now item ate the event if (e->isAccepted()) { return false; } return true; } void GraphicsDImgView::resizeEvent(QResizeEvent* e) { QGraphicsView::resizeEvent(e); d->layout->updateZoomAndSize(); emit resized(); emit viewportRectChanged(mapToScene(viewport()->rect()).boundingRect()); } void GraphicsDImgView::scrollContentsBy(int dx, int dy) { QGraphicsView::scrollContentsBy(dx, dy); emit viewportRectChanged(mapToScene(viewport()->rect()).boundingRect()); } void GraphicsDImgView::startPanning(const QPoint& pos) { if (horizontalScrollBar()->maximum() || verticalScrollBar()->maximum()) { d->movingInProgress = true; d->mousePressPos = pos; d->panningScrollPos = QPoint(horizontalScrollBar()->value(), verticalScrollBar()->value()); viewport()->setCursor(Qt::SizeAllCursor); } } void GraphicsDImgView::continuePanning(const QPoint& pos) { QPoint delta = pos - d->mousePressPos; horizontalScrollBar()->setValue(d->panningScrollPos.x() + (isRightToLeft() ? delta.x() : -delta.x())); verticalScrollBar()->setValue(d->panningScrollPos.y() - delta.y()); emit contentsMoved(false); viewport()->update(); } void GraphicsDImgView::finishPanning() { emit contentsMoved(true); viewport()->unsetCursor(); } void GraphicsDImgView::scrollPointOnPoint(const QPointF& scenePos, const QPoint& viewportPos) { // This is inspired from QGraphicsView's centerOn() QPointF viewPoint = matrix().map(scenePos); if (horizontalScrollBar()->maximum()) { if (isRightToLeft()) { qint64 horizontal = 0; horizontal += horizontalScrollBar()->minimum(); horizontal += horizontalScrollBar()->maximum(); horizontal -= int(viewPoint.x() - viewportPos.x()); horizontalScrollBar()->setValue(horizontal); } else { horizontalScrollBar()->setValue(int(viewPoint.x() - viewportPos.x())); } } if (verticalScrollBar()->maximum()) { verticalScrollBar()->setValue(int(viewPoint.y() - viewportPos.y())); } viewport()->update(); } void GraphicsDImgView::wheelEvent(QWheelEvent* e) { int p = this->verticalScrollBar()->sliderPosition(); if (e->modifiers() & Qt::ShiftModifier) { e->accept(); if (e->angleDelta().y() < 0) { emit toNextImage(); } else if (e->angleDelta().y() > 0) { emit toPreviousImage(); } return; } else if (e->modifiers() & Qt::ControlModifier) { // When zooming with the mouse-wheel, the image center is kept fixed. - + if (e->angleDelta().y() < 0) { d->layout->decreaseZoom(e->pos()); } else if (e->angleDelta().y() > 0) { d->layout->increaseZoom(e->pos()); } return; } else if( ((p == this->verticalScrollBar()->maximum()) && (e->angleDelta().y() < 0)) || ((p == this->verticalScrollBar()->minimum()) && (e->angleDelta().y() > 0)) ) { // I had to add this condition for "ImageBrushGuideWidget" that subclasses ImageRegionWidget, used // in the healingclone tool. // If I remove that condition, this event handler gets called recursively and the program // crashes.T I couldn't figure out the reason. [Ahmed Fathy] return; } else { QGraphicsView::wheelEvent(e); } } void GraphicsDImgView::slotCornerButtonPressed() { if (d->panIconPopup) { d->panIconPopup->hide(); d->panIconPopup->deleteLater(); d->panIconPopup = nullptr; } d->panIconPopup = new PanIconFrame(this); PanIconWidget* const pan = new PanIconWidget(d->panIconPopup); /* connect(pan, SIGNAL(signalSelectionTakeFocus()), this, SIGNAL(signalContentTakeFocus())); */ connect(pan, SIGNAL(signalSelectionMoved(QRect,bool)), this, SLOT(slotPanIconSelectionMoved(QRect,bool))); connect(pan, SIGNAL(signalHidden()), this, SLOT(slotPanIconHidden())); pan->setImage(180, 120, item()->image()); QRectF sceneRect(mapToScene(viewport()->rect().topLeft()), mapToScene(viewport()->rect().bottomRight())); pan->setRegionSelection(item()->zoomSettings()->sourceRect(sceneRect).toRect()); pan->setMouseFocus(); d->panIconPopup->setMainWidget(pan); /* slotContentTakeFocus(); */ QPoint g = mapToGlobal(viewport()->pos()); g.setX(g.x()+ viewport()->size().width()); g.setY(g.y()+ viewport()->size().height()); d->panIconPopup->popup(QPoint(g.x() - d->panIconPopup->width(), g.y() - d->panIconPopup->height())); pan->setCursorToLocalRegionSelectionCenter(); } void GraphicsDImgView::slotPanIconHidden() { d->cornerButton->blockSignals(true); d->cornerButton->animateClick(); d->cornerButton->blockSignals(false); } void GraphicsDImgView::slotPanIconSelectionMoved(const QRect& imageRect, bool b) { QRectF zoomRect = item()->zoomSettings()->mapImageToZoom(imageRect); qCDebug(DIGIKAM_WIDGETS_LOG) << imageRect << zoomRect; centerOn(item()->mapToScene(zoomRect.center())); viewport()->update(); if (b) { d->panIconPopup->hide(); d->panIconPopup->deleteLater(); d->panIconPopup = nullptr; slotPanIconHidden(); //slotContentLeaveFocus(); } } void GraphicsDImgView::slotContentsMoved() { emit contentsMoving(horizontalScrollBar()->value(), verticalScrollBar()->value()); viewport()->update(); } int GraphicsDImgView::contentsX() const { return horizontalScrollBar()->value(); } int GraphicsDImgView::contentsY() const { return verticalScrollBar()->value(); } void GraphicsDImgView::setContentsPos(int x, int y) { horizontalScrollBar()->setValue(x); verticalScrollBar()->setValue(y); } void GraphicsDImgView::setShowText(bool val) { d->showText = val; } QRect GraphicsDImgView::visibleArea() const { return (mapToScene(viewport()->geometry()).boundingRect().toRect()); } void GraphicsDImgView::fitToWindow() { layout()->fitToWindow(); update(); } void GraphicsDImgView::toggleFullScreen(bool set) { if (set) { d->scene->setBackgroundBrush(Qt::black); setFrameShape(QFrame::NoFrame); } else { d->scene->setBackgroundBrush(Qt::NoBrush); setFrameShape(QFrame::StyledPanel); } } } // namespace Digikam diff --git a/core/libs/widgets/layout/sidebar.cpp b/core/libs/widgets/layout/sidebar.cpp index 2f5ce23d7f..32c05f38c8 100644 --- a/core/libs/widgets/layout/sidebar.cpp +++ b/core/libs/widgets/layout/sidebar.cpp @@ -1,1475 +1,1475 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2005-03-22 * Description : a widget to manage sidebar in GUI. * * Copyright (C) 2005-2006 by Joern Ahrens * Copyright (C) 2006-2020 by Gilles Caulier * Copyright (C) 2008-2011 by Marcel Wiesweg * Copyright (C) 2001-2003 by Joseph Wenninger * * 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 "sidebar.h" // C++ includes #include // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" namespace Digikam { class Q_DECL_HIDDEN DMultiTabBarFrame::Private { public: explicit Private() : mainLayout(nullptr), position(Qt::LeftEdge), style(DMultiTabBar::AllIconsText) { } QBoxLayout* mainLayout; QList tabs; Qt::Edge position; DMultiTabBar::TextStyle style; }; DMultiTabBarFrame::DMultiTabBarFrame(QWidget* const parent, Qt::Edge pos) : QFrame(parent), d(new Private) { d->position = pos; if ((pos == Qt::LeftEdge) || (pos == Qt::RightEdge)) { d->mainLayout = new QVBoxLayout(this); } else { d->mainLayout = new QHBoxLayout(this); } d->mainLayout->setContentsMargins(QMargins()); d->mainLayout->setSpacing(0); d->mainLayout->addStretch(); setFrameStyle(NoFrame); setBackgroundRole(QPalette::Window); } DMultiTabBarFrame::~DMultiTabBarFrame() { qDeleteAll(d->tabs); d->tabs.clear(); delete d; } void DMultiTabBarFrame::setStyle(DMultiTabBar::TextStyle style) { d->style = style; for (int i = 0 ; i < d->tabs.count() ; ++i) { d->tabs.at(i)->setStyle(d->style); } updateGeometry(); } void DMultiTabBarFrame::contentsMousePressEvent(QMouseEvent* e) { e->ignore(); } void DMultiTabBarFrame::mousePressEvent(QMouseEvent* e) { e->ignore(); } DMultiTabBarTab* DMultiTabBarFrame::tab(int id) const { QListIterator it(d->tabs); while (it.hasNext()) { DMultiTabBarTab* const tab = it.next(); if (tab->id() == id) { return tab; } } return nullptr; } int DMultiTabBarFrame::appendTab(const QPixmap& pic, int id, const QString& text) { DMultiTabBarTab* const tab = new DMultiTabBarTab(pic, text, id, this, d->position, d->style); d->tabs.append(tab); // Insert before the stretch. d->mainLayout->insertWidget(d->tabs.size()-1, tab); tab->show(); return 0; } void DMultiTabBarFrame::removeTab(int id) { for (int pos = 0 ; pos < d->tabs.count() ; ++pos) { if (d->tabs.at(pos)->id() == id) { // remove & delete the tab delete d->tabs.takeAt(pos); break; } } } void DMultiTabBarFrame::setPosition(Qt::Edge pos) { d->position = pos; for (int i = 0 ; i < d->tabs.count() ; ++i) { d->tabs.at(i)->setPosition(d->position); } updateGeometry(); } QList* DMultiTabBarFrame::tabs() { return &d->tabs; } // ------------------------------------------------------------------------------------- DMultiTabBarButton::DMultiTabBarButton(const QPixmap& pic, const QString& text, int id, QWidget* const parent) : QPushButton(QIcon(pic), text, parent), m_id(id) { // --- NOTE: use dynamic binding as slotClicked() is a virtual method which can be re-implemented in derived classes. connect(this, &QPushButton::clicked, this, &DMultiTabBarButton::slotClicked); // we can't see the focus, so don't take focus. #45557 // If keyboard navigation is wanted, then only the bar should take focus, // and arrows could change the focused button; but generally, tabbars don't take focus anyway. setFocusPolicy(Qt::NoFocus); // See RB #128005 setAttribute(Qt::WA_LayoutUsesWidgetRect); } DMultiTabBarButton::~DMultiTabBarButton() { } void DMultiTabBarButton::setText(const QString& text) { QPushButton::setText(text); } void DMultiTabBarButton::slotClicked() { updateGeometry(); emit clicked(m_id); } int DMultiTabBarButton::id() const { return m_id; } void DMultiTabBarButton::hideEvent(QHideEvent* e) { QPushButton::hideEvent(e); DMultiTabBar* const tb = dynamic_cast(parentWidget()); if (tb) { tb->updateSeparator(); } } void DMultiTabBarButton::showEvent(QShowEvent* e) { QPushButton::showEvent(e); DMultiTabBar* const tb = dynamic_cast(parentWidget()); if (tb) { tb->updateSeparator(); } } void DMultiTabBarButton::paintEvent(QPaintEvent*) { QStyleOptionButton opt; opt.initFrom(this); opt.icon = icon(); opt.iconSize = iconSize(); // removes the QStyleOptionButton::HasMenu ButtonFeature opt.features = QStyleOptionButton::Flat; QPainter painter(this); style()->drawControl(QStyle::CE_PushButton, &opt, &painter, this); } // ------------------------------------------------------------------------------------- class Q_DECL_HIDDEN DMultiTabBarTab::Private { public: explicit Private() : position(Qt::LeftEdge), style(DMultiTabBar::AllIconsText) { } Qt::Edge position; DMultiTabBar::TextStyle style; }; DMultiTabBarTab::DMultiTabBarTab(const QPixmap& pic, const QString& text, int id, QWidget* const parent, Qt::Edge pos, DMultiTabBar::TextStyle style) : DMultiTabBarButton(pic, text, id, parent), d(new Private) { d->style = style; d->position = pos; setToolTip(text); setCheckable(true); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); // shrink down to icon only, but prefer to show text if it's there } DMultiTabBarTab::~DMultiTabBarTab() { delete d; } void DMultiTabBarTab::setPosition(Qt::Edge pos) { d->position = pos; updateGeometry(); } void DMultiTabBarTab::setStyle(DMultiTabBar::TextStyle style) { d->style = style; updateGeometry(); } QPixmap DMultiTabBarTab::iconPixmap() const { int iconSize = style()->pixelMetric(QStyle::PM_SmallIconSize, nullptr, this); return icon().pixmap(iconSize); } void DMultiTabBarTab::initStyleOption(QStyleOptionToolButton* opt) const { opt->initFrom(this); // Setup icon if (!icon().isNull()) { opt->iconSize = iconPixmap().size(); opt->icon = icon(); } // Should we draw text? if (shouldDrawText()) { opt->text = text(); } if (underMouse()) { opt->state |= QStyle::State_AutoRaise | QStyle::State_MouseOver | QStyle::State_Raised; } if (isChecked()) { opt->state |= QStyle::State_Sunken | QStyle::State_On; } opt->font = font(); opt->toolButtonStyle = shouldDrawText() ? Qt::ToolButtonTextBesideIcon : Qt::ToolButtonIconOnly; opt->subControls = QStyle::SC_ToolButton; } QSize DMultiTabBarTab::sizeHint() const { return computeSizeHint(shouldDrawText()); } QSize DMultiTabBarTab::minimumSizeHint() const { return computeSizeHint(false); } void DMultiTabBarTab::computeMargins(int* hMargin, int* vMargin) const { // Unfortunately, QStyle does not give us enough information to figure out // where to place things, so we try to reverse-engineer it QStyleOptionToolButton opt; initStyleOption(&opt); QPixmap iconPix = iconPixmap(); QSize trialSize = iconPix.size(); QSize expandSize = style()->sizeFromContents(QStyle::CT_ToolButton, &opt, trialSize, this); *hMargin = (expandSize.width() - trialSize.width())/2; *vMargin = (expandSize.height() - trialSize.height())/2; } QSize DMultiTabBarTab::computeSizeHint(bool withText) const { // Compute as horizontal first, then flip around if need be. QStyleOptionToolButton opt; initStyleOption(&opt); int hMargin, vMargin; computeMargins(&hMargin, &vMargin); // Compute interior size, starting from pixmap.. QPixmap iconPix = iconPixmap(); QSize size = iconPix.size(); // Always include text height in computation, to avoid resizing the minor direction // when expanding text.. QSize textSize = fontMetrics().size(0, text()); size.setHeight(qMax(size.height(), textSize.height())); // Pick margins for major/minor direction, depending on orientation int majorMargin = isVertical() ? vMargin : hMargin; int minorMargin = isVertical() ? hMargin : vMargin; size.setWidth (size.width() + 2*majorMargin); size.setHeight(size.height() + 2*minorMargin); if (withText) { // Add enough room for the text, and an extra major margin. size.setWidth(size.width() + textSize.width() + majorMargin); } if (isVertical()) { return QSize(size.height(), size.width()); } return size; } void DMultiTabBarTab::setState(bool newState) { setChecked(newState); updateGeometry(); } void DMultiTabBarTab::setIcon(const QString& icon) { const QIcon i = QIcon::fromTheme(icon); const int iconSize = style()->pixelMetric(QStyle::PM_SmallIconSize, nullptr, this); setIcon(i.pixmap(iconSize)); } void DMultiTabBarTab::setIcon(const QPixmap& icon) { QPushButton::setIcon(icon); } bool DMultiTabBarTab::shouldDrawText() const { return ((d->style == DMultiTabBar::AllIconsText) || isChecked()); } bool DMultiTabBarTab::isVertical() const { return ((d->position == Qt::RightEdge) || (d->position == Qt::LeftEdge)); } void DMultiTabBarTab::paintEvent(QPaintEvent*) { QPainter painter(this); QStyleOptionToolButton opt; initStyleOption(&opt); // Paint bevel.. if (underMouse() || isChecked()) { opt.text.clear(); opt.icon = QIcon(); style()->drawComplexControl(QStyle::CC_ToolButton, &opt, &painter, this); } int hMargin, vMargin; computeMargins(&hMargin, &vMargin); // We first figure out how much room we have for the text, based on // icon size and margin, try to fit in by eliding, and perhaps // give up on drawing the text entirely if we're too short on room QPixmap icon = iconPixmap(); int textRoom = 0; int iconRoom = 0; QString t; if (shouldDrawText()) { if (isVertical()) { iconRoom = icon.height() + 2*vMargin; textRoom = height() - iconRoom - vMargin; } else { iconRoom = icon.width() + 2*hMargin; textRoom = width() - iconRoom - hMargin; } t = painter.fontMetrics().elidedText(text(), Qt::ElideRight, textRoom); // See whether anything is left. Qt will return either // ... or the ellipsis unicode character, 0x2026 if ((t == QLatin1String("...")) || (t == QChar(0x2026))) { t.clear(); } } // Label time.... Simple case: no text, so just plop down the icon right in the center // We only do this when the button never draws the text, to avoid jumps in icon position // when resizing if (!shouldDrawText()) { style()->drawItemPixmap(&painter, rect(), Qt::AlignCenter | Qt::AlignVCenter, icon); return; } // Now where the icon/text goes depends on text direction and tab position QRect iconArea; QRect labelArea; bool bottomIcon = false; bool rtl = layoutDirection() == Qt::RightToLeft; if (isVertical()) { if ((d->position == Qt::LeftEdge) && !rtl) { bottomIcon = true; } - + if ((d->position == Qt::RightEdge) && rtl) - { + { bottomIcon = true; } } if (isVertical()) { if (bottomIcon) { labelArea = QRect(0, vMargin, width(), textRoom); iconArea = QRect(0, vMargin + textRoom, width(), iconRoom); } else { labelArea = QRect(0, iconRoom, width(), textRoom); iconArea = QRect(0, 0, width(), iconRoom); } } else { // Pretty simple --- depends only on RTL/LTR if (rtl) { labelArea = QRect(hMargin, 0, textRoom, height()); iconArea = QRect(hMargin + textRoom, 0, iconRoom, height()); } else { labelArea = QRect(iconRoom, 0, textRoom, height()); iconArea = QRect(0, 0, iconRoom, height()); } } style()->drawItemPixmap(&painter, iconArea, Qt::AlignCenter | Qt::AlignVCenter, icon); if (t.isEmpty()) { return; } QRect labelPaintArea = labelArea; if (isVertical()) { // If we're vertical, we paint to a simple 0,0 origin rect, // and get the transformations to get us in the right place labelPaintArea = QRect(0, 0, labelArea.height(), labelArea.width()); QTransform tr; if (bottomIcon) { tr.translate(labelArea.x(), labelPaintArea.width() + labelArea.y()); tr.rotate(-90); } else { tr.translate(labelPaintArea.height() + labelArea.x(), labelArea.y()); tr.rotate(90); } painter.setTransform(tr); } style()->drawItemText(&painter, labelPaintArea, Qt::AlignLeading | Qt::AlignVCenter, palette(), true, t, QPalette::ButtonText); } // ------------------------------------------------------------------------------------- class Q_DECL_HIDDEN DMultiTabBar::Private { public: explicit Private() : internal(nullptr), layout(nullptr), btnTabSep(nullptr), position(Qt::LeftEdge) { } DMultiTabBarFrame* internal; QBoxLayout* layout; QFrame* btnTabSep; QList buttons; Qt::Edge position; }; DMultiTabBar::DMultiTabBar(Qt::Edge pos, QWidget* const parent) : QWidget(parent), d(new Private) { if ((pos == Qt::LeftEdge) || (pos == Qt::RightEdge)) { d->layout = new QVBoxLayout(this); setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); } else { d->layout = new QHBoxLayout(this); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); } d->layout->setContentsMargins(QMargins()); d->layout->setSpacing(0); d->internal = new DMultiTabBarFrame(this, pos); setPosition(pos); setStyle(ActiveIconText); d->layout->insertWidget(0, d->internal); d->layout->insertWidget(0, d->btnTabSep = new QFrame(this)); d->btnTabSep->setFixedHeight(4); d->btnTabSep->setFrameStyle(QFrame::Panel | QFrame::Sunken); d->btnTabSep->setLineWidth(2); d->btnTabSep->hide(); updateGeometry(); } DMultiTabBar::~DMultiTabBar() { qDeleteAll(d->buttons); d->buttons.clear(); delete d; } int DMultiTabBar::appendButton(const QPixmap &pic, int id, QMenu *popup, const QString&) { DMultiTabBarButton* const btn = new DMultiTabBarButton(pic, QString(), id, this); // a button with a QMenu can have another size. Make sure the button has always the same size. btn->setFixedWidth(btn->height()); btn->setMenu(popup); d->buttons.append(btn); d->layout->insertWidget(0,btn); btn->show(); d->btnTabSep->show(); return 0; } void DMultiTabBar::updateSeparator() { bool hideSep = true; QListIterator it(d->buttons); while (it.hasNext()) { if (it.next()->isVisibleTo(this)) { hideSep = false; break; } } if (hideSep) { d->btnTabSep->hide(); } else { d->btnTabSep->show(); } } int DMultiTabBar::appendTab(const QPixmap& pic, int id, const QString& text) { d->internal->appendTab(pic,id,text); return 0; } DMultiTabBarButton* DMultiTabBar::button(int id) const { QListIterator it(d->buttons); while (it.hasNext()) { DMultiTabBarButton* const button = it.next(); if (button->id() == id) { return button; } } return nullptr; } DMultiTabBarTab* DMultiTabBar::tab(int id) const { return d->internal->tab(id); } void DMultiTabBar::removeButton(int id) { for (int pos = 0 ; pos < d->buttons.count() ; ++pos) { if (d->buttons.at(pos)->id() == id) { d->buttons.takeAt(pos)->deleteLater(); break; } } if (d->buttons.count() == 0) { d->btnTabSep->hide(); } } void DMultiTabBar::removeTab(int id) { d->internal->removeTab(id); } void DMultiTabBar::setTab(int id,bool state) { DMultiTabBarTab* const ttab = tab(id); if (ttab) { ttab->setState(state); } } bool DMultiTabBar::isTabRaised(int id) const { DMultiTabBarTab* const ttab = tab(id); if (ttab) { return ttab->isChecked(); } return false; } void DMultiTabBar::setStyle(TextStyle style) { d->internal->setStyle(style); } DMultiTabBar::TextStyle DMultiTabBar::tabStyle() const { return d->internal->d->style; } void DMultiTabBar::setPosition(Qt::Edge pos) { d->position = pos; d->internal->setPosition(pos); } Qt::Edge DMultiTabBar::position() const { return d->position; } void DMultiTabBar::fontChange(const QFont&) { updateGeometry(); } // ------------------------------------------------------------------------------------- class Q_DECL_HIDDEN SidebarState { public: SidebarState() : activeWidget(nullptr), size(0) { } SidebarState(QWidget* const w, int size) : activeWidget(w), size(size) { } QWidget* activeWidget; int size; }; // ------------------------------------------------------------------------------------- class Q_DECL_HIDDEN Sidebar::Private { public: explicit Private() : minimizedDefault(false), minimized(false), isMinimized(false), tabs(0), activeTab(-1), dragSwitchId(-1), restoreSize(0), stack(nullptr), splitter(nullptr), dragSwitchTimer(nullptr), appendedTabsStateCache(), optionActiveTabEntry(QLatin1String("ActiveTab")), optionMinimizedEntry(QLatin1String("Minimized")), optionRestoreSizeEntry(QLatin1String("RestoreSize")) { } bool minimizedDefault; bool minimized; /** * Backup of shrinked status before backup(), restored by restore() * NOTE: when sidebar is hidden, only icon bar is affected. If sidebar view is * visible, this one must be shrink and restored accordingly. */ bool isMinimized; int tabs; int activeTab; int dragSwitchId; int restoreSize; QStackedWidget* stack; SidebarSplitter* splitter; QTimer* dragSwitchTimer; QHash appendedTabsStateCache; const QString optionActiveTabEntry; const QString optionMinimizedEntry; const QString optionRestoreSizeEntry; }; class Q_DECL_HIDDEN SidebarSplitter::Private { public: QList sidebars; }; // ------------------------------------------------------------------------------------- Sidebar::Sidebar(QWidget* const parent, SidebarSplitter* const sp, Qt::Edge side, bool minimizedDefault) : DMultiTabBar(side, parent), StateSavingObject(this), d(new Private) { d->splitter = sp; d->minimizedDefault = minimizedDefault; d->stack = new QStackedWidget(d->splitter); d->dragSwitchTimer = new QTimer(this); connect(d->dragSwitchTimer, SIGNAL(timeout()), this, SLOT(slotDragSwitchTimer())); d->splitter->d->sidebars << this; setStyle(DMultiTabBar::ActiveIconText); } Sidebar::~Sidebar() { saveState(); if (d->splitter) { d->splitter->d->sidebars.removeAll(this); } delete d; } SidebarSplitter* Sidebar::splitter() const { return d->splitter; } void Sidebar::doLoadState() { KConfigGroup group = getConfigGroup(); int tab = group.readEntry(entryName(d->optionActiveTabEntry), 0); bool minimized = group.readEntry(entryName(d->optionMinimizedEntry), d->minimizedDefault); d->restoreSize = group.readEntry(entryName(d->optionRestoreSizeEntry), -1); // validate if ((tab >= d->tabs) || (tab < 0)) { tab = 0; } if (minimized) { d->activeTab = tab; setTab(d->activeTab, false); d->stack->setCurrentIndex(d->activeTab); shrink(); emit signalChangedTab(d->stack->currentWidget()); return; } d->activeTab = -1; clicked(tab); } void Sidebar::doSaveState() { KConfigGroup group = getConfigGroup(); group.writeEntry(entryName(d->optionActiveTabEntry), d->activeTab); group.writeEntry(entryName(d->optionMinimizedEntry), d->minimized); group.writeEntry(entryName(d->optionRestoreSizeEntry), d->minimized ? d->restoreSize : -1); } void Sidebar::backup() { // backup preview state of sidebar view (shrink or not) d->isMinimized = d->minimized; // In all case, shrink sidebar view shrink(); DMultiTabBar::hide(); } void Sidebar::backup(const QList thirdWidgetsToBackup, QList* const sizes) { sizes->clear(); foreach (QWidget* const widget, thirdWidgetsToBackup) { *sizes << d->splitter->size(widget); } backup(); } void Sidebar::restore() { DMultiTabBar::show(); // restore preview state of sidebar view, stored in backup() if (!d->isMinimized) { expand(); } } void Sidebar::restore(const QList thirdWidgetsToRestore, const QList& sizes) { restore(); if (thirdWidgetsToRestore.size() == sizes.size()) { for (int i = 0 ; i < thirdWidgetsToRestore.size() ; ++i) { d->splitter->setSize(thirdWidgetsToRestore.at(i), sizes.at(i)); } } } void Sidebar::appendTab(QWidget* const w, const QIcon& pic, const QString& title) { // Store state (but not on initialization) if (isVisible()) { d->appendedTabsStateCache[w] = SidebarState(d->stack->currentWidget(), d->splitter->size(this)); } // Add tab w->setParent(d->stack); DMultiTabBar::appendTab(pic.pixmap(style()->pixelMetric(QStyle::PM_SmallIconSize)), d->tabs, title); d->stack->insertWidget(d->tabs, w); tab(d->tabs)->setAcceptDrops(true); tab(d->tabs)->installEventFilter(this); connect(tab(d->tabs), SIGNAL(clicked(int)), this, SLOT(clicked(int))); d->tabs++; } void Sidebar::deleteTab(QWidget* const w) { int tab = d->stack->indexOf(w); if (tab < 0) { return; } bool removingActiveTab = (tab == d->activeTab); if (removingActiveTab) { d->activeTab = -1; } d->stack->removeWidget(d->stack->widget(tab)); // delete widget removeTab(tab); d->tabs--; // restore or reset active tab and width if (!d->minimized) { // restore to state before adding tab // using a hash is simple, but does not handle well multiple add/removal operations at a time SidebarState state = d->appendedTabsStateCache.take(w); if (state.activeWidget) { int atab = d->stack->indexOf(state.activeWidget); if (atab != -1) { switchTabAndStackToTab(atab); emit signalChangedTab(d->stack->currentWidget()); if (state.size == 0) { d->minimized = true; setTab(d->activeTab, false); } d->splitter->setSize(this, state.size); } } else { if (removingActiveTab) { clicked(d->tabs - 1); } d->splitter->setSize(this, -1); } } else { d->restoreSize = -1; } } void Sidebar::clicked(int tab) { if ((tab >= d->tabs) || (tab < 0)) { return; } if (tab == d->activeTab) { d->stack->isHidden() ? expand() : shrink(); } else { switchTabAndStackToTab(tab); if (d->minimized) { expand(); } emit signalChangedTab(d->stack->currentWidget()); } } void Sidebar::setActiveTab(QWidget* const w) { int tab = d->stack->indexOf(w); if (tab < 0) { return; } switchTabAndStackToTab(tab); if (d->minimized) { expand(); } emit signalChangedTab(d->stack->currentWidget()); } void Sidebar::activePreviousTab() { int tab = d->stack->indexOf(d->stack->currentWidget()); if (tab == 0) { tab = d->tabs-1; } else { tab--; } setActiveTab(d->stack->widget(tab)); } void Sidebar::activeNextTab() { int tab = d->stack->indexOf(d->stack->currentWidget()); if (tab == d->tabs-1) { tab = 0; } else { tab++; } setActiveTab(d->stack->widget(tab)); } void Sidebar::switchTabAndStackToTab(int tab) { if (d->activeTab >= 0) { setTab(d->activeTab, false); } d->activeTab = tab; setTab(d->activeTab, true); d->stack->setCurrentIndex(d->activeTab); } QWidget* Sidebar::getActiveTab() const { if (d->splitter) { return d->stack->currentWidget(); } else { return nullptr; } } void Sidebar::shrink() { d->minimized = true; // store the size that we had. We may later need it when we restore to visible. int currentSize = d->splitter->size(this); if (currentSize) { d->restoreSize = currentSize; } d->stack->hide(); emit signalViewChanged(); } void Sidebar::expand() { d->minimized = false; d->stack->show(); QTimer::singleShot(0, this, SLOT(slotExpandTimer())); } void Sidebar::slotExpandTimer() { // Do not expand to size 0 (only splitter handle visible) // but either to previous size, or the minimum size hint if (d->splitter->size(this) == 0) { setTab(d->activeTab, true); d->splitter->setSize(this, d->restoreSize ? d->restoreSize : -1); } emit signalViewChanged(); } bool Sidebar::isExpanded() const { return !d->minimized; } bool Sidebar::eventFilter(QObject* obj, QEvent* ev) { for (int i = 0 ; i < d->tabs ; ++i) { if (obj == tab(i)) { if (ev->type() == QEvent::DragEnter) { QDragEnterEvent* const e = static_cast(ev); enterEvent(e); e->accept(); return false; } else if (ev->type() == QEvent::DragMove) { if (!d->dragSwitchTimer->isActive()) { d->dragSwitchTimer->setSingleShot(true); d->dragSwitchTimer->start(800); d->dragSwitchId = i; } return false; } else if (ev->type() == QEvent::DragLeave) { d->dragSwitchTimer->stop(); QDragLeaveEvent* const e = static_cast(ev); leaveEvent(e); return false; } else if (ev->type() == QEvent::Drop) { d->dragSwitchTimer->stop(); QDropEvent* const e = static_cast(ev); leaveEvent(e); return false; } else { return false; } } } // Else, pass the event on to the parent class return DMultiTabBar::eventFilter(obj, ev); } void Sidebar::slotDragSwitchTimer() { clicked(d->dragSwitchId); } void Sidebar::slotSplitterBtnClicked() { clicked(d->activeTab); } // ----------------------------------------------------------------------------- const QString SidebarSplitter::DEFAULT_CONFIG_KEY = QLatin1String("SplitterState"); SidebarSplitter::SidebarSplitter(QWidget* const parent) : QSplitter(parent), d(new Private) { connect(this, SIGNAL(splitterMoved(int,int)), this, SLOT(slotSplitterMoved(int,int))); } SidebarSplitter::SidebarSplitter(Qt::Orientation orientation, QWidget* const parent) : QSplitter(orientation, parent), d(new Private) { connect(this, SIGNAL(splitterMoved(int,int)), this, SLOT(slotSplitterMoved(int,int))); } SidebarSplitter::~SidebarSplitter() { // retreat cautiously from sidebars that live longer foreach (Sidebar* const sidebar, d->sidebars) { sidebar->d->splitter = nullptr; } delete d; } void SidebarSplitter::restoreState(KConfigGroup& group) { restoreState(group, DEFAULT_CONFIG_KEY); } void SidebarSplitter::restoreState(KConfigGroup& group, const QString& key) { if (group.hasKey(key)) { QByteArray state; state = group.readEntry(key, state); QSplitter::restoreState(QByteArray::fromBase64(state)); } } void SidebarSplitter::saveState(KConfigGroup& group) { saveState(group, DEFAULT_CONFIG_KEY); } void SidebarSplitter::saveState(KConfigGroup& group, const QString& key) { group.writeEntry(key, QSplitter::saveState().toBase64()); } int SidebarSplitter::size(Sidebar* const bar) const { return size(bar->d->stack); } int SidebarSplitter::size(QWidget* const widget) const { int index = indexOf(widget); if (index == -1) { return -1; } return sizes().at(index); } void SidebarSplitter::setSize(Sidebar* const bar, int size) { setSize(bar->d->stack, size); } void SidebarSplitter::setSize(QWidget* const widget, int size) { int index = indexOf(widget); if (index == -1) { return; } // special case: Use minimum size hint if (size == -1) { if (orientation() == Qt::Horizontal) { size = widget->minimumSizeHint().width(); } if (orientation() == Qt::Vertical) { size = widget->minimumSizeHint().height(); } } QList sizeList = sizes(); sizeList[index] = size; setSizes(sizeList); } void SidebarSplitter::slotSplitterMoved(int pos, int index) { Q_UNUSED(pos); // When the user moves the splitter so that size is 0 (collapsed), // it may be difficult to restore the sidebar as clicking the buttons // has no effect (only hides/shows the splitter handle) // So we want to transform this size-0-sidebar // to a sidebar that is shrunk (d->stack hidden) // and can be restored by clicking a tab bar button // We need to look at the widget between index-1 and index // and the one between index and index+1 QList sizeList = sizes(); // Is there a sidebar with size 0 before index ? if ((index > 0) && (sizeList.at(index-1) == 0)) { QWidget* const w = widget(index-1); foreach (Sidebar* const sidebar, d->sidebars) { if (w == sidebar->d->stack) { if (!sidebar->d->minimized) { sidebar->setTab(sidebar->d->activeTab, false); sidebar->shrink(); } break; } } } // Is there a sidebar with size 0 after index ? if (sizeList.at(index) == 0) { QWidget* const w = widget(index); foreach (Sidebar* const sidebar, d->sidebars) { if (w == sidebar->d->stack) { if (!sidebar->d->minimized) { sidebar->setTab(sidebar->d->activeTab, false); sidebar->shrink(); } break; } } } } } // namespace Digikam