File indexing completed on 2024-04-28 15:39:55

0001 // SPDX-FileCopyrightText: 2004 - 2022 Jesper K. Pedersen <jesper.pedersen@kdab.com>
0002 // SPDX-FileCopyrightText: 2007 Dirk Mueller <mueller@kde.org>
0003 // SPDX-FileCopyrightText: 2007 Tuomas Suutari <tuomas@nepnep.net>
0004 // SPDX-FileCopyrightText: 2008 Laurent Montel <montel@kde.org>
0005 // SPDX-FileCopyrightText: 2012 Miika Turkia <miika.turkia@gmail.com>
0006 // SPDX-FileCopyrightText: 2013 - 2024 Johannes Zarl-Zierl <johannes@zarl-zierl.at>
0007 // SPDX-FileCopyrightText: 2014 - 2018 Tobias Leupold <tl@stonemx.de>
0008 //
0009 // SPDX-License-Identifier: GPL-2.0-or-later
0010 
0011 #include "Category.h"
0012 
0013 #include "CategoryItem.h"
0014 #include "ImageDB.h"
0015 #include "MemberMap.h"
0016 #include "TagInfo.h"
0017 #include "Utilities/List.h"
0018 
0019 #include <Utilities/ImageUtil.h>
0020 #include <kpabase/Logging.h>
0021 #include <kpabase/SettingsData.h>
0022 #include <kpabase/UIDelegate.h>
0023 
0024 #include <KLocalizedString>
0025 #include <QDir>
0026 #include <QFileInfo>
0027 #include <QIcon>
0028 #include <QPixmap>
0029 #include <QPixmapCache>
0030 #include <kiconloader.h>
0031 
0032 using Utilities::StringSet;
0033 
0034 DB::Category::Category(const QString &name, const QString &icon, ViewType type, int thumbnailSize, bool show, bool positionable)
0035     : m_name(name)
0036     , m_icon(icon)
0037     , m_show(show)
0038     , m_type(type)
0039     , m_thumbnailSize(thumbnailSize)
0040     , m_positionable(positionable)
0041     , m_categoryType(DB::Category::PlainCategory)
0042     , m_shouldSave(true)
0043 {
0044 }
0045 
0046 QString DB::Category::name() const
0047 {
0048     return m_name;
0049 }
0050 
0051 void DB::Category::setName(const QString &name)
0052 {
0053     m_name = name;
0054 }
0055 
0056 bool DB::Category::positionable() const
0057 {
0058     return m_positionable;
0059 }
0060 
0061 void DB::Category::setPositionable(bool positionable)
0062 {
0063     if (positionable == m_positionable)
0064         return;
0065 
0066     m_positionable = positionable;
0067     Q_EMIT changed();
0068 }
0069 
0070 QString DB::Category::iconName() const
0071 {
0072     return m_icon;
0073 }
0074 
0075 void DB::Category::setIconName(const QString &name)
0076 {
0077     if (m_icon == name)
0078         return;
0079 
0080     m_icon = name;
0081     Q_EMIT changed();
0082 }
0083 
0084 QPixmap DB::Category::icon(int size, KIconLoader::States state) const
0085 {
0086     QPixmap pixmap = KIconLoader::global()->loadIcon(iconName(), KIconLoader::Desktop, size, state, QStringList(), nullptr, true);
0087     return pixmap;
0088 }
0089 
0090 DB::Category::ViewType DB::Category::viewType() const
0091 {
0092     return m_type;
0093 }
0094 
0095 void DB::Category::setViewType(ViewType type)
0096 {
0097     if (m_type == type)
0098         return;
0099 
0100     m_type = type;
0101     Q_EMIT changed();
0102 }
0103 
0104 int DB::Category::thumbnailSize() const
0105 {
0106     return m_thumbnailSize;
0107 }
0108 
0109 void DB::Category::setThumbnailSize(int size)
0110 {
0111     if (m_thumbnailSize == size)
0112         return;
0113 
0114     m_thumbnailSize = size;
0115     Q_EMIT changed();
0116 }
0117 
0118 bool DB::Category::doShow() const
0119 {
0120     return m_show;
0121 }
0122 
0123 void DB::Category::setDoShow(bool b)
0124 {
0125     if (b == m_show)
0126         return;
0127 
0128     m_show = b;
0129     Q_EMIT changed();
0130 }
0131 
0132 DB::Category::CategoryType DB::Category::type() const
0133 {
0134     return m_categoryType;
0135 }
0136 
0137 void DB::Category::setType(CategoryType t)
0138 {
0139     m_categoryType = t;
0140 }
0141 
0142 bool DB::Category::isSpecialCategory() const
0143 {
0144     return m_categoryType != DB::Category::PlainCategory;
0145 }
0146 
0147 QStringList DB::Category::items() const
0148 {
0149     return m_items;
0150 }
0151 
0152 QStringList DB::Category::itemsInclCategories() const
0153 {
0154     // values including member groups
0155 
0156     QStringList items = this->items();
0157 
0158     // add the groups to the list too, but only if the group is not there already, which will be the case
0159     // if it has ever been selected once.
0160     QStringList groups = DB::ImageDB::instance()->memberMap().groups(name());
0161     for (QStringList::ConstIterator it = groups.constBegin(); it != groups.constEnd(); ++it) {
0162         if (!items.contains(*it))
0163             items << *it;
0164     };
0165 
0166     return items;
0167 }
0168 
0169 DB::CategoryItem *createItem(const QString &categoryName, const QString &itemName, StringSet handledItems,
0170                              QMap<QString, DB::CategoryItem *> &categoryItems,
0171                              QMap<QString, DB::CategoryItem *> &potentialToplevelItems)
0172 {
0173     handledItems.insert(itemName);
0174     DB::CategoryItem *result = new DB::CategoryItem(itemName);
0175 
0176     const QStringList members = DB::ImageDB::instance()->memberMap().members(categoryName, itemName, false);
0177     for (QStringList::ConstIterator memberIt = members.constBegin(); memberIt != members.constEnd(); ++memberIt) {
0178         if (!handledItems.contains(*memberIt)) {
0179             DB::CategoryItem *child;
0180             if (categoryItems.contains(*memberIt))
0181                 child = categoryItems[*memberIt]->clone();
0182             else
0183                 child = createItem(categoryName, *memberIt, handledItems, categoryItems, potentialToplevelItems);
0184 
0185             potentialToplevelItems.remove(*memberIt);
0186             result->mp_subcategories.append(child);
0187         }
0188     }
0189 
0190     categoryItems.insert(itemName, result);
0191     return result;
0192 }
0193 
0194 DB::CategoryItemPtr DB::Category::itemsCategories() const
0195 {
0196     const MemberMap &map = ImageDB::instance()->memberMap();
0197     const QStringList groups = map.groups(name());
0198 
0199     QMap<QString, DB::CategoryItem *> categoryItems;
0200     QMap<QString, DB::CategoryItem *> potentialToplevelItems;
0201 
0202     for (QStringList::ConstIterator groupIt = groups.constBegin(); groupIt != groups.constEnd(); ++groupIt) {
0203         if (!categoryItems.contains(*groupIt)) {
0204             StringSet handledItems;
0205             DB::CategoryItem *child = createItem(name(), *groupIt, handledItems, categoryItems, potentialToplevelItems);
0206             potentialToplevelItems.insert(*groupIt, child);
0207         }
0208     }
0209 
0210     CategoryItem *result = new CategoryItem(QString::fromLatin1("top"), true);
0211     for (QMap<QString, DB::CategoryItem *>::ConstIterator toplevelIt = potentialToplevelItems.constBegin(); toplevelIt != potentialToplevelItems.constEnd(); ++toplevelIt) {
0212         result->mp_subcategories.append(*toplevelIt);
0213     }
0214 
0215     // Add items not found yet.
0216     QStringList elms = items();
0217     for (QStringList::ConstIterator elmIt = elms.constBegin(); elmIt != elms.constEnd(); ++elmIt) {
0218         if (!categoryItems.contains(*elmIt))
0219             result->mp_subcategories.append(new DB::CategoryItem(*elmIt));
0220     }
0221 
0222     return CategoryItemPtr(result);
0223 }
0224 
0225 void DB::Category::addOrReorderItems(const QStringList &items)
0226 {
0227     m_items = Utilities::mergeListsUniqly(items, m_items);
0228 }
0229 
0230 void DB::Category::setItems(const QStringList &items)
0231 {
0232     m_items = items;
0233 }
0234 
0235 void DB::Category::removeItem(const QString &item)
0236 {
0237     m_items.removeAll(item);
0238     m_nameMap.remove(idForName(item));
0239     m_idMap.remove(item);
0240     Q_EMIT itemRemoved(item);
0241 }
0242 
0243 void DB::Category::renameItem(const QString &oldValue, const QString &newValue)
0244 {
0245     const auto sanitizedNewValue = newValue.trimmed();
0246     int id = idForName(oldValue);
0247     m_items.removeAll(oldValue);
0248     m_nameMap.remove(id);
0249     m_idMap.remove(oldValue);
0250 
0251     addItem(sanitizedNewValue);
0252     if (id > 0)
0253         setIdMapping(sanitizedNewValue, id);
0254     Q_EMIT itemRenamed(oldValue, sanitizedNewValue);
0255 }
0256 
0257 void DB::Category::addItem(const QString &item)
0258 {
0259     const auto sanitizedItem = item.trimmed();
0260     // for the "SortLastUsed" functionality in ListSelect we remove the item and insert it again:
0261     if (m_items.contains(sanitizedItem))
0262         m_items.removeAll(sanitizedItem);
0263     m_items.prepend(sanitizedItem);
0264 }
0265 
0266 DB::TagInfo *DB::Category::itemForName(const QString &tag)
0267 {
0268     if (m_items.contains(tag)) {
0269         return new DB::TagInfo(this, tag);
0270     } else {
0271         return nullptr;
0272     }
0273 }
0274 
0275 QPixmap DB::Category::categoryImage(const QString &category, const QString &member, int width, int height) const
0276 {
0277     QString fileName = fileForCategoryImage(category, member);
0278     QString key = QString::fromLatin1("%1-%2").arg(width).arg(fileName);
0279     QPixmap res;
0280     if (QPixmapCache::find(key, &res))
0281         return res;
0282 
0283     QImage img;
0284     bool ok = img.load(fileName, "JPEG");
0285     if (!ok) {
0286         if (DB::ImageDB::instance()->memberMap().isGroup(category, member))
0287             img = KIconLoader::global()->loadIcon(QString::fromLatin1("kuser"), KIconLoader::Desktop, qMax(width, height)).toImage();
0288         else
0289             img = icon(qMax(width, height)).toImage();
0290     }
0291     res = QPixmap::fromImage(Utilities::scaleImage(img, QSize(width, height), Qt::KeepAspectRatio));
0292 
0293     QPixmapCache::insert(key, res);
0294     return res;
0295 }
0296 
0297 void DB::Category::setCategoryImage(const QString &category, const QString &member, const QImage &image)
0298 {
0299     QString dir = Settings::SettingsData::instance()->imageDirectory() + QString::fromLatin1("CategoryImages");
0300     QFileInfo fi(dir);
0301     bool ok;
0302     if (!fi.exists()) {
0303         bool ok = QDir().mkdir(dir);
0304         if (!ok) {
0305             DB::ImageDB::instance()->uiDelegate().error(
0306                 DB::LogMessage { DBLog(), QString::fromLatin1("Unable to create CategoryImages folder!") },
0307                 i18n("Unable to create folder '%1'.", dir), i18n("Unable to Create Folder"));
0308             return;
0309         }
0310     }
0311     QString fileName = fileForCategoryImage(category, member);
0312     ok = image.save(fileName, "JPEG");
0313     if (!ok) {
0314         DB::ImageDB::instance()->uiDelegate().error(
0315             DB::LogMessage { DBLog(), QString::fromLatin1("Unable to save category image '%1'!").arg(fileName) },
0316             i18n("Error when saving image '%1'.", fileName), i18n("Error Saving Image"));
0317         return;
0318     }
0319 
0320     QPixmapCache::clear();
0321 }
0322 
0323 QString DB::Category::fileForCategoryImage(const QString &category, QString member) const
0324 {
0325     QString dir = Settings::SettingsData::instance()->imageDirectory() + QString::fromLatin1("CategoryImages");
0326     member.replace(QChar::fromLatin1(' '), QChar::fromLatin1('_'));
0327     member.replace(QChar::fromLatin1('/'), QChar::fromLatin1('_'));
0328     QString fileName = dir + QString::fromLatin1("/%1-%2.jpg").arg(category, member);
0329     return fileName;
0330 }
0331 
0332 QDate DB::Category::birthDate(const QString &item) const
0333 {
0334     return m_birthDates[item];
0335 }
0336 
0337 void DB::Category::setBirthDate(const QString &item, const QDate &birthDate)
0338 {
0339     m_birthDates.insert(item, birthDate);
0340 }
0341 
0342 int DB::Category::idForName(const QString &name) const
0343 {
0344     Q_ASSERT(m_idMap.count(name) <= 1);
0345     return m_idMap[name];
0346 }
0347 
0348 void DB::Category::initIdMap()
0349 {
0350     // find maximum id
0351     // obviously, this will leave gaps in numbering when tags are deleted
0352     // assuming that tags are seldomly removed this should not be a problem
0353     int i = 1;
0354     if (!m_nameMap.empty()) {
0355         i = m_nameMap.lastKey();
0356     }
0357 
0358     for (const QString &tag : qAsConst(m_items)) {
0359         if (!m_idMap.contains(tag))
0360             setIdMapping(tag, ++i);
0361     }
0362 
0363     const QStringList groups = DB::ImageDB::instance()->memberMap().groups(m_name);
0364     for (const QString &group : groups) {
0365         if (!m_idMap.contains(group))
0366             setIdMapping(group, ++i);
0367     }
0368 }
0369 
0370 void DB::Category::setIdMapping(const QString &name, int id)
0371 {
0372     Q_ASSERT(id > 0);
0373     m_nameMap.insert(id, name);
0374     m_idMap.insert(name, id);
0375 }
0376 
0377 void DB::Category::addZeroMapping(const QString &name)
0378 {
0379     m_namesWithIdZero += name;
0380     m_idMap.insert(name, 0);
0381 }
0382 
0383 QString DB::Category::nameForId(int id) const
0384 {
0385     Q_ASSERT(m_nameMap.count(id) <= 1);
0386     return m_nameMap[id];
0387 }
0388 
0389 QStringList DB::Category::namesForIdZero() const
0390 {
0391     return m_namesWithIdZero;
0392 }
0393 
0394 void DB::Category::clearNullIds()
0395 {
0396     for (const auto &tag : m_namesWithIdZero) {
0397         m_idMap.remove(tag);
0398     }
0399     m_namesWithIdZero.clear();
0400 }
0401 
0402 bool DB::Category::shouldSave()
0403 {
0404     return m_shouldSave;
0405 }
0406 
0407 void DB::Category::setShouldSave(bool b)
0408 {
0409     m_shouldSave = b;
0410 }
0411 
0412 #include "moc_Category.cpp"
0413 // vi:expandtab:tabstop=4 shiftwidth=4: