diff --git a/DB/ImageSearchInfo.cpp b/DB/ImageSearchInfo.cpp index fcdb0bcd..e0302cb8 100644 --- a/DB/ImageSearchInfo.cpp +++ b/DB/ImageSearchInfo.cpp @@ -1,629 +1,627 @@ /* Copyright (C) 2003-2020 The KPhotoAlbum Development Team 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 of the License, 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. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "ImageSearchInfo.h" #include "AndCategoryMatcher.h" #include "CategoryMatcher.h" #include "ContainerCategoryMatcher.h" #include "ExactCategoryMatcher.h" #include "ImageDB.h" #include "Logging.h" #include "NegationCategoryMatcher.h" #include "NoTagCategoryMatcher.h" #include "OrCategoryMatcher.h" #include "ValueCategoryMatcher.h" #include #include #include #include #include #include #include using namespace DB; static QAtomicInt s_matchGeneration; static int nextGeneration() { return s_matchGeneration++; } ImageSearchInfo::ImageSearchInfo() : m_matchGeneration(nextGeneration()) { } ImageSearchInfo::ImageSearchInfo(const ImageDate &date, const QString &label, const QString &description) : m_date(date) , m_label(label) , m_description(description) , m_isNull(false) , m_matchGeneration(nextGeneration()) { } ImageSearchInfo::ImageSearchInfo(const ImageDate &date, const QString &label, const QString &description, const QString &fnPattern) : m_date(date) , m_label(label) , m_description(description) , m_fnPattern(fnPattern) , m_isNull(false) , m_matchGeneration(nextGeneration()) { } QString ImageSearchInfo::label() const { return m_label; } QRegExp ImageSearchInfo::fnPattern() const { return m_fnPattern; } QString ImageSearchInfo::description() const { return m_description; } void ImageSearchInfo::checkIfNull() { if (m_compiled.valid || isNull()) return; if (m_date.isNull() && m_label.isEmpty() && m_description.isEmpty() && m_rating == -1 && m_megapixel == 0 && m_exifSearchInfo.isNull() && m_categoryMatchText.isEmpty() #ifdef HAVE_KGEOMAP && !m_regionSelection.first.hasCoordinates() && !m_regionSelection.second.hasCoordinates() #endif ) { m_isNull = true; } } bool ImageSearchInfo::isNull() const { return m_isNull; } bool ImageSearchInfo::isCacheable() const { return m_isCacheable; } void ImageSearchInfo::setCacheable(bool cacheable) { m_isCacheable = cacheable; } bool ImageSearchInfo::match(ImageInfoPtr info) const { if (m_isNull) return true; if (m_isCacheable && info->matchGeneration() == m_matchGeneration) return info->isMatched(); bool ok = doMatch(info); if (m_isCacheable) { info->setMatchGeneration(m_matchGeneration); info->setIsMatched(ok); } return ok; } bool ImageSearchInfo::doMatch(ImageInfoPtr info) const { if (!m_compiled.valid) compile(); // -------------------------------------------------- Rating //ok = ok && (_rating == -1 ) || ( _rating == info->rating() ); if (m_rating != -1) { switch (m_ratingSearchMode) { case 1: // Image rating at least selected if (m_rating > info->rating()) return false; break; case 2: // Image rating less than selected if (m_rating < info->rating()) return false; break; case 3: // Image rating not equal if (m_rating == info->rating()) return false; break; default: if (m_rating != info->rating()) return false; break; } } // -------------------------------------------------- Resolution if (m_megapixel && (m_megapixel * 1000000 > info->size().width() * info->size().height())) return false; if (m_max_megapixel && m_max_megapixel < m_megapixel && (m_max_megapixel * 1000000 < info->size().width() * info->size().height())) return false; // -------------------------------------------------- Date QDateTime actualStart = info->date().start(); QDateTime actualEnd = info->date().end(); if (m_date.start().isValid()) { if (actualEnd < m_date.start() || (m_date.end().isValid() && actualStart > m_date.end())) return false; } else if (m_date.end().isValid() && actualStart > m_date.end()) { return false; } // -------------------------------------------------- Label if (m_label.isEmpty() && info->label().indexOf(m_label) == -1) return false; // -------------------------------------------------- RAW if (m_searchRAW && !ImageManager::RAWImageDecoder::isRAW(info->fileName())) return false; #ifdef HAVE_MARBLE // Search for GPS Position if (!m_regionSelection.isNull()) { if (!info->coordinates().hasCoordinates()) return false; double infoLat = info->coordinates().lat(); if (infoLat < m_regionSelection.south || infoLat > m_regionSelection.north) { return false; } double infoLon = info->coordinates().lon(); // copied from Marble::GeoDataLatLonBox: if (((infoLon < m_regionSelection.west || infoLon > m_regionSelection.east) && (m_regionSelection.west < m_regionSelection.east)) || ((infoLon < m_regionSelection.west && infoLon > m_regionSelection.east) && (m_regionSelection.east > m_regionSelection.west))) { return false; } } #endif // -------------------------------------------------- File name pattern if (!m_fnPattern.isEmpty() && m_fnPattern.indexIn(info->fileName().relative()) == -1) return false; // -------------------------------------------------- Options // alreadyMatched map is used to make it possible to search for // Jesper & None QMap alreadyMatched; for (CategoryMatcher *optionMatcher : m_compiled.categoryMatchers) { if (!optionMatcher->eval(info, alreadyMatched)) return false; } // -------------------------------------------------- Text if (!m_description.isEmpty()) { const QString &txt(info->description()); const QStringList list = m_description.split(QChar::fromLatin1(' '), QString::SkipEmptyParts); for (const QString &word : list) { if (txt.indexOf(word, 0, Qt::CaseInsensitive) == -1) return false; } } // -------------------------------------------------- EXIF if (!m_exifSearchInfo.matches(info->fileName())) return false; return true; } QString ImageSearchInfo::categoryMatchText(const QString &name) const { return m_categoryMatchText[name]; } void ImageSearchInfo::setCategoryMatchText(const QString &name, const QString &value) { if (value.isEmpty()) { m_categoryMatchText.remove(name); } else { m_categoryMatchText[name] = value; } m_isNull = false; m_compiled.valid = false; m_matchGeneration = nextGeneration(); } void ImageSearchInfo::addAnd(const QString &category, const QString &value) { // Escape literal "&"s in value by doubling it QString escapedValue = value; escapedValue.replace(QString::fromUtf8("&"), QString::fromUtf8("&&")); QString val = categoryMatchText(category); if (!val.isEmpty()) val += QString::fromLatin1(" & ") + escapedValue; else val = escapedValue; setCategoryMatchText(category, val); m_isNull = false; m_compiled.valid = false; m_matchGeneration = nextGeneration(); } void ImageSearchInfo::setRating(short rating) { m_rating = rating; m_isNull = false; m_compiled.valid = false; m_matchGeneration = nextGeneration(); } void ImageSearchInfo::setMegaPixel(short megapixel) { m_megapixel = megapixel; m_matchGeneration = nextGeneration(); } void ImageSearchInfo::setMaxMegaPixel(short max_megapixel) { m_max_megapixel = max_megapixel; m_matchGeneration = nextGeneration(); } void ImageSearchInfo::setSearchMode(int index) { m_ratingSearchMode = index; m_matchGeneration = nextGeneration(); } void ImageSearchInfo::setSearchRAW(bool searchRAW) { m_searchRAW = searchRAW; m_matchGeneration = nextGeneration(); } QString ImageSearchInfo::toString() const { QString res; bool first = true; for (QMap::ConstIterator it = m_categoryMatchText.begin(); it != m_categoryMatchText.end(); ++it) { if (!it.value().isEmpty()) { if (first) first = false; else res += QString::fromLatin1(" / "); QString txt = it.value(); if (txt == ImageDB::NONE()) txt = i18nc("As in No persons, no locations etc. I do realize that translators may have problem with this, " "but I need some how to indicate the category, and users may create their own categories, so this is " "the best I can do - Jesper.", "No %1", it.key()); if (txt.contains(QString::fromLatin1("|"))) txt.replace(QString::fromLatin1("&"), QString::fromLatin1(" %1 ").arg(i18n("and"))); else txt.replace(QString::fromLatin1("&"), QString::fromLatin1(" / ")); txt.replace(QString::fromLatin1("|"), QString::fromLatin1(" %1 ").arg(i18n("or"))); txt.replace(QString::fromLatin1("!"), QString::fromLatin1(" %1 ").arg(i18n("not"))); txt.replace(ImageDB::NONE(), i18nc("As in no other persons, or no other locations. " "I do realize that translators may have problem with this, " "but I need some how to indicate the category, and users may create their own categories, so this is " "the best I can do - Jesper.", "No other %1", it.key())); res += txt.simplified(); } } return res; } void ImageSearchInfo::debug() { for (QMap::Iterator it = m_categoryMatchText.begin(); it != m_categoryMatchText.end(); ++it) { qCDebug(DBCategoryMatcherLog) << it.key() << ", " << it.value(); } } -// PENDING(blackie) move this into the Options class instead of having it here. -void ImageSearchInfo::saveLock() const +QVariantMap ImageSearchInfo::getLockData() const { - KConfigGroup config = KSharedConfig::openConfig()->group(Settings::SettingsData::instance()->groupForDatabase("Privacy Settings")); - config.writeEntry(QString::fromLatin1("label"), m_label); - config.writeEntry(QString::fromLatin1("description"), m_description); - config.writeEntry(QString::fromLatin1("categories"), m_categoryMatchText.keys()); + QVariantMap pairs; + pairs[QString::fromLatin1("label")] = m_label; + pairs[QString::fromLatin1("description")] = m_description; + pairs[QString::fromLatin1("categories")] = QVariant(m_categoryMatchText.keys()); for (QMap::ConstIterator it = m_categoryMatchText.begin(); it != m_categoryMatchText.end(); ++it) { - config.writeEntry(it.key(), it.value()); + pairs[it.key()] = it.value(); } - config.sync(); + return pairs; } -ImageSearchInfo ImageSearchInfo::loadLock() +ImageSearchInfo ImageSearchInfo::loadLock(const QMap &keyValuePairs) { - KConfigGroup config = KSharedConfig::openConfig()->group(Settings::SettingsData::instance()->groupForDatabase("Privacy Settings")); ImageSearchInfo info; - info.m_label = config.readEntry("label"); - info.m_description = config.readEntry("description"); - QStringList categories = config.readEntry(QString::fromLatin1("categories"), QStringList()); + info.m_label = keyValuePairs.value(QString::fromLatin1("label"), {}).toString(); + info.m_description = keyValuePairs.value(QString::fromLatin1("description"), {}).toString(); + QStringList categories = keyValuePairs.value(QString::fromLatin1("categories"), {}).toStringList(); for (QStringList::ConstIterator it = categories.constBegin(); it != categories.constEnd(); ++it) { - info.setCategoryMatchText(*it, config.readEntry(*it, QString())); + info.setCategoryMatchText(*it, keyValuePairs.value(*it, {}).toString()); } return info; } void ImageSearchInfo::compile() const { qCDebug(DBCategoryMatcherLog) << "Compiling search info..."; m_exifSearchInfo.search(); CompiledDataPrivate compiledData; for (QMap::ConstIterator it = m_categoryMatchText.begin(); it != m_categoryMatchText.end(); ++it) { const QString category = it.key(); const QString matchText = it.value(); const QStringList orParts = matchText.split(QString::fromLatin1("|"), QString::SkipEmptyParts); DB::ContainerCategoryMatcher *orMatcher = new DB::OrCategoryMatcher; for (QString orPart : orParts) { // Split by " & ", not only by "&", so that the doubled "&"s won't be used as a split point const QStringList andParts = orPart.split(QString::fromLatin1(" & "), QString::SkipEmptyParts); DB::ContainerCategoryMatcher *andMatcher; bool exactMatch = false; bool negate = false; andMatcher = new DB::AndCategoryMatcher; for (QString str : andParts) { static const QRegExp regexp(QString::fromLatin1("^\\s*!\\s*(.*)$")); if (regexp.exactMatch(str)) { // str is preceded with NOT negate = true; str = regexp.cap(1); } str = str.trimmed(); CategoryMatcher *valueMatcher; if (str == ImageDB::NONE()) { // mark AND-group as containing a "No other" condition exactMatch = true; continue; } else { valueMatcher = new DB::ValueCategoryMatcher(category, str); if (negate) { valueMatcher = new DB::NegationCategoryMatcher(valueMatcher); negate = false; } } andMatcher->addElement(valueMatcher); } if (exactMatch) { DB::CategoryMatcher *exactMatcher = nullptr; // if andMatcher has exactMatch set, but no CategoryMatchers, then // matching "category / None" is what we want: if (andMatcher->mp_elements.count() == 0) { exactMatcher = new DB::NoTagCategoryMatcher(category); } else { ExactCategoryMatcher *noOtherMatcher = new ExactCategoryMatcher(category); if (andMatcher->mp_elements.count() == 1) noOtherMatcher->setMatcher(andMatcher->mp_elements[0]); else noOtherMatcher->setMatcher(andMatcher); exactMatcher = noOtherMatcher; } if (negate) exactMatcher = new DB::NegationCategoryMatcher(exactMatcher); orMatcher->addElement(exactMatcher); } else if (andMatcher->mp_elements.count() == 1) orMatcher->addElement(andMatcher->mp_elements[0]); else if (andMatcher->mp_elements.count() > 1) orMatcher->addElement(andMatcher); } CategoryMatcher *matcher = nullptr; if (orMatcher->mp_elements.count() == 1) matcher = orMatcher->mp_elements[0]; else if (orMatcher->mp_elements.count() > 1) matcher = orMatcher; if (matcher) { compiledData.categoryMatchers.append(matcher); if (DBCategoryMatcherLog().isDebugEnabled()) { qCDebug(DBCategoryMatcherLog) << "Matching text '" << matchText << "' in category " << category << ":"; matcher->debug(0); qCDebug(DBCategoryMatcherLog) << "."; } } } compiledData.valid = true; std::swap(m_compiled, compiledData); } void ImageSearchInfo::debugMatcher() const { if (!m_compiled.valid) compile(); qCDebug(DBCategoryMatcherLog, "And:"); for (CategoryMatcher *optionMatcher : m_compiled.categoryMatchers) { optionMatcher->debug(1); } } QList> ImageSearchInfo::query() const { if (!m_compiled.valid) compile(); // Combine _optionMachers to one list of lists in Disjunctive // Normal Form and return it. QList::Iterator it = m_compiled.categoryMatchers.begin(); QList> result; if (it == m_compiled.categoryMatchers.end()) return result; result = convertMatcher(*it); ++it; for (; it != m_compiled.categoryMatchers.end(); ++it) { QList> current = convertMatcher(*it); QList> oldResult = result; result.clear(); for (QList resultIt : oldResult) { for (QList currentIt : current) { QList tmp; tmp += resultIt; tmp += currentIt; result.append(tmp); } } } return result; } Utilities::StringSet ImageSearchInfo::findAlreadyMatched(const QString &group) const { Utilities::StringSet result; const QString str = categoryMatchText(group); if (str.contains(QString::fromLatin1("|"))) { return result; } const QStringList list = str.split(QString::fromLatin1("&"), QString::SkipEmptyParts); for (const QString &part : list) { const QString nm = part.trimmed(); if (!nm.contains(QString::fromLatin1("!"))) result.insert(nm); } return result; } QList ImageSearchInfo::extractAndMatcher(CategoryMatcher *matcher) const { QList result; AndCategoryMatcher *andMatcher; SimpleCategoryMatcher *simpleMatcher; if ((andMatcher = dynamic_cast(matcher))) { for (CategoryMatcher *child : andMatcher->mp_elements) { SimpleCategoryMatcher *simpleMatcher = dynamic_cast(child); Q_ASSERT(simpleMatcher); result.append(simpleMatcher); } } else if ((simpleMatcher = dynamic_cast(matcher))) result.append(simpleMatcher); else Q_ASSERT(false); return result; } /** Convert matcher to Disjunctive Normal Form. * * @return OR-list of AND-lists. (e.g. OR(AND(a,b),AND(c,d))) */ QList> ImageSearchInfo::convertMatcher(CategoryMatcher *item) const { QList> result; OrCategoryMatcher *orMacther; if ((orMacther = dynamic_cast(item))) { for (CategoryMatcher *child : orMacther->mp_elements) { result.append(extractAndMatcher(child)); } } else result.append(extractAndMatcher(item)); return result; } short ImageSearchInfo::rating() const { return m_rating; } ImageDate ImageSearchInfo::date() const { return m_date; } void ImageSearchInfo::addExifSearchInfo(const Exif::SearchInfo info) { m_exifSearchInfo = info; m_isNull = false; m_matchGeneration = nextGeneration(); } void DB::ImageSearchInfo::renameCategory(const QString &oldName, const QString &newName) { m_categoryMatchText[newName] = m_categoryMatchText[oldName]; m_categoryMatchText.remove(oldName); m_compiled.valid = false; m_matchGeneration = nextGeneration(); } #ifdef HAVE_MARBLE Map::GeoCoordinates::LatLonBox ImageSearchInfo::regionSelection() const { return m_regionSelection; } void ImageSearchInfo::setRegionSelection(const Map::GeoCoordinates::LatLonBox &actRegionSelection) { m_regionSelection = actRegionSelection; m_compiled.valid = false; if (!m_regionSelection.isNull()) { m_isNull = false; } m_matchGeneration = nextGeneration(); } #endif ImageSearchInfo::CompiledDataPrivate::CompiledDataPrivate(const ImageSearchInfo::CompiledDataPrivate &) { // copying invalidates the compiled data valid = false; } ImageSearchInfo::CompiledDataPrivate::~CompiledDataPrivate() { qDeleteAll(categoryMatchers); categoryMatchers.clear(); } ImageSearchInfo::CompiledDataPrivate &ImageSearchInfo::CompiledDataPrivate::operator=(const ImageSearchInfo::CompiledDataPrivate &) { // copying invalidates the compiled data valid = false; return *this; } // vi:expandtab:tabstop=4 shiftwidth=4: diff --git a/DB/ImageSearchInfo.h b/DB/ImageSearchInfo.h index f77427dd..fe48fb68 100644 --- a/DB/ImageSearchInfo.h +++ b/DB/ImageSearchInfo.h @@ -1,159 +1,159 @@ /* Copyright (C) 2003-2020 The KPhotoAlbum Development Team 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 of the License, 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. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef IMAGESEARCHINFO_H #define IMAGESEARCHINFO_H #include "ImageDate.h" #include "ImageInfoPtr.h" #include #include #ifdef HAVE_MARBLE #include #endif #include #include #include namespace DB { class SimpleCategoryMatcher; class ImageInfo; class CategoryMatcher; class ImageSearchInfo { public: ImageSearchInfo(); ImageSearchInfo(const ImageDate &date, const QString &label, const QString &description); ImageSearchInfo(const ImageDate &date, const QString &label, const QString &description, const QString &fnPattern); ImageDate date() const; QString categoryMatchText(const QString &name) const; void setCategoryMatchText(const QString &name, const QString &value); void renameCategory(const QString &oldName, const QString &newName); QString label() const; QRegExp fnPattern() const; QString description() const; /** * @brief checkIfNull evaluates whether the filter is indeed empty and * sets isNull() to \c true if that is the case. * You only need to call this if you re-use an existing ImageSearchInfo * and set/reset search parameters. * @see ThumbnailView::toggleRatingFilter */ void checkIfNull(); bool isNull() const; bool match(ImageInfoPtr) const; QList> query() const; void addAnd(const QString &category, const QString &value); short rating() const; void setRating(short rating); /** * @brief toString generates a description of the ImageSearchInfo. * The idea is not to give a complete description, but rather something * useful for the breadcrumbs at the bottom of the main window. * @return a textual description of the ImageSearchInfo */ QString toString() const; void setMegaPixel(short megapixel); void setMaxMegaPixel(short maxmegapixel); void setSearchRAW(bool m_searchRAW); void setSearchMode(int index); - void saveLock() const; - static ImageSearchInfo loadLock(); + QVariantMap getLockData() const; + static ImageSearchInfo loadLock(const QMap &keyValuePairs); void debug(); void debugMatcher() const; Utilities::StringSet findAlreadyMatched(const QString &group) const; void addExifSearchInfo(const Exif::SearchInfo info); // By default, an ImageSearchInfo is cacheable, but only one search // is cached per image. For a search that's only going to be // performed once, don't try to cache the result. void setCacheable(bool cacheable); bool isCacheable() const; #ifdef HAVE_MARBLE Map::GeoCoordinates::LatLonBox regionSelection() const; void setRegionSelection(const Map::GeoCoordinates::LatLonBox &actRegionSelection); #endif protected: void compile() const; QList extractAndMatcher(CategoryMatcher *andMatcher) const; QList> convertMatcher(CategoryMatcher *) const; private: /** * @brief The CompiledDataPrivate struct encapsulates the non-copyable data members of the ImageSearchInfo. * Its copy constructor and copy operator invalidate the object, * This allows the ImageSearchInfo to just use the default copy/move constructors/operators. */ struct CompiledDataPrivate { CompiledDataPrivate() = default; CompiledDataPrivate(const CompiledDataPrivate &other); CompiledDataPrivate(CompiledDataPrivate &&other) = default; ~CompiledDataPrivate(); CompiledDataPrivate &operator=(const CompiledDataPrivate &other); CompiledDataPrivate &operator=(CompiledDataPrivate &&other) = default; bool valid = false; QList categoryMatchers; }; ImageDate m_date; QMap m_categoryMatchText; QString m_label; QString m_description; QRegExp m_fnPattern; short m_rating = -1; short m_megapixel = 0; short m_max_megapixel = 0; int m_ratingSearchMode = 0; bool m_searchRAW = false; bool m_isNull = true; bool m_isCacheable = true; mutable CompiledDataPrivate m_compiled; Exif::SearchInfo m_exifSearchInfo; int m_matchGeneration; bool doMatch(ImageInfoPtr) const; #ifdef HAVE_MARBLE Map::GeoCoordinates::LatLonBox m_regionSelection; #endif // When adding new instance variable, please notice that this class has an explicit written copy constructor. }; } #endif /* IMAGESEARCHINFO_H */ // vi:expandtab:tabstop=4 shiftwidth=4: diff --git a/Settings/SettingsData.cpp b/Settings/SettingsData.cpp index c9941180..9d035dc8 100644 --- a/Settings/SettingsData.cpp +++ b/Settings/SettingsData.cpp @@ -1,594 +1,608 @@ /* Copyright (C) 2003-2020 The KPhotoAlbum Development Team 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 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e. V. (or its successor approved by the membership of KDE e. V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "SettingsData.h" #include #include #include #include #include #include #define STR(x) QString::fromLatin1(x) -#define value(GROUP, OPTION, DEFAULT) \ +#define cfgValue(GROUP, OPTION, DEFAULT) \ KSharedConfig::openConfig()->group(GROUP).readEntry(OPTION, DEFAULT) #define setValue(GROUP, OPTION, VALUE) \ { \ KConfigGroup group = KSharedConfig::openConfig()->group(GROUP); \ group.writeEntry(OPTION, VALUE); \ group.sync(); \ } #define getValueFunc_(TYPE, FUNC, GROUP, OPTION, DEFAULT) \ TYPE SettingsData::FUNC() const \ { \ - return (TYPE)value(GROUP, OPTION, DEFAULT); \ + return (TYPE)cfgValue(GROUP, OPTION, DEFAULT); \ } #define setValueFunc_(FUNC, TYPE, GROUP, OPTION, VALUE) \ void SettingsData::FUNC(const TYPE v) \ { \ setValue(GROUP, OPTION, VALUE); \ } #define getValueFunc(TYPE, FUNC, GROUP, DEFAULT) getValueFunc_(TYPE, FUNC, #GROUP, #FUNC, DEFAULT) #define setValueFunc(FUNC, TYPE, GROUP, OPTION) setValueFunc_(FUNC, TYPE, #GROUP, #OPTION, v) // TODO(mfwitten): document parameters. #define property_(GET_TYPE, GET_FUNC, GET_VALUE, SET_FUNC, SET_TYPE, SET_VALUE, GROUP, OPTION, GET_DEFAULT_1, GET_DEFAULT_2, GET_DEFAULT_2_TYPE) \ GET_TYPE SettingsData::GET_FUNC() const \ { \ KConfigGroup g = KSharedConfig::openConfig()->group(GROUP); \ \ if (!g.hasKey(OPTION)) \ return GET_DEFAULT_1; \ \ GET_DEFAULT_2_TYPE v = g.readEntry(OPTION, (GET_DEFAULT_2_TYPE)GET_DEFAULT_2); \ return (GET_TYPE)GET_VALUE; \ } \ setValueFunc_(SET_FUNC, SET_TYPE, GROUP, OPTION, SET_VALUE) #define property(GET_TYPE, GET_FUNC, SET_FUNC, SET_TYPE, SET_VALUE, GROUP, OPTION, GET_DEFAULT) \ getValueFunc_(GET_TYPE, GET_FUNC, GROUP, OPTION, GET_DEFAULT) \ setValueFunc_(SET_FUNC, SET_TYPE, GROUP, OPTION, SET_VALUE) #define property_copy(GET_FUNC, SET_FUNC, TYPE, GROUP, GET_DEFAULT) \ property(TYPE, GET_FUNC, SET_FUNC, TYPE, v, #GROUP, #GET_FUNC, GET_DEFAULT) #define property_ref_(GET_FUNC, SET_FUNC, TYPE, GROUP, GET_DEFAULT) \ property(TYPE, GET_FUNC, SET_FUNC, TYPE &, v, GROUP, #GET_FUNC, GET_DEFAULT) #define property_ref(GET_FUNC, SET_FUNC, TYPE, GROUP, GET_DEFAULT) \ property(TYPE, GET_FUNC, SET_FUNC, TYPE &, v, #GROUP, #GET_FUNC, GET_DEFAULT) #define property_enum(GET_FUNC, SET_FUNC, TYPE, GROUP, GET_DEFAULT) \ property(TYPE, GET_FUNC, SET_FUNC, TYPE, (int)v, #GROUP, #GET_FUNC, (int)GET_DEFAULT) #define property_sset(GET_FUNC, SET_FUNC, GROUP, GET_DEFAULT) \ property_(StringSet, GET_FUNC, v.toSet(), SET_FUNC, StringSet &, v.toList(), #GROUP, #GET_FUNC, GET_DEFAULT, QStringList(), QStringList) /** * smoothScale() is called from the image loading thread, therefore we need * to cache it this way, rather than going to KConfig. */ static bool _smoothScale = true; using namespace Settings; const WindowType Settings::MainWindow = "MainWindow"; const WindowType Settings::AnnotationDialog = "AnnotationDialog"; SettingsData *SettingsData::s_instance = nullptr; SettingsData *SettingsData::instance() { if (!s_instance) qFatal("instance called before loading a setup!"); return s_instance; } bool SettingsData::ready() { return s_instance; } void SettingsData::setup(const QString &imageDirectory, DB::UIDelegate &delegate) { if (!s_instance) s_instance = new SettingsData(imageDirectory, delegate); } SettingsData::SettingsData(const QString &imageDirectory, DB::UIDelegate &delegate) : m_UI(delegate) { m_hasAskedAboutTimeStamps = false; QString s = STR("/"); m_imageDirectory = imageDirectory.endsWith(s) ? imageDirectory : imageDirectory + s; - _smoothScale = value("Viewer", "smoothScale", true); + _smoothScale = cfgValue("Viewer", "smoothScale", true); // Split the list of Exif comments that should be stripped automatically to a list - QStringList commentsToStrip = value("General", "commentsToStrip", QString::fromLatin1("Exif_JPEG_PICTURE-,-OLYMPUS DIGITAL CAMERA-,-JENOPTIK DIGITAL CAMERA-,-")).split(QString::fromLatin1("-,-"), QString::SkipEmptyParts); + QStringList commentsToStrip = cfgValue("General", "commentsToStrip", QString::fromLatin1("Exif_JPEG_PICTURE-,-OLYMPUS DIGITAL CAMERA-,-JENOPTIK DIGITAL CAMERA-,-")).split(QString::fromLatin1("-,-"), QString::SkipEmptyParts); for (QString &comment : commentsToStrip) comment.replace(QString::fromLatin1(",,"), QString::fromLatin1(",")); m_EXIFCommentsToStrip = commentsToStrip; } ///////////////// //// General //// ///////////////// // clang-format off property_copy(useEXIFRotate, setUseEXIFRotate, bool, General, true) property_copy(useEXIFComments, setUseEXIFComments, bool, General, true) property_copy(stripEXIFComments, setStripEXIFComments, bool, General, true) property_copy(commentsToStrip, setCommentsToStrip, QString, General, "" /* see constructor */) property_copy(searchForImagesOnStart, setSearchForImagesOnStart, bool, General, true) property_copy(ignoreFileExtension, setIgnoreFileExtension, bool, General, false) property_copy(skipSymlinks, setSkipSymlinks, bool, General, false) property_copy(skipRawIfOtherMatches, setSkipRawIfOtherMatches, bool, General, false) property_copy(useRawThumbnail, setUseRawThumbnail, bool, General, true) property_copy(useRawThumbnailSize, setUseRawThumbnailSize, QSize, General, QSize(1024, 768)) property_copy(useCompressedIndexXML, setUseCompressedIndexXML, bool, General, true) property_copy(compressBackup, setCompressBackup, bool, General, true) property_copy(showSplashScreen, setShowSplashScreen, bool, General, true) property_copy(showHistogram, setShowHistogram, bool, General, true) property_copy(autoSave, setAutoSave, int, General, 5) property_copy(backupCount, setBackupCount, int, General, 5) property_enum(tTimeStamps, setTTimeStamps, TimeStampTrust, General, Always) property_copy(excludeDirectories, setExcludeDirectories, QString, General, QString::fromLatin1("xml,ThumbNails,.thumbs")) #ifdef KPA_ENABLE_REMOTECONTROL property_copy(recentAndroidAddress, setRecentAndroidAddress, QString, General, QString()) property_copy(listenForAndroidDevicesOnStartup, setListenForAndroidDevicesOnStartup, bool, General, false) #endif getValueFunc(QString, colorScheme, General, QString()) void SettingsData::setColorScheme(const QString &path) { if (path != colorScheme()) { setValue("General", "colorScheme", path); emit colorSchemeChanged(); } } getValueFunc(QSize, histogramSize, General, QSize(15, 30)) getValueFunc(ViewSortType, viewSortType, General, (int)SortLastUse) getValueFunc(AnnotationDialog::MatchType, matchType, General, (int)AnnotationDialog::MatchFromWordStart) getValueFunc(bool, histogramUseLinearScale, General, false) // clang-format on void SettingsData::setHistogramUseLinearScale(const bool useLinearScale) { if (useLinearScale == histogramUseLinearScale()) return; setValue("General", "histogramUseLinearScale", useLinearScale); emit histogramScaleChanged(); } void SettingsData::setHistogramSize(const QSize &size) { if (size == histogramSize()) return; setValue("General", "histogramSize", size); emit histogramSizeChanged(size); } void SettingsData::setViewSortType(const ViewSortType tp) { if (tp == viewSortType()) return; setValue("General", "viewSortType", (int)tp); emit viewSortTypeChanged(tp); } void SettingsData::setMatchType(const AnnotationDialog::MatchType mt) { if (mt == matchType()) return; setValue("General", "matchType", (int)mt); emit matchTypeChanged(mt); } bool SettingsData::trustTimeStamps() { if (tTimeStamps() == Always) return true; else if (tTimeStamps() == Never) return false; else { if (!m_hasAskedAboutTimeStamps) { const QString txt = i18n("When reading time information of images, their Exif info is used. " "Exif info may, however, not be supported by your KPhotoAlbum installation, " "or no valid information may be in the file. " "As a backup, KPhotoAlbum may use the timestamp of the image - this may, " "however, not be valid in case the image is scanned in. " "So the question is, should KPhotoAlbum trust the time stamp on your images?"); const QString logMsg = QString::fromUtf8("Trust timestamps for this session?"); auto answer = uiDelegate().questionYesNo(logMsg, txt, i18n("Trust Time Stamps?")); if (answer == DB::UserFeedback::Confirm) m_trustTimeStamps = true; else m_trustTimeStamps = false; m_hasAskedAboutTimeStamps = true; } return m_trustTimeStamps; } } //////////////////////////////// //// File Version Detection //// //////////////////////////////// // clang-format off property_copy(detectModifiedFiles, setDetectModifiedFiles, bool, FileVersionDetection, true) property_copy(modifiedFileComponent, setModifiedFileComponent, QString, FileVersionDetection, "^(.*)-edited.([^.]+)$") property_copy(originalFileComponent, setOriginalFileComponent, QString, FileVersionDetection, "\\1.\\2") property_copy(moveOriginalContents, setMoveOriginalContents, bool, FileVersionDetection, false) property_copy(autoStackNewFiles, setAutoStackNewFiles, bool, FileVersionDetection, true) property_copy(copyFileComponent, setCopyFileComponent, QString, FileVersionDetection, "(.[^.]+)$") property_copy(copyFileReplacementComponent, setCopyFileReplacementComponent, QString, FileVersionDetection, "-edited\\1") property_copy(loadOptimizationPreset, setLoadOptimizationPreset, int, FileVersionDetection, 0) property_copy(overlapLoadMD5, setOverlapLoadMD5, bool, FileVersionDetection, false) property_copy(preloadThreadCount, setPreloadThreadCount, int, FileVersionDetection, 1) property_copy(thumbnailPreloadThreadCount, setThumbnailPreloadThreadCount, int, FileVersionDetection, 1) property_copy(thumbnailBuilderThreadCount, setThumbnailBuilderThreadCount, int, FileVersionDetection, 0) // clang-format on //////////////////// //// Thumbnails //// //////////////////// // clang-format off property_copy(displayLabels, setDisplayLabels, bool, Thumbnails, true) property_copy(displayCategories, setDisplayCategories, bool, Thumbnails, false) property_copy(autoShowThumbnailView, setAutoShowThumbnailView, int, Thumbnails, 20) property_copy(showNewestThumbnailFirst, setShowNewestFirst, bool, Thumbnails, false) property_copy(thumbnailDisplayGrid, setThumbnailDisplayGrid, bool, Thumbnails, false) property_copy(previewSize, setPreviewSize, int, Thumbnails, 256) property_copy(thumbnailSpace, setThumbnailSpace, int, Thumbnails, 4) // not available via GUI, but should be consistent (and maybe confgurable for powerusers): property_copy(minimumThumbnailSize, setMinimumThumbnailSize, int, Thumbnails, 32) property_copy(maximumThumbnailSize, setMaximumThumbnailSize, int, Thumbnails, 4096) property_enum(thumbnailAspectRatio, setThumbnailAspectRatio, ThumbnailAspectRatio, Thumbnails, Aspect_3_2) property_copy(incrementalThumbnails, setIncrementalThumbnails, bool, Thumbnails, true) // database specific so that changing it doesn't invalidate the thumbnail cache for other databases: getValueFunc_(int, thumbnailSize, groupForDatabase("Thumbnails"), "thumbSize", 256) // clang-format on void SettingsData::setThumbnailSize(int value) { // enforce limits: value = qBound(minimumThumbnailSize(), value, maximumThumbnailSize()); if (value != thumbnailSize()) emit thumbnailSizeChanged(value); setValue(groupForDatabase("Thumbnails"), "thumbSize", value); } int SettingsData::actualThumbnailSize() const { // this is database specific since it's a derived value of thumbnailSize - int retval = value(groupForDatabase("Thumbnails"), "actualThumbSize", 0); + int retval = cfgValue(groupForDatabase("Thumbnails"), "actualThumbSize", 0); // if no value has been set, use thumbnailSize if (retval == 0) retval = thumbnailSize(); return retval; } void SettingsData::setActualThumbnailSize(int value) { // enforce limits: value = qBound(minimumThumbnailSize(), value, thumbnailSize()); if (value != actualThumbnailSize()) { setValue(groupForDatabase("Thumbnails"), "actualThumbSize", value); emit actualThumbnailSizeChanged(value); } } //////////////// //// Viewer //// //////////////// // clang-format off property_ref(viewerSize, setViewerSize, QSize, Viewer, QSize(1024, 768)) property_ref(slideShowSize, setSlideShowSize, QSize, Viewer, QSize(1024, 768)) property_copy(launchViewerFullScreen, setLaunchViewerFullScreen, bool, Viewer, false) property_copy(launchSlideShowFullScreen, setLaunchSlideShowFullScreen, bool, Viewer, true) property_copy(showInfoBox, setShowInfoBox, bool, Viewer, true) property_copy(showLabel, setShowLabel, bool, Viewer, true) property_copy(showDescription, setShowDescription, bool, Viewer, true) property_copy(showDate, setShowDate, bool, Viewer, true) property_copy(showImageSize, setShowImageSize, bool, Viewer, true) property_copy(showRating, setShowRating, bool, Viewer, true) property_copy(showTime, setShowTime, bool, Viewer, true) property_copy(showFilename, setShowFilename, bool, Viewer, false) property_copy(showEXIF, setShowEXIF, bool, Viewer, true) property_copy(slideShowInterval, setSlideShowInterval, int, Viewer, 5) property_copy(viewerCacheSize, setViewerCacheSize, int, Viewer, 195) property_copy(infoBoxWidth, setInfoBoxWidth, int, Viewer, 400) property_copy(infoBoxHeight, setInfoBoxHeight, int, Viewer, 300) property_enum(infoBoxPosition, setInfoBoxPosition, Position, Viewer, Bottom) property_enum(viewerStandardSize, setViewerStandardSize, StandardViewSize, Viewer, FullSize) // clang-format on bool SettingsData::smoothScale() const { return _smoothScale; } void SettingsData::setSmoothScale(bool b) { _smoothScale = b; setValue("Viewer", "smoothScale", b); } //////////////////// //// Categories //// //////////////////// // clang-format off property_ref(untaggedCategory, setUntaggedCategory, QString, General, i18n("Events")) property_ref(untaggedTag, setUntaggedTag, QString, General, i18n("untagged")) property_copy(untaggedImagesTagVisible, setUntaggedImagesTagVisible, bool, General, false) // clang-format on ////////////// //// Exif //// ////////////// // clang-format off property_sset(exifForViewer, setExifForViewer, Exif, StringSet()) property_sset(exifForDialog, setExifForDialog, Exif, Exif::Info::instance()->standardKeys()) property_ref(iptcCharset, setIptcCharset, QString, Exif, QString()) // clang-format on ///////////////////// //// Exif Import //// ///////////////////// // clang-format off property_copy(updateExifData, setUpdateExifData, bool, ExifImport, true) property_copy(updateImageDate, setUpdateImageDate, bool, ExifImport, false) property_copy(useModDateIfNoExif, setUseModDateIfNoExif, bool, ExifImport, true) property_copy(updateOrientation, setUpdateOrientation, bool, ExifImport, false) property_copy(updateDescription, setUpdateDescription, bool, ExifImport, false) // clang-format on /////////////////////// //// Miscellaneous //// /////////////////////// // clang-format off property_ref_(HTMLBaseDir, setHTMLBaseDir, QString, groupForDatabase("HTML Settings"), QString::fromLocal8Bit(qgetenv("HOME")) + STR("/public_html")) property_ref_(HTMLBaseURL, setHTMLBaseURL, QString, groupForDatabase("HTML Settings"), STR("file://") + HTMLBaseDir()) property_ref_(HTMLDestURL, setHTMLDestURL, QString, groupForDatabase("HTML Settings"), STR("file://") + HTMLBaseDir()) property_ref_(HTMLCopyright, setHTMLCopyright, QString, groupForDatabase("HTML Settings"), STR("")) property_ref_(HTMLDate, setHTMLDate, int, groupForDatabase("HTML Settings"), true) property_ref_(HTMLTheme, setHTMLTheme, int, groupForDatabase("HTML Settings"), -1) property_ref_(HTMLKimFile, setHTMLKimFile, int, groupForDatabase("HTML Settings"), true) property_ref_(HTMLInlineMovies, setHTMLInlineMovies, int, groupForDatabase("HTML Settings"), true) property_ref_(HTML5Video, setHTML5Video, int, groupForDatabase("HTML Settings"), true) property_ref_(HTML5VideoGenerate, setHTML5VideoGenerate, int, groupForDatabase("HTML Settings"), true) property_ref_(HTMLThumbSize, setHTMLThumbSize, int, groupForDatabase("HTML Settings"), 128) property_ref_(HTMLNumOfCols, setHTMLNumOfCols, int, groupForDatabase("HTML Settings"), 5) property_ref_(HTMLSizes, setHTMLSizes, QString, groupForDatabase("HTML Settings"), STR("")) property_ref_(HTMLIncludeSelections, setHTMLIncludeSelections, QString, groupForDatabase("HTML Settings"), STR("")) // clang-format on QDate SettingsData::fromDate() const { - QString date = value("Miscellaneous", "fromDate", STR("")); + QString date = cfgValue("Miscellaneous", "fromDate", STR("")); return date.isEmpty() ? QDate(QDate::currentDate().year(), 1, 1) : QDate::fromString(date, Qt::ISODate); } void SettingsData::setFromDate(const QDate &date) { if (date.isValid()) setValue("Miscellaneous", "fromDate", date.toString(Qt::ISODate)); } QDate SettingsData::toDate() const { - QString date = value("Miscellaneous", "toDate", STR("")); + QString date = cfgValue("Miscellaneous", "toDate", STR("")); return date.isEmpty() ? QDate(QDate::currentDate().year() + 1, 1, 1) : QDate::fromString(date, Qt::ISODate); } void SettingsData::setToDate(const QDate &date) { if (date.isValid()) setValue("Miscellaneous", "toDate", date.toString(Qt::ISODate)); } QString SettingsData::imageDirectory() const { return m_imageDirectory; } QString SettingsData::groupForDatabase(const char *setting) const { return STR("%1 - %2").arg(STR(setting)).arg(imageDirectory()); } DB::ImageSearchInfo SettingsData::currentLock() const { - return DB::ImageSearchInfo::loadLock(); + // duplicating logic from ImageSearchInfo here is not ideal + // FIXME(jzarl): review the whole database view lock mechanism + const auto group = groupForDatabase("Privacy Settings"); + QVariantMap keyValuePairs; + keyValuePairs[STR("label")] = cfgValue(group, "label", {}); + keyValuePairs[STR("description")] = cfgValue(group, "description", {}); + keyValuePairs[STR("categories")] = cfgValue(group, "categories", QVariant()); + const QStringList categories = cfgValue(group, "categories", QVariant()).toStringList(); + for (QStringList::ConstIterator it = categories.constBegin(); it != categories.constEnd(); ++it) { + keyValuePairs[*it] = cfgValue(group, *it, {}); + } + return DB::ImageSearchInfo::loadLock(keyValuePairs); } void SettingsData::setCurrentLock(const DB::ImageSearchInfo &info, bool exclude) { - info.saveLock(); + const auto pairs = info.getLockData(); + for (QVariantMap::const_iterator it = pairs.cbegin(); it != pairs.cend(); ++it) { + setValue(groupForDatabase("Privacy Settings"), it.key(), it.value()); + } setValue(groupForDatabase("Privacy Settings"), "exclude", exclude); } bool SettingsData::lockExcludes() const { - return value(groupForDatabase("Privacy Settings"), "exclude", false); + return cfgValue(groupForDatabase("Privacy Settings"), "exclude", false); } getValueFunc_(bool, locked, groupForDatabase("Privacy Settings"), "locked", false) void SettingsData::setLocked(bool lock, bool force) { if (lock == locked() && !force) return; setValue(groupForDatabase("Privacy Settings"), "locked", lock); emit locked(lock, lockExcludes()); } void SettingsData::setWindowGeometry(WindowType win, const QRect &geometry) { setValue("Window Geometry", win, geometry); } QRect SettingsData::windowGeometry(WindowType win) const { - return value("Window Geometry", win, QRect(0, 0, 800, 600)); + return cfgValue("Window Geometry", win, QRect(0, 0, 800, 600)); } double Settings::SettingsData::getThumbnailAspectRatio() const { double ratio = 1.0; switch (Settings::SettingsData::instance()->thumbnailAspectRatio()) { case Settings::Aspect_16_9: ratio = 9.0 / 16; break; case Settings::Aspect_4_3: ratio = 3.0 / 4; break; case Settings::Aspect_3_2: ratio = 2.0 / 3; break; case Settings::Aspect_9_16: ratio = 16 / 9.0; break; case Settings::Aspect_3_4: ratio = 4 / 3.0; break; case Settings::Aspect_2_3: ratio = 3 / 2.0; break; case Settings::Aspect_1_1: ratio = 1.0; break; } return ratio; } QStringList Settings::SettingsData::EXIFCommentsToStrip() { return m_EXIFCommentsToStrip; } void Settings::SettingsData::setEXIFCommentsToStrip(QStringList EXIFCommentsToStrip) { m_EXIFCommentsToStrip = EXIFCommentsToStrip; } bool Settings::SettingsData::getOverlapLoadMD5() const { switch (Settings::SettingsData::instance()->loadOptimizationPreset()) { case Settings::LoadOptimizationSlowNVME: case Settings::LoadOptimizationFastNVME: return true; break; case Settings::LoadOptimizationManual: return Settings::SettingsData::instance()->overlapLoadMD5(); break; case Settings::LoadOptimizationHardDisk: case Settings::LoadOptimizationNetwork: case Settings::LoadOptimizationSataSSD: default: return false; break; } } int Settings::SettingsData::getPreloadThreadCount() const { switch (Settings::SettingsData::instance()->loadOptimizationPreset()) { case Settings::LoadOptimizationManual: return Settings::SettingsData::instance()->preloadThreadCount(); break; case Settings::LoadOptimizationSlowNVME: case Settings::LoadOptimizationFastNVME: case Settings::LoadOptimizationSataSSD: return qMax(1, qMin(16, QThread::idealThreadCount())); break; case Settings::LoadOptimizationHardDisk: case Settings::LoadOptimizationNetwork: default: return 1; break; } } int Settings::SettingsData::getThumbnailPreloadThreadCount() const { switch (Settings::SettingsData::instance()->loadOptimizationPreset()) { case Settings::LoadOptimizationManual: return Settings::SettingsData::instance()->thumbnailPreloadThreadCount(); break; case Settings::LoadOptimizationSlowNVME: case Settings::LoadOptimizationFastNVME: case Settings::LoadOptimizationSataSSD: return qMax(1, qMin(16, QThread::idealThreadCount() / 2)); break; case Settings::LoadOptimizationHardDisk: case Settings::LoadOptimizationNetwork: default: return 1; break; } } int Settings::SettingsData::getThumbnailBuilderThreadCount() const { switch (Settings::SettingsData::instance()->loadOptimizationPreset()) { case Settings::LoadOptimizationManual: return Settings::SettingsData::instance()->thumbnailBuilderThreadCount(); break; case Settings::LoadOptimizationSlowNVME: case Settings::LoadOptimizationFastNVME: case Settings::LoadOptimizationSataSSD: case Settings::LoadOptimizationHardDisk: case Settings::LoadOptimizationNetwork: default: return qMax(1, qMin(16, QThread::idealThreadCount() - 1)); break; } } DB::UIDelegate &SettingsData::uiDelegate() const { return m_UI; } // vi:expandtab:tabstop=4 shiftwidth=4: