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: