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

0001 // SPDX-FileCopyrightText: 2003-2020 The KPhotoAlbum Development Team
0002 // SPDX-FileCopyrightText: 2021 Johannes Zarl-Zierl <johannes@zarl-zierl.at>
0003 // SPDX-FileCopyrightText: 2022 Johannes Zarl-Zierl <johannes@zarl-zierl.at>
0004 //
0005 // SPDX-License-Identifier: GPL-2.0-or-later
0006 
0007 #include "ImageInfo.h"
0008 
0009 #include "CategoryCollection.h"
0010 #include "FileInfo.h"
0011 #include "ImageDB.h"
0012 #include "MemberMap.h"
0013 
0014 #include <kpabase/FileNameUtil.h>
0015 #include <kpabase/Logging.h>
0016 #include <kpabase/SettingsData.h>
0017 #include <kpabase/StringSet.h>
0018 #include <kpaexif/Database.h>
0019 #include <kpaexif/DatabaseElement.h>
0020 
0021 #include <QFile>
0022 #include <QFileInfo>
0023 #include <QImageReader>
0024 #include <QStringList>
0025 
0026 using namespace DB;
0027 
0028 ImageInfo::ImageInfo()
0029     : m_angle(0)
0030     , m_imageOnDisk(OnDisk::Unchecked)
0031     , m_null(true)
0032     , m_type(anyMediaType)
0033     , m_rating(-1)
0034     , m_stackId(0)
0035     , m_stackOrder(0)
0036     , m_videoLength(-1)
0037     , m_isMatched(false)
0038     , m_matchGeneration(-1)
0039     , m_locked(false)
0040     , m_dirty(false)
0041 {
0042 }
0043 
0044 ImageInfo::ImageInfo(const DB::FileName &fileName, MediaType type, FileInformation infoMode)
0045     : m_imageOnDisk(YesOnDisk)
0046     , m_null(false)
0047     , m_size(-1, -1)
0048     , m_type(type)
0049     , m_rating(-1)
0050     , m_stackId(0)
0051     , m_stackOrder(0)
0052     , m_videoLength(-1)
0053     , m_isMatched(false)
0054     , m_matchGeneration(-1)
0055     , m_locked(false)
0056 {
0057     QFileInfo fi(fileName.absolute());
0058     m_label = fi.completeBaseName();
0059     m_angle = 0;
0060 
0061     setFileName(fileName);
0062 
0063     // Read Exif information
0064     if (infoMode != FileInformation::Ignore) {
0065         ExifMode mode = EXIFMODE_INIT;
0066         if (infoMode == FileInformation::ReadAndUpdateExifDB)
0067             mode &= ~EXIFMODE_DATABASE_UPDATE;
0068         readExif(fileName, mode);
0069     }
0070 
0071     m_dirty = false;
0072 }
0073 
0074 ImageInfo::ImageInfo(const ImageInfo &other)
0075     : QSharedData(other)
0076 {
0077     *this = other;
0078 }
0079 
0080 void ImageInfo::setIsMatched(bool isMatched)
0081 {
0082     m_isMatched = isMatched;
0083 }
0084 
0085 bool ImageInfo::isMatched() const
0086 {
0087     return m_isMatched;
0088 }
0089 
0090 void ImageInfo::setMatchGeneration(int matchGeneration)
0091 {
0092     m_matchGeneration = matchGeneration;
0093 }
0094 
0095 int ImageInfo::matchGeneration() const
0096 {
0097     return m_matchGeneration;
0098 }
0099 
0100 void ImageInfo::setLabel(const QString &desc)
0101 {
0102     if (desc != m_label)
0103         markDirty();
0104     m_label = desc;
0105 }
0106 
0107 QString ImageInfo::label() const
0108 {
0109     return m_label;
0110 }
0111 
0112 void ImageInfo::setDescription(const QString &desc)
0113 {
0114     if (desc != m_description)
0115         markDirty();
0116     m_description = desc.trimmed();
0117 }
0118 
0119 QString ImageInfo::description() const
0120 {
0121     return m_description;
0122 }
0123 
0124 void ImageInfo::setCategoryInfo(const QString &key, const StringSet &value)
0125 {
0126     // Don't check if really changed, because it's too slow.
0127     markDirty();
0128     m_categoryInfomation[key] = value;
0129 }
0130 
0131 bool ImageInfo::hasCategoryInfo(const QString &key, const QString &value) const
0132 {
0133     return m_categoryInfomation[key].contains(value);
0134 }
0135 
0136 bool DB::ImageInfo::hasCategoryInfo(const QString &key, const StringSet &values) const
0137 {
0138     return values.intersects(m_categoryInfomation[key]);
0139 }
0140 
0141 StringSet ImageInfo::itemsOfCategory(const QString &key) const
0142 {
0143     return m_categoryInfomation[key];
0144 }
0145 
0146 void ImageInfo::renameItem(const QString &category, const QString &oldValue, const QString &newValue)
0147 {
0148     if (m_taggedAreas.contains(category)) {
0149         if (m_taggedAreas[category].contains(oldValue)) {
0150             m_taggedAreas[category][newValue] = m_taggedAreas[category][oldValue];
0151             m_taggedAreas[category].remove(oldValue);
0152         }
0153     }
0154 
0155     StringSet &set = m_categoryInfomation[category];
0156     StringSet::iterator it = set.find(oldValue);
0157     if (it != set.end()) {
0158         markDirty();
0159         set.erase(it);
0160         set.insert(newValue);
0161     }
0162 }
0163 
0164 DB::FileName ImageInfo::fileName() const
0165 {
0166     return m_fileName;
0167 }
0168 
0169 void ImageInfo::setFileName(const DB::FileName &fileName)
0170 {
0171     if (fileName != m_fileName)
0172         markDirty();
0173     m_fileName = fileName;
0174 
0175     m_imageOnDisk = Unchecked;
0176     DB::CategoryPtr folderCategory = DB::ImageDB::instance()->categoryCollection()->categoryForSpecial(DB::Category::FolderCategory);
0177     if (folderCategory) {
0178         DB::MemberMap &map = DB::ImageDB::instance()->memberMap();
0179         createFolderCategoryItem(folderCategory, map);
0180         // ImageDB::instance()->setMemberMap( map );
0181     }
0182 }
0183 
0184 void ImageInfo::rotate(int degrees, RotationMode mode)
0185 {
0186     // ensure positive degrees:
0187     degrees += 360;
0188     degrees = degrees % 360;
0189     if (degrees == 0)
0190         return;
0191 
0192     markDirty();
0193     m_angle = (m_angle + degrees) % 360;
0194 
0195     if (degrees == 90 || degrees == 270) {
0196         m_size.transpose();
0197     }
0198 
0199     // the AnnotationDialog manages this by itself and sets RotateImageInfoOnly:
0200     if (mode == RotateImageInfoAndAreas) {
0201         for (auto &areasOfCategory : m_taggedAreas) {
0202             for (auto &area : areasOfCategory) {
0203                 QRect rotatedArea;
0204 
0205                 // parameter order for QRect::setCoords:
0206                 // setCoords( left, top, right, bottom )
0207                 // keep in mind that _size is already transposed
0208                 switch (degrees) {
0209                 case 90:
0210                     rotatedArea.setCoords(
0211                         m_size.width() - area.bottom(),
0212                         area.left(),
0213                         m_size.width() - area.top(),
0214                         area.right());
0215                     break;
0216                 case 180:
0217                     rotatedArea.setCoords(
0218                         m_size.width() - area.right(),
0219                         m_size.height() - area.bottom(),
0220                         m_size.width() - area.left(),
0221                         m_size.height() - area.top());
0222                     break;
0223                 case 270:
0224                     rotatedArea.setCoords(
0225                         area.top(),
0226                         m_size.height() - area.right(),
0227                         area.bottom(),
0228                         m_size.height() - area.left());
0229                     break;
0230                 default:
0231                     // degrees==0; "odd" values won't happen.
0232                     rotatedArea = area;
0233                     break;
0234                 }
0235 
0236                 // update _taggedAreas[category][tag]:
0237                 area = rotatedArea;
0238             }
0239         }
0240     }
0241 }
0242 
0243 int ImageInfo::angle() const
0244 {
0245     return m_angle;
0246 }
0247 
0248 void ImageInfo::setAngle(int angle)
0249 {
0250     if (angle != m_angle)
0251         markDirty();
0252     m_angle = angle;
0253 }
0254 
0255 short ImageInfo::rating() const
0256 {
0257     return m_rating;
0258 }
0259 
0260 void ImageInfo::setRating(short rating)
0261 {
0262     Q_ASSERT((rating >= 0 && rating <= 10) || rating == -1);
0263 
0264     if (rating > 10)
0265         rating = 10;
0266     if (rating < -1)
0267         rating = -1;
0268     if (m_rating != rating)
0269         markDirty();
0270 
0271     m_rating = rating;
0272 }
0273 
0274 DB::StackID ImageInfo::stackId() const
0275 {
0276     return m_stackId;
0277 }
0278 
0279 void ImageInfo::setStackId(const DB::StackID stackId)
0280 {
0281     if (stackId != m_stackId)
0282         markDirty();
0283     m_stackId = stackId;
0284 }
0285 
0286 unsigned int ImageInfo::stackOrder() const
0287 {
0288     return m_stackOrder;
0289 }
0290 
0291 void ImageInfo::setStackOrder(const unsigned int stackOrder)
0292 {
0293     if (stackOrder != m_stackOrder)
0294         markDirty();
0295     m_stackOrder = stackOrder;
0296 }
0297 
0298 void ImageInfo::setVideoLength(int length)
0299 {
0300     if (m_videoLength != length)
0301         markDirty();
0302     m_videoLength = length;
0303 }
0304 
0305 int ImageInfo::videoLength() const
0306 {
0307     return m_videoLength;
0308 }
0309 
0310 void ImageInfo::setDate(const ImageDate &date)
0311 {
0312     if (date != m_date)
0313         markDirty();
0314     m_date = date;
0315 }
0316 
0317 ImageDate &ImageInfo::date()
0318 {
0319     return m_date;
0320 }
0321 
0322 ImageDate ImageInfo::date() const
0323 {
0324     return m_date;
0325 }
0326 
0327 bool ImageInfo::operator!=(const ImageInfo &other) const
0328 {
0329     return !(*this == other);
0330 }
0331 
0332 bool ImageInfo::operator==(const ImageInfo &other) const
0333 {
0334     bool changed = (m_fileName != other.m_fileName || m_label != other.m_label || (!m_description.isEmpty() && !other.m_description.isEmpty() && m_description != other.m_description) || // one might be isNull.
0335                     m_date != other.m_date || m_angle != other.m_angle || m_rating != other.m_rating || (m_stackId != other.m_stackId || !((m_stackId == 0) ? true : (m_stackOrder == other.m_stackOrder))));
0336     if (!changed) {
0337         QStringList keys = DB::ImageDB::instance()->categoryCollection()->categoryNames();
0338         for (QStringList::ConstIterator it = keys.constBegin(); it != keys.constEnd(); ++it)
0339             changed |= m_categoryInfomation[*it] != other.m_categoryInfomation[*it];
0340     }
0341     return !changed;
0342 }
0343 
0344 void ImageInfo::renameCategory(const QString &oldName, const QString &newName)
0345 {
0346     markDirty();
0347 
0348     m_categoryInfomation[newName] = m_categoryInfomation[oldName];
0349     m_categoryInfomation.remove(oldName);
0350 
0351     m_taggedAreas[newName] = m_taggedAreas[oldName];
0352     m_taggedAreas.remove(oldName);
0353 }
0354 
0355 void ImageInfo::setMD5Sum(const MD5 &sum, bool storeEXIF)
0356 {
0357     if (sum != m_md5sum) {
0358         // if we make a QObject derived class out of imageinfo, we might invalidate thumbnails from here
0359 
0360         // file changed -> reload/invalidate metadata:
0361         ExifMode mode = EXIFMODE_ORIENTATION | EXIFMODE_DATABASE_UPDATE;
0362         // fuzzy dates are usually set for a reason
0363         if (!m_date.isFuzzy())
0364             mode |= EXIFMODE_DATE;
0365         // FIXME (ZaJ): the "right" thing to do would be to update the description
0366         //              - if it is currently empty (done.)
0367         //              - if it has been set from the exif info and not been changed (TODO)
0368         if (m_description.isEmpty())
0369             mode |= EXIFMODE_DESCRIPTION;
0370 
0371         if (!storeEXIF)
0372             mode &= ~EXIFMODE_DATABASE_UPDATE;
0373         readExif(fileName(), mode);
0374         if (storeEXIF) {
0375             // Size isn't really EXIF, but this is the most obvious
0376             // place to extract it
0377             QImageReader reader(m_fileName.absolute());
0378             if (reader.canRead()) {
0379                 m_size = reader.size();
0380                 if (m_angle == 90 || m_angle == 270)
0381                     m_size.transpose();
0382             }
0383         }
0384 
0385         // FIXME (ZaJ): it *should* make sense to set the ImageDB::md5Map() from here, but I want
0386         //              to make sure I fully understand everything first...
0387         //              this could also be done as signal md5Changed(old,new)
0388 
0389         // image size is invalidated by the thumbnail builder, if needed
0390 
0391         markDirty();
0392     }
0393     m_md5sum = sum;
0394 }
0395 
0396 void ImageInfo::setLocked(bool locked)
0397 {
0398     m_locked = locked;
0399 }
0400 
0401 bool ImageInfo::isLocked() const
0402 {
0403     return m_locked;
0404 }
0405 
0406 void ImageInfo::readExif(const DB::FileName &fullPath, DB::ExifMode mode)
0407 {
0408     DB::FileInfo exifInfo = DB::FileInfo::read(fullPath, mode);
0409 
0410     // Date
0411     if (updateDateInformation(mode)) {
0412         const ImageDate newDate(exifInfo.dateTime());
0413         setDate(newDate);
0414     }
0415 
0416     // Orientation
0417     if ((mode & EXIFMODE_ORIENTATION) && Settings::SettingsData::instance()->useEXIFRotate()) {
0418         setAngle(exifInfo.angle());
0419     }
0420 
0421     // Description
0422     if ((mode & EXIFMODE_DESCRIPTION) && Settings::SettingsData::instance()->useEXIFComments()) {
0423         bool doSetDescription = true;
0424         QString desc = exifInfo.description();
0425 
0426         if (Settings::SettingsData::instance()->stripEXIFComments()) {
0427             for (const auto &ignoredComment : Settings::SettingsData::instance()->EXIFCommentsToStrip()) {
0428                 if (desc == ignoredComment) {
0429                     doSetDescription = false;
0430                     break;
0431                 }
0432             }
0433         }
0434 
0435         if (doSetDescription) {
0436             setDescription(desc);
0437         }
0438     }
0439 
0440     // Database update
0441     if (mode & EXIFMODE_DATABASE_UPDATE) {
0442         DB::ImageDB::instance()->exifDB()->add(exifInfo.getFileName(), exifInfo.getExifData());
0443 #ifdef HAVE_MARBLE
0444         // GPS coords might have changed...
0445         m_coordsIsSet = false;
0446 #endif
0447     }
0448 }
0449 
0450 QStringList ImageInfo::availableCategories() const
0451 {
0452     return m_categoryInfomation.keys();
0453 }
0454 
0455 QSize ImageInfo::size() const
0456 {
0457     return m_size;
0458 }
0459 
0460 void ImageInfo::setSize(const QSize &size)
0461 {
0462     if (size != m_size)
0463         markDirty();
0464     m_size = size;
0465 }
0466 
0467 bool ImageInfo::imageOnDisk(const DB::FileName &fileName)
0468 {
0469     return fileName.exists();
0470 }
0471 
0472 ImageInfo::ImageInfo(const DB::FileName &fileName,
0473                      const QString &label,
0474                      const QString &description,
0475                      const ImageDate &date,
0476                      int angle,
0477                      const MD5 &md5sum,
0478                      const QSize &size,
0479                      MediaType type,
0480                      short rating,
0481                      unsigned int stackId,
0482                      unsigned int stackOrder)
0483     : m_fileName(fileName)
0484     , m_label(label)
0485     , m_description(description)
0486     , m_date(date)
0487     , m_angle(angle)
0488     , m_md5sum(md5sum)
0489     , m_null(false)
0490     , m_size(size)
0491     , m_type(type)
0492 {
0493     markDirty();
0494 
0495     if (rating > 10)
0496         rating = 10;
0497     if (rating < -1)
0498         rating = -1;
0499     m_rating = rating;
0500     m_stackId = stackId;
0501     m_stackOrder = stackOrder;
0502     m_videoLength = -1;
0503     m_matchGeneration = -1;
0504 }
0505 
0506 // Note: we need this operator because the base class QSharedData hides
0507 // its copy operator to make exclude the reference counting from being
0508 // copied.
0509 ImageInfo &ImageInfo::operator=(const ImageInfo &other)
0510 {
0511     m_fileName = other.m_fileName;
0512     m_label = other.m_label;
0513     m_description = other.m_description;
0514     m_date = other.m_date;
0515     m_categoryInfomation = other.m_categoryInfomation;
0516     m_taggedAreas = other.m_taggedAreas;
0517     m_angle = other.m_angle;
0518     m_imageOnDisk = other.m_imageOnDisk;
0519     m_md5sum = other.m_md5sum;
0520     m_null = other.m_null;
0521     m_size = other.m_size;
0522     m_type = other.m_type;
0523     m_rating = other.m_rating;
0524     m_stackId = other.m_stackId;
0525     m_stackOrder = other.m_stackOrder;
0526     m_videoLength = other.m_videoLength;
0527     m_isMatched = other.m_isMatched;
0528     m_matchGeneration = other.m_matchGeneration;
0529 #ifdef HAVE_KGEOMAP
0530     m_coordinates = other.m_coordinates;
0531     m_coordsIsSet = other.m_coordsIsSet;
0532 #endif
0533     m_locked = other.m_locked;
0534     m_dirty = other.m_dirty;
0535 
0536     return *this;
0537 }
0538 
0539 MediaType DB::ImageInfo::mediaType() const
0540 {
0541     return m_type;
0542 }
0543 
0544 bool ImageInfo::isVideo() const
0545 {
0546     return m_type == Video;
0547 }
0548 
0549 void DB::ImageInfo::createFolderCategoryItem(DB::CategoryPtr folderCategory, DB::MemberMap &memberMap)
0550 {
0551     QString folderName = Utilities::relativeFolderName(m_fileName.relative());
0552     if (folderName.isEmpty())
0553         return;
0554 
0555     if (!memberMap.contains(folderCategory->name(), folderName)) {
0556         QStringList directories = folderName.split(QString::fromLatin1("/"));
0557 
0558         QString curPath;
0559         for (QStringList::ConstIterator directoryIt = directories.constBegin(); directoryIt != directories.constEnd(); ++directoryIt) {
0560             if (curPath.isEmpty())
0561                 curPath = *directoryIt;
0562             else {
0563                 QString oldPath = curPath;
0564                 curPath = curPath + QString::fromLatin1("/") + *directoryIt;
0565                 memberMap.addMemberToGroup(folderCategory->name(), oldPath, curPath);
0566             }
0567         }
0568         folderCategory->addItem(folderName);
0569     }
0570 
0571     m_categoryInfomation.insert(folderCategory->name(), StringSet() << folderName);
0572 }
0573 
0574 void DB::ImageInfo::copyExtraData(const DB::ImageInfo &from, bool copyAngle)
0575 {
0576     m_categoryInfomation = from.m_categoryInfomation;
0577     m_description = from.m_description;
0578     // Hmm...  what should the date be?  orig or modified?
0579     // _date = from._date;
0580     if (copyAngle)
0581         m_angle = from.m_angle;
0582     m_rating = from.m_rating;
0583 }
0584 
0585 void DB::ImageInfo::removeExtraData()
0586 {
0587     m_categoryInfomation.clear();
0588     m_description.clear();
0589     m_rating = -1;
0590 }
0591 
0592 void ImageInfo::merge(const ImageInfo &other)
0593 {
0594     // Merge date
0595     if (other.date() != m_date) {
0596         // a fuzzy date has been set by the user and therefore "wins" over an exact date.
0597         // two fuzzy dates can be merged
0598         // two exact dates should ideally be cross-checked with Exif information in the file.
0599         // Nevertheless, we merge them into a fuzzy date to avoid the complexity of checking the file.
0600         if (other.date().isFuzzy()) {
0601             if (m_date.isFuzzy())
0602                 m_date.extendTo(other.date());
0603             else
0604                 m_date = other.date();
0605         } else if (!m_date.isFuzzy()) {
0606             m_date.extendTo(other.date());
0607         }
0608         // else: keep m_date
0609     }
0610 
0611     // Merge description
0612     if (!other.description().isEmpty()) {
0613         if (m_description.isEmpty())
0614             m_description = other.description();
0615         else if (m_description != other.description())
0616             m_description += QString::fromUtf8("\n-----------\n") + other.m_description;
0617     }
0618 
0619     // Clear untagged tag if only one of the images was untagged
0620     const QString untaggedCategory = Settings::SettingsData::instance()->untaggedCategory();
0621     const QString untaggedTag = Settings::SettingsData::instance()->untaggedTag();
0622     const bool isCompleted = !m_categoryInfomation[untaggedCategory].contains(untaggedTag) || !other.m_categoryInfomation[untaggedCategory].contains(untaggedTag);
0623 
0624     // Merge tags
0625     const auto categoryInfomationKeys = m_categoryInfomation.keys();
0626     QSet<QString> keys(categoryInfomationKeys.begin(), categoryInfomationKeys.end());
0627     const auto otherCategoryInfomationKeys = other.m_categoryInfomation.keys();
0628     const QSet<QString> otherCategoryInfomationKeysSet(otherCategoryInfomationKeys.begin(), otherCategoryInfomationKeys.end());
0629     keys.unite(otherCategoryInfomationKeysSet);
0630     for (const QString &key : keys) {
0631         m_categoryInfomation[key].unite(other.m_categoryInfomation[key]);
0632     }
0633 
0634     // Clear untagged tag if only one of the images was untagged
0635     if (isCompleted)
0636         m_categoryInfomation[untaggedCategory].remove(untaggedTag);
0637 
0638     // merge stacks:
0639     if (isStacked() || other.isStacked()) {
0640         DB::FileNameList stackImages;
0641         if (!isStacked())
0642             stackImages.append(fileName());
0643         else
0644             stackImages.append(DB::ImageDB::instance()->getStackFor(fileName()));
0645         stackImages.append(DB::ImageDB::instance()->getStackFor(other.fileName()));
0646 
0647         DB::ImageDB::instance()->unstack(stackImages);
0648         if (!DB::ImageDB::instance()->stack(stackImages))
0649             qCWarning(DBLog, "Could not merge stacks!");
0650     }
0651 }
0652 
0653 void DB::ImageInfo::addCategoryInfo(const QString &category, const StringSet &values)
0654 {
0655     for (StringSet::const_iterator valueIt = values.constBegin(); valueIt != values.constEnd(); ++valueIt) {
0656         if (!m_categoryInfomation[category].contains(*valueIt)) {
0657             markDirty();
0658             m_categoryInfomation[category].insert(*valueIt);
0659         }
0660     }
0661 }
0662 
0663 void DB::ImageInfo::clearAllCategoryInfo()
0664 {
0665     m_categoryInfomation.clear();
0666     m_taggedAreas.clear();
0667 }
0668 
0669 void DB::ImageInfo::removeCategoryInfo(const QString &category, const StringSet &values)
0670 {
0671     for (StringSet::const_iterator valueIt = values.constBegin(); valueIt != values.constEnd(); ++valueIt) {
0672         if (m_categoryInfomation[category].contains(*valueIt)) {
0673             markDirty();
0674             m_categoryInfomation[category].remove(*valueIt);
0675             m_taggedAreas[category].remove(*valueIt);
0676         }
0677     }
0678 }
0679 
0680 void DB::ImageInfo::addCategoryInfo(const QString &category, const QString &value, const QRect &area)
0681 {
0682     if (!m_categoryInfomation[category].contains(value)) {
0683         markDirty();
0684         m_categoryInfomation[category].insert(value);
0685 
0686         if (area.isValid()) {
0687             m_taggedAreas[category][value] = area;
0688         }
0689     }
0690 }
0691 
0692 void DB::ImageInfo::removeCategoryInfo(const QString &category, const QString &value)
0693 {
0694     if (m_categoryInfomation[category].contains(value)) {
0695         markDirty();
0696         m_categoryInfomation[category].remove(value);
0697         m_taggedAreas[category].remove(value);
0698     }
0699 }
0700 
0701 void DB::ImageInfo::setPositionedTags(const QString &category, const PositionTags &positionedTags)
0702 {
0703     markDirty();
0704     m_taggedAreas[category] = positionedTags;
0705 }
0706 
0707 bool DB::ImageInfo::updateDateInformation(int mode) const
0708 {
0709     if ((mode & EXIFMODE_DATE) == 0)
0710         return false;
0711 
0712     if ((mode & EXIFMODE_FORCE) != 0)
0713         return true;
0714 
0715     return true;
0716 }
0717 
0718 TaggedAreas DB::ImageInfo::taggedAreas() const
0719 {
0720     return m_taggedAreas;
0721 }
0722 
0723 QRect DB::ImageInfo::areaForTag(QString category, QString tag) const
0724 {
0725     // QMap::value returns a default constructed value if the key is not found:
0726     return m_taggedAreas.value(category).value(tag);
0727 }
0728 
0729 #ifdef HAVE_MARBLE
0730 Map::GeoCoordinates DB::ImageInfo::coordinates() const
0731 {
0732     if (m_coordsIsSet) {
0733         return m_coordinates;
0734     }
0735 
0736     static const int EXIF_GPS_VERSIONID = 0;
0737     static const int EXIF_GPS_LATREF = 1;
0738     static const int EXIF_GPS_LAT = 2;
0739     static const int EXIF_GPS_LONREF = 3;
0740     static const int EXIF_GPS_LON = 4;
0741     static const int EXIF_GPS_ALTREF = 5;
0742     static const int EXIF_GPS_ALT = 6;
0743 
0744     static const QString S = QString::fromUtf8("S");
0745     static const QString W = QString::fromUtf8("W");
0746 
0747     static QList<Exif::DatabaseElement *> fields;
0748     if (fields.isEmpty()) {
0749         // the order here matters! we use the named int constants afterwards to refer to them:
0750         fields.append(new Exif::IntExifElement("Exif.GPSInfo.GPSVersionID")); // actually a byte value
0751         fields.append(new Exif::StringExifElement("Exif.GPSInfo.GPSLatitudeRef"));
0752         fields.append(new Exif::RationalExifElement("Exif.GPSInfo.GPSLatitude"));
0753         fields.append(new Exif::StringExifElement("Exif.GPSInfo.GPSLongitudeRef"));
0754         fields.append(new Exif::RationalExifElement("Exif.GPSInfo.GPSLongitude"));
0755         fields.append(new Exif::IntExifElement("Exif.GPSInfo.GPSAltitudeRef")); // actually a byte value
0756         fields.append(new Exif::RationalExifElement("Exif.GPSInfo.GPSAltitude"));
0757     }
0758 
0759     // read field values from database:
0760     bool foundIt = DB::ImageDB::instance()->exifDB()->readFields(m_fileName, fields);
0761 
0762     // if the Database query result doesn't contain exif GPS info (-> upgraded exifdb from DBVersion < 2), it is null
0763     // if the result is int 0, then there's no exif gps information in the image
0764     // otherwise we can proceed to parse the information
0765     if (foundIt && fields[EXIF_GPS_VERSIONID]->value().isNull()) {
0766         auto exifDB = DB::ImageDB::instance()->exifDB();
0767         // update exif DB and repeat the search:
0768         exifDB->remove(fileName());
0769         exifDB->add(fileName());
0770 
0771         exifDB->readFields(m_fileName, fields);
0772         Q_ASSERT(!fields[EXIF_GPS_VERSIONID]->value().isNull());
0773     }
0774 
0775     Map::GeoCoordinates coords;
0776 
0777     // gps info set?
0778     // don't use the versionid field here, because some cameras use 0 as its value
0779     if (foundIt && fields[EXIF_GPS_LAT]->value().toInt() != -1.0
0780         && fields[EXIF_GPS_LON]->value().toInt() != -1.0) {
0781         // lat/lon/alt reference determines sign of float:
0782         double latr = (fields[EXIF_GPS_LATREF]->value().toString() == S) ? -1.0 : 1.0;
0783         double lat = fields[EXIF_GPS_LAT]->value().toFloat();
0784         double lonr = (fields[EXIF_GPS_LONREF]->value().toString() == W) ? -1.0 : 1.0;
0785         double lon = fields[EXIF_GPS_LON]->value().toFloat();
0786         double altr = (fields[EXIF_GPS_ALTREF]->value().toInt() == 1) ? -1.0 : 1.0;
0787         double alt = fields[EXIF_GPS_ALT]->value().toFloat();
0788 
0789         if (lat != -1.0 && lon != -1.0) {
0790             coords.setLatLon(latr * lat, lonr * lon);
0791             if (alt != 0.0f) {
0792                 coords.setAlt(altr * alt);
0793             }
0794         }
0795     }
0796 
0797     m_coordinates = coords;
0798     m_coordsIsSet = true;
0799     return m_coordinates;
0800 }
0801 
0802 #endif
0803 
0804 void ImageInfo::markDirty()
0805 {
0806     m_dirty = true;
0807     m_matchGeneration = -1;
0808 }
0809 
0810 // vi:expandtab:tabstop=4 shiftwidth=4: