diff --git a/include/OpenColorIO/OpenColorAppHelpers.h b/include/OpenColorIO/OpenColorAppHelpers.h index 9bb40e42c5..854eaf81d0 100644 --- a/include/OpenColorIO/OpenColorAppHelpers.h +++ b/include/OpenColorIO/OpenColorAppHelpers.h @@ -666,9 +666,13 @@ class OCIOEXPORT ConfigMergingParameters void setActiveDisplays(const char * displays); const char * getActiveDisplays() const; + int getNumActiveDisplays() const; + const char * getActiveDisplay(int index) const; void setActiveViews(const char * views); const char * getActiveViews() const; + int getNumActiveViews() const; + const char * getActiveView(int index) const; void setInactiveColorspaces(const char * colorspaces); const char * getInactiveColorSpaces() const; diff --git a/include/OpenColorIO/OpenColorIO.h b/include/OpenColorIO/OpenColorIO.h index 982d0facd2..5af3fb4de0 100644 --- a/include/OpenColorIO/OpenColorIO.h +++ b/include/OpenColorIO/OpenColorIO.h @@ -1086,6 +1086,11 @@ class OCIOEXPORT Config */ void setActiveDisplays(const char * displays); const char * getActiveDisplays() const; + void addActiveDisplay(const char * view); + void removeActiveDisplay(const char * view); + void clearActiveDisplays(); + const char * getActiveDisplay(int index) const; + int getNumActiveDisplays() const; /** * \brief @@ -1104,6 +1109,11 @@ class OCIOEXPORT Config */ void setActiveViews(const char * views); const char * getActiveViews() const; + void addActiveView(const char * view); + void removeActiveView(const char * view); + void clearActiveViews(); + const char * getActiveView(int index) const; + int getNumActiveViews() const; /// Get all displays in the config, ignoring the active_displays list. int getNumDisplaysAll() const noexcept; diff --git a/src/OpenColorIO/Config.cpp b/src/OpenColorIO/Config.cpp index f244ae435e..b01532e508 100644 --- a/src/OpenColorIO/Config.cpp +++ b/src/OpenColorIO/Config.cpp @@ -3411,7 +3411,7 @@ void Config::removeSharedView(const char * view) { std::ostringstream os; os << "Shared view could not be removed from config. A shared view named '" - << view << "' could be be found."; + << view << "' could not be found."; throw Exception(os.str().c_str()); } } @@ -4005,6 +4005,94 @@ const char * Config::getActiveDisplays() const return getImpl()->m_activeDisplaysStr.c_str(); } +void Config::addActiveDisplay(const char * display) +{ + if( !display || !display[0] ) + { + throw Exception("Active display could not be added to config, display name has to be a " + "non-empty name."); + } + + auto it = std::find(getImpl()->m_activeDisplays.begin(), + getImpl()->m_activeDisplays.end(), display); + + if( it != getImpl()->m_activeDisplays.end() ) + { + std::ostringstream os; + os << "Active display could not be added to config. An active display named '" + << display << "' already exists."; + throw Exception(os.str().c_str()); + } + + getImpl()->m_activeDisplays.push_back(display); + + getImpl()->m_displayCache.clear(); + + AutoMutex lock(getImpl()->m_cacheidMutex); + getImpl()->resetCacheIDs(); +} + +void Config::removeActiveDisplay(const char * display) +{ + if( !display || !display[0] ) + { + throw Exception("Active display could not be removed from config, display name has to be a " + "non-empty name."); + } + + auto it = std::find( getImpl()->m_activeDisplays.begin(), + getImpl()->m_activeDisplays.end(), display ); + + if( it != getImpl()->m_activeDisplays.end() ) + { + getImpl()->m_activeDisplays.erase(it); + } + else + { + std::ostringstream os; + os << "Active display could not be removed from config. An active display named '" + << display << "' could not be found."; + throw Exception(os.str().c_str()); + } + + getImpl()->m_displayCache.clear(); + AutoMutex lock(getImpl()->m_cacheidMutex); + getImpl()->resetCacheIDs(); +} + +void Config::clearActiveDisplays() +{ + getImpl()->m_activeDisplays.clear(); + + getImpl()->m_displayCache.clear(); + + AutoMutex lock(getImpl()->m_cacheidMutex); + getImpl()->resetCacheIDs(); +} + +const char * Config::getActiveDisplay( int index ) const +{ + if( index<0 || + index >= static_cast(getImpl()->m_activeDisplays.size())) + { + return nullptr; + } + + return getImpl()->m_activeDisplays[index].c_str(); +} + +int Config::getNumActiveDisplays() const +{ + const int numActiveDisplays = static_cast(getImpl()->m_activeDisplays.size()); + if( numActiveDisplays == 1 && + getImpl()->m_activeDisplays[0].empty() ) + { + return 0; + } + + return static_cast(getImpl()->m_activeDisplays.size()); +} + void Config::setActiveViews(const char * views) { getImpl()->m_activeViews.clear(); @@ -4022,6 +4110,92 @@ const char * Config::getActiveViews() const return getImpl()->m_activeViewsStr.c_str(); } +void Config::addActiveView(const char * view) +{ + if( !view || !view[0] ) + { + throw Exception("Active view could not be added to config, view name has to be a " + "non-empty name."); + } + + auto it = std::find(getImpl()->m_activeViews.begin(), + getImpl()->m_activeViews.end(), view); + + if( it != getImpl()->m_activeViews.end() ) + { + std::ostringstream os; + os << "Active view could not be added to config. An active view named '" + << view << "' already exists."; + throw Exception(os.str().c_str()); + } + + getImpl()->m_activeViews.push_back(view); + + getImpl()->m_displayCache.clear(); + AutoMutex lock(getImpl()->m_cacheidMutex); + getImpl()->resetCacheIDs(); +} + +void Config::removeActiveView(const char * view) +{ + if( !view || !view[0] ) + { + throw Exception("Active view could not be removed from config, view name has to be a " + "non-empty name."); + } + + auto it = std::find( getImpl()->m_activeViews.begin(), + getImpl()->m_activeViews.end(), view ); + + if(it!=getImpl()->m_activeViews.end()) + { + getImpl()->m_activeViews.erase(it); + } + else + { + std::ostringstream os; + os << "Active view could not be removed from config. An active view named '" + << view << "' could not be found."; + throw Exception(os.str().c_str()); + } + + getImpl()->m_displayCache.clear(); + AutoMutex lock(getImpl()->m_cacheidMutex); + getImpl()->resetCacheIDs(); +} + +void Config::clearActiveViews() +{ + getImpl()->m_activeViews.clear(); + + getImpl()->m_displayCache.clear(); + AutoMutex lock(getImpl()->m_cacheidMutex); + getImpl()->resetCacheIDs(); +} + +const char * Config::getActiveView( int index ) const +{ + if( index<0 || + index >= static_cast(getImpl()->m_activeViews.size())) + { + return nullptr; + } + + return getImpl()->m_activeViews[index].c_str(); +} + +int Config::getNumActiveViews() const +{ + const int numActiveViews = static_cast(getImpl()->m_activeViews.size()); + if( numActiveViews == 1 && + getImpl()->m_activeViews[0].empty() ) + { + return 0; + } + + return static_cast(getImpl()->m_activeViews.size()); +} + int Config::getNumDisplaysAll() const noexcept { return static_cast(getImpl()->m_displays.size()); diff --git a/src/OpenColorIO/OCIOYaml.cpp b/src/OpenColorIO/OCIOYaml.cpp index b1cee18982..d29cfc5b2a 100644 --- a/src/OpenColorIO/OCIOYaml.cpp +++ b/src/OpenColorIO/OCIOYaml.cpp @@ -4992,13 +4992,26 @@ inline void save(YAML::Emitter & out, const Config & config) out << YAML::Newline; out << YAML::Key << "active_displays"; StringUtils::StringVec active_displays; - if(config.getActiveDisplays() != NULL && strlen(config.getActiveDisplays()) > 0) - active_displays = SplitStringEnvStyle(config.getActiveDisplays()); + int nDisplays = config.getNumActiveDisplays(); + active_displays.reserve( nDisplays ); + for (int i = 0; i < nDisplays; i++) + { + active_displays.push_back(config.getActiveDisplay(i)); + } + + // The YAML library will wrap names that use a comma in quotes. out << YAML::Value << YAML::Flow << active_displays; + out << YAML::Key << "active_views"; StringUtils::StringVec active_views; - if(config.getActiveViews() != NULL && strlen(config.getActiveViews()) > 0) - active_views = SplitStringEnvStyle(config.getActiveViews()); + int nViews = config.getNumActiveViews(); + active_views.reserve( nViews ); + for (int i = 0; i < nViews; i++) + { + active_views.push_back(config.getActiveView(i)); + } + + // The YAML library will wrap names that use a comma in quotes. out << YAML::Value << YAML::Flow << active_views; const std::string inactiveCSs = config.getInactiveColorSpaces(); diff --git a/src/OpenColorIO/ParseUtils.cpp b/src/OpenColorIO/ParseUtils.cpp index a8a8a7a3c1..869c4f1d8c 100644 --- a/src/OpenColorIO/ParseUtils.cpp +++ b/src/OpenColorIO/ParseUtils.cpp @@ -690,33 +690,156 @@ bool StrEqualsCaseIgnore(const std::string & a, const std::string & b) return 0 == Platform::Strcasecmp(a.c_str(), b.c_str()); } +// Find the end of a name from a list contained in a string. +// The element of the list are separeted by a character defined by the sep parameter. +// The name can be surrounded by quotes to enable name including theses symbols. +static int findEndOfName(const std::string & s, size_t start, char sep) +{ + int currentPos = static_cast(start); + int nameEndPos = currentPos; + bool isEndFound = false; + + std::string symbols = "\""; + symbols += sep; + + while( !isEndFound ) + { + nameEndPos = static_cast( s.find_first_of( symbols, currentPos ) ); + if( nameEndPos == static_cast(std::string::npos) ) + { + // We reached the end of the list + nameEndPos = static_cast( s.size() ); + isEndFound = true; + } + else if( s[nameEndPos] == '"' ) + { + // We found a quote, we need to find the next one + nameEndPos = static_cast( s.find_first_of("\"", nameEndPos + 1) ); + if(nameEndPos == (int)std::string::npos) + { + // We reached the end of the list instead + std::ostringstream os; + os << "The string '" << s << "' is not correctly formatted. It is missing a closing quote."; + throw Exception(os.str().c_str()); + } + else + { + // We found the second quote, + // we need to continue the search for a symbol separating the elements (: or ,) + currentPos = nameEndPos + 1; + } + } + else if( s[nameEndPos] == sep ) + { + // We found a symbol separating the elements, we stop here + isEndFound = true; + } + } + + return nameEndPos; +} + StringUtils::StringVec SplitStringEnvStyle(const std::string & str) { - StringUtils::StringVec outputvec; const std::string s = StringUtils::Trim(str); - if (StringUtils::Find(s, ",") != std::string::npos) + if( s.size() == 0 ) { - outputvec = StringUtils::Split(s, ','); + return { "" }; } - else if (StringUtils::Find(s, ":") != std::string::npos) + + StringUtils::StringVec outputvec; + auto foundComma = s.find_first_of(","); + auto foundColon = s.find_first_of(":"); + + if( foundComma != std::string::npos || + foundColon != std::string::npos ) { - outputvec = StringUtils::Split(s, ':'); + int currentPos = 0; + while( s.size() > 0 && + currentPos <= (int) s.size() ) + { + int nameEndPos = findEndOfName( s, + currentPos, + foundComma != std::string::npos ? ',' : ':' ); + if(nameEndPos > currentPos) + { + outputvec.push_back(s.substr(currentPos, nameEndPos - currentPos)); + currentPos = nameEndPos + 1; + } + else + { + outputvec.push_back(""); + currentPos += 1; + } + } } else { + // If there is no colon either, + // we consider the string as a single element outputvec.push_back(s); } - for (auto & val : outputvec) + for ( auto & val : outputvec ) { - val = StringUtils::Trim(val); + const std::string trimmedValue = StringUtils::Trim(val); + + // If the trimmed value is surrounded by quotes, we remove them + if( trimmedValue.size() > 1 && + trimmedValue[0] == '"' && + trimmedValue[trimmedValue.size() - 1] == '"' ) + { + val = trimmedValue.substr(1, trimmedValue.size() - 2); + } + else + { + val = trimmedValue; + } } + return outputvec; } std::string JoinStringEnvStyle(const StringUtils::StringVec & outputvec) { - return StringUtils::Join(outputvec, ','); + std::string result; + const int nElement = static_cast(outputvec.size()); + if( nElement == 0 ) + { + return ""; + } + + // We check if the value contains a symbol that could be interpreted as a separator + // and if it is not already surrounded by quotes + const std::string& firstValue = outputvec[0]; + if( firstValue.find_first_of(",:") != std::string::npos && + firstValue.size() > 1 && + firstValue[0] != '"' && + firstValue[firstValue.size() - 1] != '"' ) + { + result += "\"" + firstValue + "\""; + } + else + { + result += firstValue; + } + + for( int i = 1; i < nElement; ++i ) + { + if( outputvec[i].find_first_of(",:") != std::string::npos && + outputvec[i].size() > 1 && + outputvec[i][0] != '"' && + outputvec[i][outputvec[i].size() - 1] != '"' ) + { + result += ", \"" + outputvec[i] + "\""; + } + else + { + result += ", " + outputvec[i]; + } + } + + return result; } // Return a vector of strings that are both in vec1 and vec2. diff --git a/src/OpenColorIO/apphelpers/mergeconfigs/MergeConfigsHelpers.cpp b/src/OpenColorIO/apphelpers/mergeconfigs/MergeConfigsHelpers.cpp index 0e5f770f04..1ea158254e 100644 --- a/src/OpenColorIO/apphelpers/mergeconfigs/MergeConfigsHelpers.cpp +++ b/src/OpenColorIO/apphelpers/mergeconfigs/MergeConfigsHelpers.cpp @@ -153,6 +153,16 @@ const char * ConfigMergingParameters::getActiveDisplays() const return getImpl()->m_overrideCfg->getActiveDisplays(); } +int ConfigMergingParameters::getNumActiveDisplays() const +{ + return getImpl()->m_overrideCfg->getNumActiveDisplays(); +} + +const char * ConfigMergingParameters::getActiveDisplay(int index) const +{ + return getImpl()->m_overrideCfg->getActiveDisplay(index); +} + void ConfigMergingParameters::setActiveViews(const char * views) { getImpl()->m_overrideCfg->setActiveViews(views); @@ -163,6 +173,16 @@ const char * ConfigMergingParameters::getActiveViews() const return getImpl()->m_overrideCfg->getActiveViews(); } +int ConfigMergingParameters::getNumActiveViews() const +{ + return getImpl()->m_overrideCfg->getNumActiveViews(); +} + +const char * ConfigMergingParameters::getActiveView(int index) const +{ + return getImpl()->m_overrideCfg->getActiveView(index); +} + void ConfigMergingParameters::setInactiveColorspaces(const char * colorspaces) { getImpl()->m_overrideCfg->setInactiveColorSpaces(colorspaces); diff --git a/src/OpenColorIO/apphelpers/mergeconfigs/OCIOMYaml.cpp b/src/OpenColorIO/apphelpers/mergeconfigs/OCIOMYaml.cpp index 76b0e3be74..25f6dcd086 100644 --- a/src/OpenColorIO/apphelpers/mergeconfigs/OCIOMYaml.cpp +++ b/src/OpenColorIO/apphelpers/mergeconfigs/OCIOMYaml.cpp @@ -548,15 +548,27 @@ inline void save(YAML::Emitter & out, const ConfigMerger & merger) out << YAML::Key << "active_displays"; StringUtils::StringVec active_displays; - if (p->getActiveDisplays() != NULL && strlen(p->getActiveDisplays()) > 0) - active_displays = SplitStringEnvStyle(p->getActiveDisplays()); + int nDisplays = p->getNumActiveDisplays(); + active_displays.reserve( nDisplays ); + for (int i = 0; i < nDisplays; i++) + { + active_displays.push_back(p->getActiveDisplay(i)); + } + + // The YAML library will wrap names that use a comma in quotes. out << YAML::Value << YAML::Flow << active_displays; out << YAML::Newline; out << YAML::Key << "active_views"; StringUtils::StringVec active_views; - if (p->getActiveViews() != NULL && strlen(p->getActiveViews()) > 0) - active_views = SplitStringEnvStyle(p->getActiveViews()); + int nViews = p->getNumActiveViews(); + active_views.reserve( nViews ); + for (int i = 0; i < nViews; i++) + { + active_views.push_back(p->getActiveView(i)); + } + + // The YAML library will wrap names that use a comma in quotes. out << YAML::Value << YAML::Flow << active_views; out << YAML::Key << "inactive_colorspaces"; diff --git a/tests/cpu/ParseUtils_tests.cpp b/tests/cpu/ParseUtils_tests.cpp index 42f20360c8..83a377c243 100644 --- a/tests/cpu/ParseUtils_tests.cpp +++ b/tests/cpu/ParseUtils_tests.cpp @@ -388,6 +388,10 @@ OCIO_ADD_TEST(ParseUtils, string_vec_to_int_vec) OCIO_ADD_TEST(ParseUtils, split_string_env_style) { StringUtils::StringVec outputvec; + outputvec = OCIO::SplitStringEnvStyle(""); + OCIO_CHECK_EQUAL(1, outputvec.size()); + outputvec.clear(); + outputvec = OCIO::SplitStringEnvStyle("This:is:a:test"); OCIO_CHECK_EQUAL(4, outputvec.size()); OCIO_CHECK_EQUAL("This", outputvec[0]); @@ -395,13 +399,15 @@ OCIO_ADD_TEST(ParseUtils, split_string_env_style) OCIO_CHECK_EQUAL("a", outputvec[2]); OCIO_CHECK_EQUAL("test", outputvec[3]); outputvec.clear(); - outputvec = OCIO::SplitStringEnvStyle(" This : is : a: test "); + + outputvec = OCIO::SplitStringEnvStyle(" \"This\" : is : a: test "); OCIO_CHECK_EQUAL(4, outputvec.size()); OCIO_CHECK_EQUAL("This", outputvec[0]); OCIO_CHECK_EQUAL("is", outputvec[1]); OCIO_CHECK_EQUAL("a", outputvec[2]); OCIO_CHECK_EQUAL("test", outputvec[3]); outputvec.clear(); + outputvec = OCIO::SplitStringEnvStyle(" This , is , a, test "); OCIO_CHECK_EQUAL(4, outputvec.size()); OCIO_CHECK_EQUAL("This", outputvec[0]); @@ -409,16 +415,93 @@ OCIO_ADD_TEST(ParseUtils, split_string_env_style) OCIO_CHECK_EQUAL("a", outputvec[2]); OCIO_CHECK_EQUAL("test", outputvec[3]); outputvec.clear(); + outputvec = OCIO::SplitStringEnvStyle("This:is , a:test "); OCIO_CHECK_EQUAL(2, outputvec.size()); OCIO_CHECK_EQUAL("This:is", outputvec[0]); OCIO_CHECK_EQUAL("a:test", outputvec[1]); outputvec.clear(); + outputvec = OCIO::SplitStringEnvStyle(",,"); OCIO_CHECK_EQUAL(3, outputvec.size()); OCIO_CHECK_EQUAL("", outputvec[0]); OCIO_CHECK_EQUAL("", outputvec[1]); OCIO_CHECK_EQUAL("", outputvec[2]); + + outputvec = OCIO::SplitStringEnvStyle(" \"This : is \": a: test "); + OCIO_CHECK_EQUAL(3, outputvec.size()); + OCIO_CHECK_EQUAL("This : is ", outputvec[0]); + OCIO_CHECK_EQUAL("a", outputvec[1]); + OCIO_CHECK_EQUAL("test", outputvec[2]); + + OCIO_CHECK_THROW_WHAT( OCIO::SplitStringEnvStyle(" This : is \": a: test "), + OCIO::Exception, + "The string 'This : is \": a: test' is not correctly formatted. It is missing a closing quote."); + + outputvec = OCIO::SplitStringEnvStyle(" This : is \": a: test \""); + OCIO_CHECK_EQUAL(2, outputvec.size()); + OCIO_CHECK_EQUAL("This", outputvec[0]); + OCIO_CHECK_EQUAL("is \": a: test \"" , outputvec[1]); + + outputvec = OCIO::SplitStringEnvStyle(" \"This : is \", a, test "); + OCIO_CHECK_EQUAL(3, outputvec.size()); + OCIO_CHECK_EQUAL("This : is ", outputvec[0]); + OCIO_CHECK_EQUAL("a", outputvec[1]); + OCIO_CHECK_EQUAL("test", outputvec[2]); + + // If the string contains a comma, + // it is chosen as the separator character rather than the colon + // (even if it is within quotes and therefore not used as such). + outputvec = OCIO::SplitStringEnvStyle(" \"This , is \": a: test "); + OCIO_CHECK_EQUAL(1, outputvec.size()); + OCIO_CHECK_EQUAL("\"This , is \": a: test", outputvec[0]); + + outputvec = OCIO::SplitStringEnvStyle(" \"This , is \": a, test "); + OCIO_CHECK_EQUAL(2, outputvec.size()); + OCIO_CHECK_EQUAL("\"This , is \": a", outputvec[0]); + OCIO_CHECK_EQUAL("test", outputvec[1]); + + outputvec = OCIO::SplitStringEnvStyle(" This : is \": a: test \""); + OCIO_CHECK_EQUAL(2, outputvec.size()); + OCIO_CHECK_EQUAL("This", outputvec[0]); + OCIO_CHECK_EQUAL("is \": a: test \"" , outputvec[1]); + + OCIO_CHECK_THROW_WHAT( OCIO::SplitStringEnvStyle(" This : is : a: test \""), + OCIO::Exception, + "The string 'This : is : a: test \"' is not correctly formatted. It is missing a closing quote."); +} + +OCIO_ADD_TEST(ParseUtils, join_string_env_style) +{ + StringUtils::StringVec outputvec {"This", "is", "a", "test"}; + + OCIO_CHECK_EQUAL( "This, is, a, test", OCIO::JoinStringEnvStyle(outputvec) ); + outputvec.clear(); + + OCIO_CHECK_EQUAL( "", OCIO::JoinStringEnvStyle(outputvec) ); + outputvec.clear(); + + outputvec = { "This:is", "a:test" }; + OCIO_CHECK_EQUAL( "\"This:is\", \"a:test\"", OCIO::JoinStringEnvStyle(outputvec) ); + outputvec.clear(); + + outputvec = { "", "", "" }; + OCIO_CHECK_EQUAL( ", , ", OCIO::JoinStringEnvStyle(outputvec) ); + outputvec.clear(); + + outputvec = { "This : is", "a: test" }; + OCIO_CHECK_EQUAL( "\"This : is\", \"a: test\"", OCIO::JoinStringEnvStyle(outputvec) ); + outputvec.clear(); + + outputvec = { "This", "is \": a: test" }; + OCIO_CHECK_EQUAL( "This, \"is \": a: test\"", OCIO::JoinStringEnvStyle(outputvec) ); + + outputvec = { "\"This, is, a, string\"", "this, one, too" }; + OCIO_CHECK_EQUAL( "\"This, is, a, string\", \"this, one, too\"" , OCIO::JoinStringEnvStyle(outputvec) ); + + outputvec = { "This", "is: ", "\"a very good,\"", " fine, helpful, and useful ", "test" }; + OCIO_CHECK_EQUAL( "This, \"is: \", \"a very good,\", \" fine, helpful, and useful \", test", + OCIO::JoinStringEnvStyle(outputvec) ); } OCIO_ADD_TEST(ParseUtils, intersect_string_vecs_case_ignore) diff --git a/tests/cpu/ViewingRules_tests.cpp b/tests/cpu/ViewingRules_tests.cpp index a58ffaf63f..62e94f2fd9 100644 --- a/tests/cpu/ViewingRules_tests.cpp +++ b/tests/cpu/ViewingRules_tests.cpp @@ -488,6 +488,7 @@ active_views: [] std::stringstream os; os << *config.get(); + OCIO_CHECK_EQUAL(0, config->getNumActiveDisplays()); OCIO_CHECK_EQUAL(os.str(), SIMPLE_CONFIG); // Copy to set active views.