File indexing completed on 2025-01-19 03:53:36
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2007-09-19 0007 * Description : Access to comments of an item in the database 0008 * 0009 * SPDX-FileCopyrightText: 2007-2013 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de> 0010 * SPDX-FileCopyrightText: 2009-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> 0011 * 0012 * SPDX-License-Identifier: GPL-2.0-or-later 0013 * 0014 * ============================================================ */ 0015 0016 #include "itemcomments.h" 0017 0018 // Qt includes 0019 0020 #include <QLocale> 0021 0022 // Local includes 0023 0024 #include "coredb.h" 0025 0026 namespace Digikam 0027 { 0028 0029 class Q_DECL_HIDDEN ItemComments::Private : public QSharedData 0030 { 0031 public: 0032 0033 explicit Private() 0034 : id (-1), 0035 unique(ItemComments::UniquePerLanguage) 0036 { 0037 } 0038 0039 void init(const CoreDbAccess& access, qlonglong imageId) 0040 { 0041 id = imageId; 0042 infos = access.db()->getItemComments(id); 0043 0044 for (int i = 0 ; i < infos.size() ; ++i) 0045 { 0046 CommentInfo& info = infos[i]; 0047 0048 if (info.language.isNull()) 0049 { 0050 info.language = QLatin1String("x-default"); 0051 } 0052 } 0053 } 0054 0055 void languageMatch(const QString& fullCode, 0056 const QString& langCode, 0057 int& fullCodeMatch, 0058 int& langCodeMatch, 0059 int& defaultCodeMatch, 0060 int& firstMatch, 0061 DatabaseComment::Type type = DatabaseComment::Comment) const 0062 { 0063 // if you change the algorithm, please take a look at ItemCopyright as well 0064 0065 fullCodeMatch = -1; 0066 langCodeMatch = -1; 0067 defaultCodeMatch = -1; 0068 firstMatch = -1; 0069 0070 if (infos.isEmpty()) 0071 { 0072 return; 0073 } 0074 0075 // First we search for a full match 0076 // Second for a match of the language code 0077 // Third for the default code 0078 // Fourth we return the first comment 0079 0080 QLatin1String defaultCode("x-default"); 0081 0082 for (int i = 0 ; i < infos.size() ; ++i) 0083 { 0084 const CommentInfo& info = infos.at(i); 0085 0086 if (info.type == type) 0087 { 0088 if (firstMatch == -1) 0089 { 0090 firstMatch = i; 0091 } 0092 0093 if (info.language == fullCode) 0094 { 0095 fullCodeMatch = i; 0096 break; 0097 } 0098 else if (info.language.startsWith(langCode) && langCodeMatch == -1) 0099 { 0100 langCodeMatch = i; 0101 } 0102 else if (info.language == defaultCode) 0103 { 0104 defaultCodeMatch = i; 0105 } 0106 } 0107 } 0108 } 0109 0110 void adjustStoredIndexes(QSet<int>& set, int removedIndex) 0111 { 0112 QSet<int> newSet; 0113 0114 Q_FOREACH (int index, set) 0115 { 0116 if (index > removedIndex) 0117 { 0118 newSet << index - 1; 0119 } 0120 else if (index < removedIndex) 0121 { 0122 newSet << index; 0123 } 0124 0125 // drop index == removedIndex 0126 } 0127 0128 set = newSet; 0129 } 0130 0131 void adjustStoredIndexes(int removedIndex) 0132 { 0133 adjustStoredIndexes(dirtyIndices, removedIndex); 0134 adjustStoredIndexes(newIndices, removedIndex); 0135 } 0136 0137 public: 0138 0139 qlonglong id; 0140 QList<CommentInfo> infos; 0141 QSet<int> dirtyIndices; 0142 QSet<int> newIndices; 0143 QSet<int> idsToRemove; 0144 ItemComments::UniqueBehavior unique; 0145 }; 0146 0147 ItemComments::ItemComments() 0148 : d(nullptr) 0149 { 0150 } 0151 0152 ItemComments::ItemComments(qlonglong imageid) 0153 : d(new Private) 0154 { 0155 CoreDbAccess access; 0156 d->init(access, imageid); 0157 } 0158 0159 ItemComments::ItemComments(const CoreDbAccess& access, qlonglong imageid) 0160 : d(new Private) 0161 { 0162 d->init(access, imageid); 0163 } 0164 0165 ItemComments::ItemComments(const ItemComments& other) 0166 : d(other.d) 0167 { 0168 } 0169 0170 ItemComments::~ItemComments() 0171 { 0172 apply(); 0173 } 0174 0175 ItemComments& ItemComments::operator=(const ItemComments& other) 0176 { 0177 d = other.d; 0178 0179 return *this; 0180 } 0181 0182 bool ItemComments::isNull() const 0183 { 0184 return !d; 0185 } 0186 0187 QString ItemComments::defaultComment(DatabaseComment::Type type) const 0188 { 0189 return defaultComment(nullptr, type); 0190 } 0191 0192 QString ItemComments::defaultComment(int* const index, DatabaseComment::Type type) const 0193 { 0194 if (!d) 0195 { 0196 return QString(); 0197 } 0198 0199 QString spec = QLocale().name().toLower(); 0200 QString langCode = spec.left(spec.indexOf(QLatin1Char('_'))) + QLatin1Char('-'); 0201 QString fullCode = spec.replace(QLatin1Char('_'), QLatin1Char('-')); 0202 0203 int fullCodeMatch, langCodeMatch, defaultCodeMatch, firstMatch; 0204 0205 d->languageMatch(fullCode, langCode, fullCodeMatch, langCodeMatch, defaultCodeMatch, firstMatch, type); 0206 0207 int chosen = fullCodeMatch; 0208 0209 if (chosen == -1) 0210 { 0211 chosen = langCodeMatch; 0212 } 0213 0214 if (chosen == -1) 0215 { 0216 chosen = defaultCodeMatch; 0217 } 0218 0219 if (chosen == -1) 0220 { 0221 chosen = firstMatch; 0222 } 0223 0224 if (index) 0225 { 0226 *index = chosen; 0227 } 0228 0229 if (chosen == -1) 0230 { 0231 return QString(); 0232 } 0233 else 0234 { 0235 return d->infos.at(chosen).comment; 0236 } 0237 } 0238 0239 QString ItemComments::commentForLanguage(const QString& languageCode, 0240 int* const index, 0241 LanguageChoiceBehavior behavior) const 0242 { 0243 if (!d) 0244 { 0245 return QString(); 0246 } 0247 0248 int fullCodeMatch, langCodeMatch, defaultCodeMatch, firstMatch; 0249 0250 // en-us => en- 0251 0252 QString firstPart; 0253 0254 if (languageCode == QLatin1String("x-default")) 0255 { 0256 firstPart = languageCode; 0257 } 0258 else 0259 { 0260 firstPart = languageCode.section(QLatin1Char('-'), 0, 0, QString::SectionIncludeTrailingSep); 0261 } 0262 0263 d->languageMatch(languageCode, firstPart, fullCodeMatch, langCodeMatch, defaultCodeMatch, firstMatch); 0264 0265 int chosen = fullCodeMatch; 0266 0267 if (chosen == -1) 0268 { 0269 chosen = langCodeMatch; 0270 } 0271 0272 if ((chosen == -1) && (behavior > ReturnMatchingLanguageOnly)) 0273 { 0274 chosen = defaultCodeMatch; 0275 0276 if ((chosen == -1) && (behavior == ReturnMatchingDefaultOrFirstLanguage)) 0277 { 0278 chosen = firstMatch; 0279 } 0280 } 0281 0282 if (index) 0283 { 0284 *index = chosen; 0285 } 0286 0287 if (chosen == -1) 0288 { 0289 return QString(); 0290 } 0291 else 0292 { 0293 return d->infos.at(chosen).comment; 0294 } 0295 } 0296 0297 int ItemComments::numberOfComments() const 0298 { 0299 if (!d) 0300 { 0301 return 0; 0302 } 0303 0304 return d->infos.size(); 0305 } 0306 0307 DatabaseComment::Type ItemComments::type(int index) const 0308 { 0309 if (!d) 0310 { 0311 return DatabaseComment::UndefinedType; 0312 } 0313 0314 return d->infos.at(index).type; 0315 } 0316 0317 QString ItemComments::language(int index) const 0318 { 0319 if (!d) 0320 { 0321 return QString(); 0322 } 0323 0324 return d->infos.at(index).language; 0325 } 0326 0327 QString ItemComments::author(int index) const 0328 { 0329 if (!d) 0330 { 0331 return QString(); 0332 } 0333 0334 return d->infos.at(index).author; 0335 } 0336 0337 QDateTime ItemComments::date(int index) const 0338 { 0339 if (!d) 0340 { 0341 return QDateTime(); 0342 } 0343 0344 return d->infos.at(index).date; 0345 } 0346 0347 QString ItemComments::comment(int index) const 0348 { 0349 if (!d) 0350 { 0351 return QString(); 0352 } 0353 0354 return d->infos.at(index).comment; 0355 } 0356 0357 void ItemComments::setUniqueBehavior(UniqueBehavior behavior) 0358 { 0359 if (!d) 0360 { 0361 return; 0362 } 0363 0364 d->unique = behavior; 0365 } 0366 0367 void ItemComments::addComment(const QString& comment, 0368 const QString& lang, 0369 const QString& author_, 0370 const QDateTime& date, 0371 DatabaseComment::Type type) 0372 { 0373 if (!d) 0374 { 0375 return; 0376 } 0377 0378 bool multipleCommentsPerLanguage = (d->unique == UniquePerLanguageAndAuthor); 0379 QString language = lang; 0380 0381 if (language.isEmpty()) 0382 { 0383 language = QLatin1String("x-default"); 0384 } 0385 0386 QString author = author_; 0387 0388 /// @todo This makes no sense - is another variable supposed to be used instead? - Michael Hansen 0389 0390 if (author.isEmpty()) 0391 { 0392 author = QString(); 0393 } 0394 0395 for (int i = 0 ; i < d->infos.size() ; ++i) 0396 { 0397 CommentInfo& info = d->infos[i]; 0398 0399 // some extra considerations on replacing 0400 0401 if ((info.type == type) && (info.type == DatabaseComment::Comment) && (info.language == language)) 0402 { 0403 if (!multipleCommentsPerLanguage || (info.author == author)) 0404 { 0405 info.comment = comment; 0406 info.date = date; 0407 info.author = author; 0408 d->dirtyIndices << i; 0409 return; 0410 } 0411 } 0412 0413 // simulate unique restrictions of db. 0414 // There is a problem that a NULL value is never unique, see #189080 0415 0416 if ((info.type == type) && 0417 (info.language == language) && 0418 ((info.author == author) || (info.author.isEmpty() && author.isEmpty()))) 0419 { 0420 info.comment = comment; 0421 info.date = date; 0422 d->dirtyIndices << i; 0423 return; 0424 } 0425 } 0426 0427 addCommentDirectly(comment, language, author, type, date); 0428 } 0429 0430 void ItemComments::addHeadline(const QString& headline, const QString& lang, 0431 const QString& author, const QDateTime& date) 0432 { 0433 addComment(headline, lang, author, date, DatabaseComment::Headline); 0434 } 0435 0436 void ItemComments::addTitle(const QString& title, const QString& lang, 0437 const QString& author, const QDateTime& date) 0438 { 0439 addComment(title, lang, author, date, DatabaseComment::Title); 0440 } 0441 0442 void ItemComments::replaceComments(const CaptionsMap& map, DatabaseComment::Type type) 0443 { 0444 if (!d) 0445 { 0446 return; 0447 } 0448 0449 d->dirtyIndices.clear(); 0450 0451 for (CaptionsMap::const_iterator it = map.constBegin() ; it != map.constEnd() ; ++it) 0452 { 0453 CaptionValues val = it.value(); 0454 addComment(val.caption, it.key(), val.author, val.date, type); 0455 } 0456 0457 // remove all comments of this type that have not been touched above 0458 0459 for (int i = 0 ; i < d->infos.size() /* changing! */ ; ) 0460 { 0461 if (!d->dirtyIndices.contains(i) && !d->newIndices.contains(i) && (d->infos[i].type == type)) 0462 { 0463 remove(i); 0464 } 0465 else 0466 { 0467 ++i; 0468 } 0469 } 0470 } 0471 0472 void ItemComments::replaceFrom(const ItemComments& source) 0473 { 0474 if (!d) 0475 { 0476 return; 0477 } 0478 0479 if (!source.d) 0480 { 0481 removeAll(); 0482 return; 0483 } 0484 0485 Q_FOREACH (const CommentInfo& info, source.d->infos) 0486 { 0487 addComment(info.comment, info.language, info.author, info.date, info.type); 0488 } 0489 0490 // remove all that have not been touched above 0491 0492 for (int i = 0 ; i < d->infos.size() /* changing! */ ; ) 0493 { 0494 if (!d->dirtyIndices.contains(i) && !d->newIndices.contains(i)) 0495 { 0496 remove(i); 0497 } 0498 else 0499 { 0500 ++i; 0501 } 0502 } 0503 } 0504 0505 void ItemComments::addCommentDirectly(const QString& comment, 0506 const QString& language, 0507 const QString& author, 0508 DatabaseComment::Type type, 0509 const QDateTime& date) 0510 { 0511 CommentInfo info; 0512 info.comment = comment; 0513 info.language = language; 0514 info.author = author; 0515 info.type = type; 0516 info.date = date; 0517 0518 d->newIndices << d->infos.size(); 0519 d->infos << info; 0520 } 0521 0522 void ItemComments::remove(int index) 0523 { 0524 if (!d) 0525 { 0526 return; 0527 } 0528 0529 d->idsToRemove << d->infos.at(index).id; 0530 d->infos.removeAt(index); 0531 d->adjustStoredIndexes(index); 0532 } 0533 0534 void ItemComments::removeAll(DatabaseComment::Type type) 0535 { 0536 if (!d) 0537 { 0538 return; 0539 } 0540 0541 for (int i = 0 ; i < d->infos.size() /* changing! */ ; ) 0542 { 0543 if (d->infos.at(i).type == type) 0544 { 0545 remove(i); 0546 } 0547 else 0548 { 0549 ++i; 0550 } 0551 } 0552 } 0553 0554 void ItemComments::removeAllComments() 0555 { 0556 removeAll(DatabaseComment::Comment); 0557 } 0558 0559 void ItemComments::removeAll() 0560 { 0561 if (!d) 0562 { 0563 return; 0564 } 0565 0566 Q_FOREACH (const CommentInfo& info, d->infos) 0567 { 0568 d->idsToRemove << info.id; 0569 } 0570 0571 d->infos.clear(); 0572 d->dirtyIndices.clear(); 0573 d->newIndices.clear(); 0574 } 0575 0576 void ItemComments::changeComment(int index, const QString& comment) 0577 { 0578 if (!d) 0579 { 0580 return; 0581 } 0582 0583 d->infos[index].comment = comment; 0584 d->dirtyIndices << index; 0585 } 0586 0587 void ItemComments::changeLanguage(int index, const QString& language) 0588 { 0589 if (!d) 0590 { 0591 return; 0592 } 0593 0594 d->infos[index].language = language; 0595 d->dirtyIndices << index; 0596 } 0597 0598 void ItemComments::changeAuthor(int index, const QString& author) 0599 { 0600 if (!d) 0601 { 0602 return; 0603 } 0604 0605 d->infos[index].author = author; 0606 d->dirtyIndices << index; 0607 } 0608 0609 void ItemComments::changeDate(int index, const QDateTime& date) 0610 { 0611 if (!d) 0612 { 0613 return; 0614 } 0615 0616 d->infos[index].date = date; 0617 d->dirtyIndices << index; 0618 } 0619 0620 void ItemComments::changeType(int index, DatabaseComment::Type type) 0621 { 0622 if (!d) 0623 { 0624 return; 0625 } 0626 0627 d->infos[index].type = type; 0628 d->dirtyIndices << index; 0629 } 0630 0631 void ItemComments::apply() 0632 { 0633 if (!d) 0634 { 0635 return; 0636 } 0637 0638 CoreDbAccess access; 0639 apply(access); 0640 } 0641 0642 void ItemComments::apply(CoreDbAccess& access) 0643 { 0644 if (!d) 0645 { 0646 return; 0647 } 0648 0649 Q_FOREACH (int commentId, d->idsToRemove) 0650 { 0651 access.db()->removeImageComment(commentId, d->id); 0652 } 0653 0654 d->idsToRemove.clear(); 0655 0656 Q_FOREACH (int index, d->newIndices) 0657 { 0658 CommentInfo& info = d->infos[index]; 0659 info.id = access.db()->setImageComment(d->id, info.comment, info.type, info.language, info.author, info.date); 0660 } 0661 0662 d->dirtyIndices.subtract(d->newIndices); 0663 d->newIndices.clear(); 0664 0665 Q_FOREACH (int index, d->dirtyIndices) 0666 { 0667 QVariantList values; 0668 CommentInfo& info = d->infos[index]; 0669 values << (int)info.type << info.language << info.author << info.date << info.comment; 0670 access.db()->changeImageComment(info.id, d->id, values); 0671 } 0672 0673 d->dirtyIndices.clear(); 0674 } 0675 0676 CaptionsMap ItemComments::toCaptionsMap(DatabaseComment::Type type) const 0677 { 0678 CaptionsMap map; 0679 0680 if (d) 0681 { 0682 Q_FOREACH (const CommentInfo& info, d->infos) 0683 { 0684 if (info.type == type) 0685 { 0686 CaptionValues val; 0687 val.caption = info.comment; 0688 val.author = info.author; 0689 val.date = info.date; 0690 map[info.language] = val; 0691 } 0692 } 0693 } 0694 0695 return map; 0696 } 0697 0698 } // namespace Digikam