From dcc983331dca43c660aceb231704fdeeddbf4be8 Mon Sep 17 00:00:00 2001 From: Simone Gotti Date: Tue, 12 Mar 2024 08:50:44 +0100 Subject: [PATCH 1/3] thumbnail: rename get/setStage to get/setTrashed Also return a bool in getTrashed since it's already used as a bool in setTrash and in all the checks. --- rtgui/filebrowser.cc | 24 ++++++++++++------------ rtgui/filecatalog.cc | 4 ++-- rtgui/thumbnail.cc | 18 +++++++++--------- rtgui/thumbnail.h | 4 ++-- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/rtgui/filebrowser.cc b/rtgui/filebrowser.cc index 26f0d5cd6..661b6051a 100644 --- a/rtgui/filebrowser.cc +++ b/rtgui/filebrowser.cc @@ -547,13 +547,13 @@ void FileBrowser::rightClicked () untrash->set_sensitive (false); for (size_t i = 0; i < selected.size(); i++) - if ((static_cast(selected[i]))->thumbnail->getStage()) { + if ((static_cast(selected[i]))->thumbnail->getTrashed()) { untrash->set_sensitive (true); break; } for (size_t i = 0; i < selected.size(); i++) - if (!(static_cast(selected[i]))->thumbnail->getStage()) { + if (!(static_cast(selected[i]))->thumbnail->getTrashed()) { trash->set_sensitive (true); break; } @@ -649,7 +649,7 @@ void FileBrowser::addEntry_ (FileBrowserEntry* entry) entry->addButtonSet(new FileThumbnailButtonSet(entry)); entry->getThumbButtonSet()->setRank(entry->thumbnail->getRank()); entry->getThumbButtonSet()->setColorLabel(entry->thumbnail->getColorLabel()); - entry->getThumbButtonSet()->setInTrash(entry->thumbnail->getStage()); + entry->getThumbButtonSet()->setInTrash(entry->thumbnail->getTrashed()); entry->getThumbButtonSet()->setButtonListener(this); entry->resize(getThumbnailHeight()); entry->filtered = !checkFilter(entry); @@ -1023,12 +1023,12 @@ void FileBrowser::menuItemActivated (Gtk::MenuItem* m) const auto thumbnail = mselected[i]->thumbnail; const auto rank = thumbnail->getRank(); const auto colorLabel = thumbnail->getColorLabel(); - const auto stage = thumbnail->getStage(); + const auto stage = thumbnail->getTrashed(); thumbnail->createProcParamsForUpdate (false, true); thumbnail->setRank(rank); thumbnail->setColorLabel(colorLabel); - thumbnail->setStage(stage); + thumbnail->setTrashed(stage); // Empty run to update the thumb rtengine::procparams::ProcParams params = thumbnail->getProcParams (); @@ -1558,8 +1558,8 @@ bool FileBrowser::checkFilter (ThumbBrowserEntryBase* entryb) const // true -> ((entry->thumbnail->isRecentlySaved() && filter.showRecentlySaved[0]) && !filter.showRecentlySaved[1]) || ((!entry->thumbnail->isRecentlySaved() && filter.showRecentlySaved[1]) && !filter.showRecentlySaved[0]) || - (entry->thumbnail->getStage() && !filter.showTrash) || - (!entry->thumbnail->getStage() && !filter.showNotTrash)) { + (entry->thumbnail->getTrashed() && !filter.showTrash) || + (!entry->thumbnail->getTrashed() && !filter.showNotTrash)) { return false; } @@ -1625,11 +1625,11 @@ void FileBrowser::toTrashRequested (std::vector tbe) // no need to notify listeners as item goes to trash, likely to be deleted - if (tbe[i]->thumbnail->getStage()) { + if (tbe[i]->thumbnail->getTrashed()) { continue; } - tbe[i]->thumbnail->setStage (true); + tbe[i]->thumbnail->setTrashed (true); if (tbe[i]->getThumbButtonSet()) { tbe[i]->getThumbButtonSet()->setRank (tbe[i]->thumbnail->getRank()); @@ -1649,11 +1649,11 @@ void FileBrowser::fromTrashRequested (std::vector tbe) for (size_t i = 0; i < tbe.size(); i++) { // if thumbnail was marked inTrash=true then param file must be there, no need to run customprofilebuilder - if (!tbe[i]->thumbnail->getStage()) { + if (!tbe[i]->thumbnail->getTrashed()) { continue; } - tbe[i]->thumbnail->setStage (false); + tbe[i]->thumbnail->setTrashed (false); if (tbe[i]->getThumbButtonSet()) { tbe[i]->getThumbButtonSet()->setRank (tbe[i]->thumbnail->getRank()); @@ -1784,7 +1784,7 @@ void FileBrowser::buttonPressed (LWButton* button, int actionCode, void* actionD FileBrowserEntry* entry = static_cast(actionData); tbe.push_back (entry); - if (!entry->thumbnail->getStage()) { + if (!entry->thumbnail->getTrashed()) { toTrashRequested (tbe); } else { fromTrashRequested (tbe); diff --git a/rtgui/filecatalog.cc b/rtgui/filecatalog.cc index 69b9e6dc9..8c5a70628 100644 --- a/rtgui/filecatalog.cc +++ b/rtgui/filecatalog.cc @@ -1910,7 +1910,7 @@ void FileCatalog::emptyTrash () std::vector toDel; for (const auto entry : t) { - if ((static_cast(entry))->thumbnail->getStage() == 1) { + if ((static_cast(entry))->thumbnail->getTrashed()) { toDel.push_back(static_cast(entry)); } } @@ -1926,7 +1926,7 @@ bool FileCatalog::trashIsEmpty () const auto& t = fileBrowser->getEntries(); for (const auto entry : t) { - if ((static_cast(entry))->thumbnail->getStage() == 1) { + if ((static_cast(entry))->thumbnail->getTrashed()) { return false; } } diff --git a/rtgui/thumbnail.cc b/rtgui/thumbnail.cc index 266dbacd3..0d413e6a4 100644 --- a/rtgui/thumbnail.cc +++ b/rtgui/thumbnail.cc @@ -141,7 +141,7 @@ Thumbnail::Thumbnail(CacheManager* cm, const Glib::ustring& fname, CacheImageDat // TODO? should we call notifylisterners_procParamsChanged here? setRank(cfs.rankOld); - setStage(cfs.inTrashOld); + setTrashed(cfs.inTrashOld); } delete tpp; @@ -444,7 +444,7 @@ void Thumbnail::clearProcParams (int whoClearedIt) // preserve rank, colorlabel and inTrash across clear int rank = getRank(); int colorlabel = getColorLabel(); - int inTrash = getStage(); + int inTrash = getTrashed(); cfs.recentlySaved = false; @@ -460,7 +460,7 @@ void Thumbnail::clearProcParams (int whoClearedIt) setRank(rank); pparamsValid = cfs.rating != rank; setColorLabel(colorlabel); - setStage(inTrash); + setTrashed(inTrash); // params could get validated by rank/inTrash values restored above if (pparamsValid) { @@ -563,7 +563,7 @@ void Thumbnail::setProcParams (const ProcParams& pp, ParamsEdited* pe, int whoCh // do not update rank, colorlabel and inTrash const int rank = getRank(); const int colorlabel = getColorLabel(); - const int inTrash = getStage(); + const int inTrash = getTrashed(); if (pe) { pe->combine(*pparams, pp, true); @@ -575,7 +575,7 @@ void Thumbnail::setProcParams (const ProcParams& pp, ParamsEdited* pe, int whoCh setRank(rank); setColorLabel(colorlabel); - setStage(inTrash); + setTrashed(inTrash); if (updateCacheNow) { updateCache(); @@ -1115,15 +1115,15 @@ void Thumbnail::setColorLabel (int colorlabel) } } -int Thumbnail::getStage () const +bool Thumbnail::getTrashed () const { return pparams->inTrash; } -void Thumbnail::setStage (bool stage) +void Thumbnail::setTrashed (bool trashed) { - if (pparams->inTrash != stage) { - pparams->inTrash = stage; + if (pparams->inTrash != trashed) { + pparams->inTrash = trashed; pparamsValid = true; } } diff --git a/rtgui/thumbnail.h b/rtgui/thumbnail.h index 82c4f4738..64014b0e8 100644 --- a/rtgui/thumbnail.h +++ b/rtgui/thumbnail.h @@ -155,8 +155,8 @@ public: int getColorLabel () const; void setColorLabel (int colorlabel); - int getStage () const; - void setStage (bool stage); + bool getTrashed () const; + void setTrashed (bool trashed); void addThumbnailListener (ThumbnailListener* tnl); void removeThumbnailListener (ThumbnailListener* tnl); From 68fd35d881fd1368e435bb95ccf44fc32fbc7d13 Mon Sep 17 00:00:00 2001 From: Simone Gotti Date: Tue, 12 Mar 2024 13:55:51 +0100 Subject: [PATCH 2/3] thumbnail: decouple proc params rank/color/trash Add a Properties type that contains values for rank, color and trashed Each value will also contain an edited flag. The properties variable is used to store rank, color and trashed. They are fetched from the proc params and will update the proc params when needed. This is the base for future implementations where some properties (like rank) will be also retrieved from other sources like xmp sidecar files. --- rtgui/thumbnail.cc | 118 ++++++++++++++++++++++++++------------------- rtgui/thumbnail.h | 26 ++++++++++ 2 files changed, 95 insertions(+), 49 deletions(-) diff --git a/rtgui/thumbnail.cc b/rtgui/thumbnail.cc index 0d413e6a4..763735e13 100644 --- a/rtgui/thumbnail.cc +++ b/rtgui/thumbnail.cc @@ -133,7 +133,7 @@ Thumbnail::Thumbnail(CacheManager* cm, const Glib::ustring& fname, CacheImageDat generateExifDateTimeStrings (); if (cfs.rankOld >= 0) { - // rank and inTrash were found in cache (old style), move them over to pparams + // rank and inTrash were found in cache (old style), move them over to pparams or xmp sidecar // try to load the last saved parameters from the cache or from the paramfile file createProcParamsForUpdate(false, false); // this can execute customprofilebuilder to generate param file @@ -144,6 +144,8 @@ Thumbnail::Thumbnail(CacheManager* cm, const Glib::ustring& fname, CacheImageDat setTrashed(cfs.inTrashOld); } + loadProperties(); + delete tpp; tpp = nullptr; } @@ -173,6 +175,8 @@ Thumbnail::Thumbnail(CacheManager* cm, const Glib::ustring& fname, const std::st initial_ = false; + loadProperties(); + delete tpp; tpp = nullptr; } @@ -306,7 +310,7 @@ const ProcParams& Thumbnail::getProcParamsU () * @param returnParams Ask to return a pointer to a ProcParams object if true * @param force True if the profile has to be re-generated even if it already exists * @param flaggingMode True if the ProcParams will be created because the file browser is being flagging an image - * (rang, to trash, color labels). This parameter is passed to the CPB. + * (rank, to trash, color labels). This parameter is passed to the CPB. * * @return Return a pointer to a ProcPamas structure to be updated if returnParams is true and if everything went fine, NULL otherwise. */ @@ -441,12 +445,6 @@ void Thumbnail::clearProcParams (int whoClearedIt) { MyMutex::MyLock lock(mutex); - // preserve rank, colorlabel and inTrash across clear - int rank = getRank(); - int colorlabel = getColorLabel(); - int inTrash = getTrashed(); - - cfs.recentlySaved = false; pparamsValid = false; @@ -456,13 +454,10 @@ void Thumbnail::clearProcParams (int whoClearedIt) // reset the params to defaults pparams->setDefaults(); - // and restore rank and inTrash - setRank(rank); - pparamsValid = cfs.rating != rank; - setColorLabel(colorlabel); - setTrashed(inTrash); + // preserve rank, colorlabel and inTrash across clear + updateProcParamsProperties(); - // params could get validated by rank/inTrash values restored above + // params could get validated by updateProcParamsProperties if (pparamsValid) { updateCache(); } else { @@ -560,11 +555,6 @@ void Thumbnail::setProcParams (const ProcParams& pp, ParamsEdited* pe, int whoCh return; } - // do not update rank, colorlabel and inTrash - const int rank = getRank(); - const int colorlabel = getColorLabel(); - const int inTrash = getTrashed(); - if (pe) { pe->combine(*pparams, pp, true); } else { @@ -573,9 +563,8 @@ void Thumbnail::setProcParams (const ProcParams& pp, ParamsEdited* pe, int whoCh pparamsValid = true; - setRank(rank); - setColorLabel(colorlabel); - setTrashed(inTrash); + // do not update rank, colorlabel and inTrash + updateProcParamsProperties(); if (updateCacheNow) { updateCache(); @@ -1045,7 +1034,6 @@ void Thumbnail::saveThumbnail () */ void Thumbnail::updateCache (bool updatePParams, bool updateCacheImageData) { - if (updatePParams && pparamsValid) { pparams->save ( options.saveParamsFile ? fname + paramFileExtension : "", @@ -1084,48 +1072,34 @@ void Thumbnail::setFileName (const Glib::ustring &fn) cfs.md5 = ::getMD5 (fname); } -int Thumbnail::getRank () const +int Thumbnail::getRank() const { - // prefer the user-set rank over the embedded Rating - // pparams->rank == -1 means that there is no saved rank yet, so we should - // next look for the embedded Rating metadata. - if (pparams->rank != -1) { - return pparams->rank; - } else { - return cfs.rating; - } + return properties.rank; } -void Thumbnail::setRank (int rank) +void Thumbnail::setRank(int rank) { - pparams->rank = rank; - pparamsValid = true; + properties.rank = rank; } -int Thumbnail::getColorLabel () const +int Thumbnail::getColorLabel() const { - return pparams->colorlabel; + return properties.color; } -void Thumbnail::setColorLabel (int colorlabel) +void Thumbnail::setColorLabel(int colorlabel) { - if (pparams->colorlabel != colorlabel) { - pparams->colorlabel = colorlabel; - pparamsValid = true; - } + properties.color = colorlabel; } -bool Thumbnail::getTrashed () const +bool Thumbnail::getTrashed() const { - return pparams->inTrash; + return properties.trashed; } -void Thumbnail::setTrashed (bool trashed) +void Thumbnail::setTrashed(bool trashed) { - if (pparams->inTrash != trashed) { - pparams->inTrash = trashed; - pparamsValid = true; - } + properties.trashed = trashed; } void Thumbnail::addThumbnailListener (ThumbnailListener* tnl) @@ -1239,6 +1213,52 @@ void Thumbnail::getCamWB(double& temp, double& green, rtengine::StandardObserver } } +void Thumbnail::loadProperties() +{ + properties = Properties(); + + // get initial rank from cache or image metadata + if (cfs.exifValid) { + properties.rank.value = rtengine::LIM(cfs.getRating(), 0, 5); + } else { + const std::unique_ptr md(rtengine::FramesMetaData::fromFile(fname)); + if (md && md->hasExif()) { + properties.rank.value = rtengine::LIM(md->getRating(), 0, 5); + } + } + + // update rank and load color, trash from procparams + if (pparamsValid) { + if (pparams->rank >= 0) { + properties.rank.value = pparams->rank; + } + properties.color.value = pparams->colorlabel; + properties.trashed.value = pparams->inTrash; + } +} + +void Thumbnail::updateProcParamsProperties() +{ + if (!properties.edited()) { + return; + } + + if (properties.trashed.edited && properties.trashed != pparams->inTrash) { + pparams->inTrash = properties.trashed; + pparamsValid = true; + } + + if (properties.rank.edited && properties.rank != pparams->rank) { + pparams->rank = properties.rank; + pparamsValid = true; + } + + if (properties.color.edited && properties.color != pparams->colorlabel) { + pparams->colorlabel = properties.color; + pparamsValid = true; + } +} + void Thumbnail::saveMetadata() { if (options.rtSettings.metadata_xmp_sync != rtengine::Settings::MetadataXmpSync::READ_WRITE) { diff --git a/rtgui/thumbnail.h b/rtgui/thumbnail.h index 64014b0e8..e24be18f5 100644 --- a/rtgui/thumbnail.h +++ b/rtgui/thumbnail.h @@ -78,6 +78,30 @@ class Thumbnail bool initial_; + // Properties holds values and edited states for rank, color and trashed + struct Properties { + template struct Property { + T value; + bool edited; + Property(T v): value(v), edited(false) {} + Property& operator=(T v) + { + value = v; + edited = true; + return *this; + } + operator T() const { return value; } + }; + Property rank; + Property color; + Property trashed; + + explicit Properties(int r=0, int c=0, bool t=false): + rank(r), color(c), trashed(t) {} + bool edited() const { return rank.edited || color.edited || trashed.edited; } + }; + Properties properties; + // vector of listeners std::vector listeners; @@ -90,6 +114,8 @@ class Thumbnail Glib::ustring getCacheFileName (const Glib::ustring& subdir, const Glib::ustring& fext) const; void saveMetadata(); + void loadProperties(); + void updateProcParamsProperties(); public: Thumbnail (CacheManager* cm, const Glib::ustring& fname, CacheImageData* cf); From 1098966a8bbe92e390c4b688eeae23491530832b Mon Sep 17 00:00:00 2001 From: Simone Gotti Date: Tue, 12 Mar 2024 18:12:49 +0100 Subject: [PATCH 3/3] Add optional image rank/color load/save from/to xmp sidecar Add optional ability to load/save image rank property from/to xmp sidecar "xmp.Rating" and color property from xmp "xmp.Label" ignoring the ones provided in the processing params file. This behavior is disabled by default and an option under settings -> file browser has been added to enable it. When enabled: * On load: * rank and color are not read from processing params. * rank is mapped from xmp sidecar file rating entry. * color is mapped from xmp sidecar file label entry. * On save: * rank and color are saved to the xmp sidecar * rank and color are also saved to the processing param (pp3) files to keep them in sync Rating mapping: Since rating can be also -1 but rank only goes from 0 to 5, the -1 value is ignored like already done when importing from embedded xmp data. Color mapping: XMP has no color concept, usually programs like digikam uses the label field to write a color name ("Red", "Orange"). The problem is that this isn't standardized and label can be any string. Additionally Rawtherapee has 5 specific colors while other programs can have different colors with different name so they won't be shown if they don't map to the 5 color names supported by rawtherapee. On save only the 5 color supported by rawtherapee wil be saved. Trash is kept only in the profile files for multiple reasons: * There's no trash concept in xmp, there's the rejected concept assigned to a rating == -1. * We could map rejected to trash but in rawtherapee rank and trash are two different values and an image can have both rank >= 0 and trashed set to true. Using an unique value like rating for rank and trash (when -1) will require changing the current rawtherapee logic. * Also digikam only handles ratings from 0 to 5 (no -1) and handles trash in its own internal way without reflecting it in the xmp sidecar. --- rtdata/languages/default | 1 + rtengine/metadata.cc | 13 ----- rtengine/metadata.h | 14 +++++ rtgui/options.cc | 18 +++++++ rtgui/options.h | 6 +++ rtgui/preferences.cc | 7 +++ rtgui/preferences.h | 2 + rtgui/thumbnail.cc | 107 +++++++++++++++++++++++++++++++++++++-- rtgui/thumbnail.h | 1 + 9 files changed, 152 insertions(+), 17 deletions(-) diff --git a/rtdata/languages/default b/rtdata/languages/default index a6a8cdc5c..a6897f316 100644 --- a/rtdata/languages/default +++ b/rtdata/languages/default @@ -2033,6 +2033,7 @@ PREFERENCES_THUMBNAIL_INSPECTOR_JPEG;Embedded JPEG preview PREFERENCES_THUMBNAIL_INSPECTOR_MODE;Image to show PREFERENCES_THUMBNAIL_INSPECTOR_RAW;Neutral raw rendering PREFERENCES_THUMBNAIL_INSPECTOR_RAW_IF_NO_JPEG_FULLSIZE;Embedded JPEG if fullsize, neutral raw otherwise +PREFERENCES_THUMBNAIL_RANK_COLOR_MODE;Load/Save thumbnail rank and color from/to XMP sidecars PREFERENCES_TOOLPANEL_AVAILABLETOOLS;Available Tools PREFERENCES_TOOLPANEL_CLONE_FAVORITES;Keep favorite tools in original locations PREFERENCES_TOOLPANEL_CLONE_FAVORITES_TOOLTIP;If set, favorite tools will appear in both the favorites tab and their original tabs.\n\nNote: Enabling this option may result in a slight delay when switching tabs. diff --git a/rtengine/metadata.cc b/rtengine/metadata.cc index 0a55c1424..df8988f5a 100644 --- a/rtengine/metadata.cc +++ b/rtengine/metadata.cc @@ -106,19 +106,6 @@ void clear_metadata_key(Data &data, const Key &key) } } -template -auto to_long(const Iterator &iter, Integer n = Integer{0}) -> decltype( -#if EXIV2_TEST_VERSION(0,28,0) - iter->toInt64() -) { - return iter->toInt64(n); -#else - iter->toLong() -) { - return iter->toLong(n); -#endif -} - } // namespace diff --git a/rtengine/metadata.h b/rtengine/metadata.h index 7424b2720..75d37d46c 100644 --- a/rtengine/metadata.h +++ b/rtengine/metadata.h @@ -97,4 +97,18 @@ private: static std::unique_ptr cache_; }; +template +auto to_long(const Iterator &iter, Integer n = Integer{0}) -> decltype( +#if EXIV2_TEST_VERSION(0,28,0) + iter->toInt64() +) { + return iter->toInt64(n); +#else + iter->toLong() +) { + return iter->toLong(n); +#endif +} + + } // namespace rtengine diff --git a/rtgui/options.cc b/rtgui/options.cc index c8750809b..522bcc26a 100644 --- a/rtgui/options.cc +++ b/rtgui/options.cc @@ -694,6 +694,7 @@ void Options::setDefaults() lastICCProfCreatorDir = ""; gimpPluginShowInfoDialog = true; maxRecentFolders = 15; + thumbnailRankColorMode = Options::ThumbnailPropertyMode::PROCPARAMS; sortMethod = SORT_BY_NAME; sortDescending = false; rtSettings.lensfunDbDirectory = ""; // set also in main.cc and main-cli.cc @@ -1366,6 +1367,15 @@ void Options::readFromFile(Glib::ustring fname) if (keyFile.has_key("File Browser", "BrowseRecursiveFollowLinks")) { browseRecursiveFollowLinks = keyFile.get_boolean("File Browser", "BrowseRecursiveFollowLinks"); } + + if (keyFile.has_key("File Browser", "ThumbnailRankColorMode")) { + std::string val = keyFile.get_string("File Browser", "ThumbnailRankColorMode"); + if (val == "xmp") { + thumbnailRankColorMode = ThumbnailPropertyMode::XMP; + } else { + thumbnailRankColorMode = ThumbnailPropertyMode::PROCPARAMS; + } + } } if (keyFile.has_group("Clipping Indication")) { @@ -2462,6 +2472,14 @@ void Options::saveToFile(Glib::ustring fname) keyFile.set_string_list("File Browser", "RecentFolders", temp); } + switch (thumbnailRankColorMode) { + case ThumbnailPropertyMode::XMP: + keyFile.set_string("File Browser", "ThumbnailRankColorMode", "xmp"); + break; + default: // ThumbnailPropertyMode::PROCPARAMS + keyFile.set_string("File Browser", "ThumbnailRankColorMode", "procparams"); + break; + } keyFile.set_integer("File Browser", "SortMethod", sortMethod); keyFile.set_boolean("File Browser", "SortDescending", sortDescending); keyFile.set_boolean("File Browser", "BrowseRecursive", browseRecursive); diff --git a/rtgui/options.h b/rtgui/options.h index 2b5df3aa6..d65013dac 100644 --- a/rtgui/options.h +++ b/rtgui/options.h @@ -492,6 +492,12 @@ public: size_t maxRecentFolders; // max. number of recent folders stored in options file std::vector recentFolders; // List containing all recent folders + enum class ThumbnailPropertyMode { + PROCPARAMS, // store rank and color in procparams sidecars + XMP // store rank and color xmp sidecar + }; + ThumbnailPropertyMode thumbnailRankColorMode; + enum SortMethod { SORT_BY_NAME, SORT_BY_DATE, diff --git a/rtgui/preferences.cc b/rtgui/preferences.cc index 527800cb6..eddfc561e 100644 --- a/rtgui/preferences.cc +++ b/rtgui/preferences.cc @@ -1470,6 +1470,9 @@ Gtk::Widget* Preferences::getFileBrowserPanel() vbro->pack_start(*sameThumbSize, Gtk::PACK_SHRINK, 0); vbro->pack_start(*ckbInternalThumbIfUntouched, Gtk::PACK_SHRINK, 0); + thumbnailRankColorMode = Gtk::manage(new Gtk::CheckButton(M("PREFERENCES_THUMBNAIL_RANK_COLOR_MODE"))); + vbro->pack_start(*thumbnailRankColorMode, Gtk::PACK_SHRINK, 0); + Gtk::Box* hbrecent = Gtk::manage(new Gtk::Box()); Gtk::Label* labrecent = Gtk::manage (new Gtk::Label (M("PREFERENCES_MAXRECENTFOLDERS") + ":", Gtk::ALIGN_START)); maxRecentFolders = Gtk::manage(new Gtk::SpinButton()); @@ -2029,6 +2032,8 @@ void Preferences::storePreferences() moptions.rtSettings.metadata_xmp_sync = rtengine::Settings::MetadataXmpSync(metadataSyncCombo->get_active_row_number()); moptions.rtSettings.xmp_sidecar_style = rtengine::Settings::XmpSidecarStyle(xmpSidecarCombo->get_active_row_number()); + + moptions.thumbnailRankColorMode = thumbnailRankColorMode->get_active() ? Options::ThumbnailPropertyMode::XMP : Options::ThumbnailPropertyMode::PROCPARAMS; } void Preferences::fillPreferences() @@ -2287,6 +2292,8 @@ void Preferences::fillPreferences() metadataSyncCombo->set_active(int(moptions.rtSettings.metadata_xmp_sync)); xmpSidecarCombo->set_active(int(moptions.rtSettings.xmp_sidecar_style)); + + thumbnailRankColorMode->set_active(moptions.thumbnailRankColorMode == Options::ThumbnailPropertyMode::XMP); } /* diff --git a/rtgui/preferences.h b/rtgui/preferences.h index b34a8348f..191f122f5 100644 --- a/rtgui/preferences.h +++ b/rtgui/preferences.h @@ -224,6 +224,8 @@ class Preferences final : Gtk::CheckButton* ckbInternalThumbIfUntouched; + Gtk::CheckButton *thumbnailRankColorMode; + Gtk::Entry* txtCustProfBuilderPath; Gtk::ComboBoxText* custProfBuilderLabelType; diff --git a/rtgui/thumbnail.cc b/rtgui/thumbnail.cc index 763735e13..7ee4d3da1 100644 --- a/rtgui/thumbnail.cc +++ b/rtgui/thumbnail.cc @@ -105,6 +105,46 @@ bool CPBDump( return true; } +struct ColorMapper { + std::map indexLabelMap; + std::map labelIndexMap; + + ColorMapper(std::map colors) { + for (const auto& color: colors) { + indexLabelMap.insert({color.first, color.second}); + labelIndexMap.insert({color.second, color.first}); + } + } + + int index(const std::string &label) const + { + auto it = labelIndexMap.find(label); + if (it != labelIndexMap.end()) { + return it->second; + } + return 0; + } + + std::string label(int index) const + { + auto it = indexLabelMap.find(index); + if (it != indexLabelMap.end()) { + return it->second; + } + return ""; + } +}; + +const std::map defaultColors = { + {1, "Red"}, + {2, "Yellow"}, + {3, "Green"}, + {4, "Blue"}, + {5, "Purple"} +}; + +auto defaultColorMapper = ColorMapper(defaultColors); + } // namespace using namespace rtengine::procparams; @@ -1034,6 +1074,8 @@ void Thumbnail::saveThumbnail () */ void Thumbnail::updateCache (bool updatePParams, bool updateCacheImageData) { + updateProcParamsProperties(); + if (updatePParams && pparamsValid) { pparams->save ( options.saveParamsFile ? fname + paramFileExtension : "", @@ -1049,6 +1091,8 @@ void Thumbnail::updateCache (bool updatePParams, bool updateCacheImageData) if (updatePParams && pparamsValid) { saveMetadata(); } + + saveXMPSidecarProperties(); } Thumbnail::~Thumbnail () @@ -1227,13 +1271,37 @@ void Thumbnail::loadProperties() } } - // update rank and load color, trash from procparams + // update rank and color from procparams or xmp sidecar + // load trash from procparams if (pparamsValid) { - if (pparams->rank >= 0) { - properties.rank.value = pparams->rank; + if (options.thumbnailRankColorMode == Options::ThumbnailPropertyMode::PROCPARAMS) { + if (pparams->rank >= 0) { + properties.rank.value = pparams->rank; + } } - properties.color.value = pparams->colorlabel; + properties.trashed.value = pparams->inTrash; + properties.color.value = pparams->colorlabel; + } + + if (options.thumbnailRankColorMode == Options::ThumbnailPropertyMode::XMP) { + try { + auto xmp = rtengine::Exiv2Metadata::getXmpSidecar(fname); + auto pos = xmp.findKey(Exiv2::XmpKey("Xmp.xmp.Rating")); + if (pos != xmp.end()) { + int r = rtengine::to_long(pos); + properties.rank.value = rtengine::LIM(r, 0, 5); + } + + pos = xmp.findKey(Exiv2::XmpKey("Xmp.xmp.Label")); + if (pos != xmp.end()) { + properties.color.value = defaultColorMapper.index(pos->toString()); + } + } catch (std::exception &exc) { + std::cerr << "ERROR loading thumbnail properties data from " + << rtengine::Exiv2Metadata::xmpSidecarPath(fname) + << ": " << exc.what() << std::endl; + } } } @@ -1248,6 +1316,8 @@ void Thumbnail::updateProcParamsProperties() pparamsValid = true; } + // save procparams rank and color also when options.thumbnailRankColorMode == Options::ThumbnailPropertyMode::XMP + // so they'll be kept in sync if (properties.rank.edited && properties.rank != pparams->rank) { pparams->rank = properties.rank; pparamsValid = true; @@ -1259,6 +1329,35 @@ void Thumbnail::updateProcParamsProperties() } } +void Thumbnail::saveXMPSidecarProperties() +{ + if (!properties.edited()) { + return; + } + + if (options.thumbnailRankColorMode != Options::ThumbnailPropertyMode::XMP) { + return; + } + + auto fn = rtengine::Exiv2Metadata::xmpSidecarPath(fname); + try { + auto xmp = rtengine::Exiv2Metadata::getXmpSidecar(fname); + if (properties.rank.edited) { + xmp["Xmp.xmp.Rating"] = std::to_string(properties.rank); + } + if (properties.color.edited) { + xmp["Xmp.xmp.Label"] = defaultColorMapper.label(properties.color); + } + + rtengine::Exiv2Metadata meta; + meta.xmpData() = std::move(xmp); + meta.saveToXmp(fn); + } catch (std::exception &exc) { + std::cerr << "ERROR saving thumbnail properties data to " << fn + << ": " << exc.what() << std::endl; + } +} + void Thumbnail::saveMetadata() { if (options.rtSettings.metadata_xmp_sync != rtengine::Settings::MetadataXmpSync::READ_WRITE) { diff --git a/rtgui/thumbnail.h b/rtgui/thumbnail.h index e24be18f5..c1e52760c 100644 --- a/rtgui/thumbnail.h +++ b/rtgui/thumbnail.h @@ -116,6 +116,7 @@ class Thumbnail void saveMetadata(); void loadProperties(); void updateProcParamsProperties(); + void saveXMPSidecarProperties(); public: Thumbnail (CacheManager* cm, const Glib::ustring& fname, CacheImageData* cf);