diff --git a/Fwk/AppFwk/cafProjectDataModel/cafPdmCore/CMakeLists.txt b/Fwk/AppFwk/cafProjectDataModel/cafPdmCore/CMakeLists.txt index 80c3ebadf0b..4a4292d4c90 100644 --- a/Fwk/AppFwk/cafProjectDataModel/cafPdmCore/CMakeLists.txt +++ b/Fwk/AppFwk/cafProjectDataModel/cafPdmCore/CMakeLists.txt @@ -17,6 +17,7 @@ qt_standard_project_setup() set(PROJECT_FILES cafAssert.h cafAppEnum.h + cafAppEnumMapper.h cafClassTypeName.h cafPdmBase.h cafPdmChildArrayField.h diff --git a/Fwk/AppFwk/cafProjectDataModel/cafPdmCore/cafAppEnum.h b/Fwk/AppFwk/cafProjectDataModel/cafPdmCore/cafAppEnum.h index 7263c6fdcd3..b753cc46ebc 100644 --- a/Fwk/AppFwk/cafProjectDataModel/cafPdmCore/cafAppEnum.h +++ b/Fwk/AppFwk/cafProjectDataModel/cafPdmCore/cafAppEnum.h @@ -42,6 +42,7 @@ #include #include +#include "cafAppEnumMapper.h" #include #include @@ -123,7 +124,7 @@ template class AppEnum : public AppEnumInterface { public: - AppEnum() { m_value = EnumMapper::instance()->defaultValue(); } + AppEnum() { m_value = AppEnumMapper::instance()->defaultValue(); } AppEnum( T value ) : m_value( value ) { @@ -145,59 +146,60 @@ class AppEnum : public AppEnumInterface operator T() const { return m_value; } T value() const { return m_value; } - size_t index() const { return EnumMapper::instance()->index( m_value ); } - QString text() const { return EnumMapper::instance()->text( m_value ); } - QString uiText() const { return EnumMapper::instance()->uiText( m_value ); } + size_t index() const { return AppEnumMapper::instance()->index( m_value ); } + QString text() const { return AppEnumMapper::instance()->text( m_value ); } + QString uiText() const { return AppEnumMapper::instance()->uiText( m_value ); } AppEnum& operator=( T value ) { m_value = value; return *this; } - bool setFromText( const QString& text ) { return EnumMapper::instance()->enumVal( m_value, text ); } - bool setFromIndex( size_t index ) { return EnumMapper::instance()->enumVal( m_value, index ); } + bool setFromText( const QString& text ) { return AppEnumMapper::instance()->enumVal( m_value, text ); } + bool setFromIndex( size_t index ) { return AppEnumMapper::instance()->enumVal( m_value, index ); } QString textForSerialization() const override { return text(); } void setTextForSerialization( const QString& text ) override { setFromText( text ); } // Static interface to access the properties of the enum definition - static bool isValid( const QString& text ) { return EnumMapper::instance()->isValid( text ); } - static bool isValid( size_t index ) { return index < EnumMapper::instance()->size(); } - static size_t size() { return EnumMapper::instance()->size(); } + static bool isValid( const QString& text ) { return AppEnumMapper::instance()->isValid( text ); } + static bool isValid( size_t index ) { return index < AppEnumMapper::instance()->size(); } + static size_t size() { return AppEnumMapper::instance()->size(); } - static QStringList uiTexts() { return EnumMapper::instance()->uiTexts(); } + static QStringList uiTexts() { return AppEnumMapper::instance()->uiTexts(); } static T fromIndex( size_t idx ) { T val; - EnumMapper::instance()->enumVal( val, idx ); + AppEnumMapper::instance()->enumVal( val, idx ); return val; } static T fromText( const QString& text ) { T val; - EnumMapper::instance()->enumVal( val, text ); + AppEnumMapper::instance()->enumVal( val, text ); return val; } - static size_t index( T enumValue ) { return EnumMapper::instance()->index( enumValue ); } - static QString text( T enumValue ) { return EnumMapper::instance()->text( enumValue ); } + static size_t index( T enumValue ) { return AppEnumMapper::instance()->index( enumValue ); } + static QString text( T enumValue ) { return AppEnumMapper::instance()->text( enumValue ); } static QString textFromIndex( size_t idx ) { return text( fromIndex( idx ) ); } - static QString uiText( T enumValue ) { return EnumMapper::instance()->uiText( enumValue ); } + static QString uiText( T enumValue ) { return AppEnumMapper::instance()->uiText( enumValue ); } static QString uiTextFromIndex( size_t idx ) { return uiText( fromIndex( idx ) ); } -private: //================================================================================================== /// The setup method is supposed to be specialized for each and every type instantiation of this class, /// and is supposed to set up the mapping between enum values, text and ui-text using the \m addItem /// method. It may also set a default value using \m setDefault //================================================================================================== static void setUp(); + +private: static void addItem( T enumVal, const QString& text, const QString& uiText, const QStringList& aliases = {} ) { - EnumMapper::instance()->addItem( enumVal, text, uiText, aliases ); + AppEnumMapper::instance()->addItem( enumVal, text, uiText, aliases ); } - static void setDefault( T defaultEnumValue ) { EnumMapper::instance()->setDefault( defaultEnumValue ); } + static void setDefault( T defaultEnumValue ) { AppEnumMapper::instance()->setDefault( defaultEnumValue ); } T m_value; @@ -209,176 +211,6 @@ class AppEnum : public AppEnumInterface /// but AppEnum implementation gets nicer this way. /// The real core of this class is the vector map member and the static instance method //================================================================================================== - - class EnumMapper - { - private: - class EnumData - { - public: - EnumData( T enumVal, const QString& text, const QString& uiText, const QStringList& aliases ) - : m_enumVal( enumVal ) - , m_text( text ) - , m_uiText( uiText ) - , m_aliases( aliases ) - { - } - - bool isMatching( const QString& text ) const { return ( text == m_text || m_aliases.contains( text ) ); } - - T m_enumVal; - QString m_text; - QString m_uiText; - QStringList m_aliases; - }; - - public: - void addItem( T enumVal, const QString& text, QString uiText, const QStringList& aliases ) - { - // Make sure the alias text is unique for enum - for ( const auto& alias : aliases ) - { - for ( const auto& enumData : instance()->m_mapping ) - { - CAF_ASSERT( !enumData.isMatching( alias ) ); - } - } - - // Make sure the text is trimmed, as this text is streamed to XML and will be trimmed when read back - // from XML text https://github.com/OPM/ResInsight/issues/7829 - instance()->m_mapping.push_back( EnumData( enumVal, text.trimmed(), uiText, aliases ) ); - } - - static EnumMapper* instance() - { - static EnumMapper storedInstance; - static bool isInitialized = false; - if ( !isInitialized ) - { - isInitialized = true; - AppEnum::setUp(); - } - return &storedInstance; - } - - void setDefault( T defaultEnumValue ) - { - m_defaultValue = defaultEnumValue; - m_defaultValueIsSet = true; - } - - T defaultValue() const - { - if ( m_defaultValueIsSet ) - { - return m_defaultValue; - } - else - { - // CAF_ASSERT(m_mapping.size()); - return m_mapping[0].m_enumVal; - } - } - - bool isValid( const QString& text ) const - { - size_t idx; - for ( idx = 0; idx < m_mapping.size(); ++idx ) - { - if ( text == m_mapping[idx].m_text ) return true; - } - - return false; - } - - size_t size() const { return m_mapping.size(); } - - bool enumVal( T& value, const QString& text ) const - { - value = defaultValue(); - - QString trimmedText = text.trimmed(); - - for ( size_t idx = 0; idx < m_mapping.size(); ++idx ) - { - // Make sure the text parsed from a text stream is trimmed - // https://github.com/OPM/ResInsight/issues/7829 - if ( m_mapping[idx].isMatching( trimmedText ) ) - { - value = m_mapping[idx].m_enumVal; - return true; - } - } - return false; - } - - bool enumVal( T& value, size_t index ) const - { - value = defaultValue(); - if ( index < m_mapping.size() ) - { - value = m_mapping[index].m_enumVal; - return true; - } - else - return false; - } - - size_t index( T enumValue ) const - { - size_t idx; - for ( idx = 0; idx < m_mapping.size(); ++idx ) - { - if ( enumValue == m_mapping[idx].m_enumVal ) return idx; - } - - return idx; - } - - QString uiText( T value ) const - { - size_t idx; - for ( idx = 0; idx < m_mapping.size(); ++idx ) - { - if ( value == m_mapping[idx].m_enumVal ) return m_mapping[idx].m_uiText; - } - return ""; - } - - QStringList uiTexts() const - { - QStringList uiTextList; - size_t idx; - for ( idx = 0; idx < m_mapping.size(); ++idx ) - { - uiTextList.append( m_mapping[idx].m_uiText ); - } - return uiTextList; - } - - QString text( T value ) const - { - size_t idx; - for ( idx = 0; idx < m_mapping.size(); ++idx ) - { - if ( value == m_mapping[idx].m_enumVal ) return m_mapping[idx].m_text; - } - return ""; - } - - private: - EnumMapper() - : m_defaultValue( T() ) - , m_defaultValueIsSet( false ) - { - } - - friend class AppEnum; - - std::vector m_mapping; - T m_defaultValue; - bool m_defaultValueIsSet; - }; }; template diff --git a/Fwk/AppFwk/cafProjectDataModel/cafPdmCore/cafAppEnumMapper.h b/Fwk/AppFwk/cafProjectDataModel/cafPdmCore/cafAppEnumMapper.h new file mode 100644 index 00000000000..a440b472927 --- /dev/null +++ b/Fwk/AppFwk/cafProjectDataModel/cafPdmCore/cafAppEnumMapper.h @@ -0,0 +1,260 @@ +//################################################################################################## +// +// Custom Visualization Core library +// Copyright (C) 2011-2013 Ceetron AS +// +// This library may be used under the terms of either the GNU General Public License or +// the GNU Lesser General Public License as follows: +// +// GNU General Public License Usage +// This library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// See the GNU General Public License at <> +// for more details. +// +// GNU Lesser General Public License Usage +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// See the GNU Lesser General Public License at <> +// for more details. +// +//################################################################################################## + +#pragma once + +#include "cafAssert.h" + +#include +#include + +#include +#include + +namespace caf +{ + +// Forward declaration +template +class AppEnum; + +//================================================================================================== +/// A private class to handle the instance of the mapping vector for AppEnum. +//================================================================================================== +template +class AppEnumMapper +{ +private: + class EnumData + { + public: + EnumData( T enumVal, const QString& text, const QString& uiText, const QStringList& aliases ) + : m_enumVal( enumVal ) + , m_text( text ) + , m_uiText( uiText ) + , m_aliases( aliases ) + { + } + + bool isMatching( const QString& text ) const noexcept + { + return ( text == m_text || m_aliases.contains( text ) ); + } + + T m_enumVal; + QString m_text; + QString m_uiText; + QStringList m_aliases; + }; + +public: + void addItem( T enumVal, const QString& text, const QString& uiText, const QStringList& aliases ) + { + // Pre-validate input to avoid partial state on failure + const QString trimmedText = text.trimmed(); + if ( trimmedText.isEmpty() ) + { + CAF_ASSERT( false && "Text cannot be empty" ); + return; + } + + // Check for duplicate main text + auto textExists = std::any_of( m_mapping.cbegin(), + m_mapping.cend(), + [&trimmedText]( const EnumData& enumData ) + { return trimmedText == enumData.m_text; } ); + CAF_ASSERT( !textExists && "Duplicate text found" ); + + // Make sure the alias text is unique for enum + for ( const auto& alias : aliases ) + { + auto aliasExists = std::any_of( m_mapping.cbegin(), + m_mapping.cend(), + [&alias]( const EnumData& enumData ) { return enumData.isMatching( alias ); } ); + CAF_ASSERT( !aliasExists && "Duplicate alias found" ); + } + + // Check for duplicate enum value + auto enumExists = std::any_of( m_mapping.cbegin(), + m_mapping.cend(), + [enumVal]( const EnumData& enumData ) { return enumVal == enumData.m_enumVal; } ); + CAF_ASSERT( !enumExists && "Duplicate enum value found" ); + + // Reserve space to avoid potential reallocation during emplace_back + if ( m_mapping.size() == m_mapping.capacity() ) + { + m_mapping.reserve( m_mapping.size() * 2 + 1 ); + } + + // Make sure the text is trimmed, as this text is streamed to XML and will be trimmed when read back + // from XML text https://github.com/OPM/ResInsight/issues/7829 + m_mapping.emplace_back( enumVal, trimmedText, uiText, aliases ); + } + + static AppEnumMapper* instance() + { + static AppEnumMapper storedInstance; + static bool isInitialized = false; + if ( !isInitialized ) + { + isInitialized = true; + AppEnum::setUp(); + } + return &storedInstance; + } + + void setDefault( T defaultEnumValue ) noexcept + { + m_defaultValue = defaultEnumValue; + m_defaultValueIsSet = true; + } + + T defaultValue() const + { + if ( m_defaultValueIsSet ) + { + return m_defaultValue; + } + else if ( !m_mapping.empty() ) + { + return m_mapping[0].m_enumVal; + } + else + { + CAF_ASSERT( false && "No mapping available and no default set" ); + return T(); + } + } + + bool isValid( const QString& text ) const + { + auto it = std::find_if( m_mapping.cbegin(), + m_mapping.cend(), + [&text]( const EnumData& enumData ) { return text == enumData.m_text; } ); + return it != m_mapping.cend(); + } + + size_t size() const noexcept { return m_mapping.size(); } + bool empty() const noexcept { return m_mapping.empty(); } + + bool enumVal( T& value, const QString& text ) const + { + const QString trimmedText = text.trimmed(); + + auto it = std::find_if( m_mapping.cbegin(), + m_mapping.cend(), + [&trimmedText]( const EnumData& enumData ) { return enumData.isMatching( trimmedText ); } ); + + if ( it != m_mapping.cend() ) + { + value = it->m_enumVal; + return true; + } + else + { + value = defaultValue(); + return false; + } + } + + bool enumVal( T& value, size_t index ) const + { + if ( index < m_mapping.size() ) + { + value = m_mapping[index].m_enumVal; + return true; + } + else + { + value = defaultValue(); + return false; + } + } + + size_t index( T enumValue ) const + { + auto it = std::find_if( m_mapping.cbegin(), + m_mapping.cend(), + [enumValue]( const EnumData& enumData ) { return enumValue == enumData.m_enumVal; } ); + + return it != m_mapping.cend() ? static_cast( std::distance( m_mapping.cbegin(), it ) ) : m_mapping.size(); + } + + QString uiText( T value ) const + { + auto it = std::find_if( m_mapping.cbegin(), + m_mapping.cend(), + [value]( const EnumData& enumData ) { return value == enumData.m_enumVal; } ); + + return it != m_mapping.cend() ? it->m_uiText : QString(); + } + + QStringList uiTexts() const + { + QStringList uiTextList; + uiTextList.reserve( static_cast( m_mapping.size() ) ); + + for ( const auto& enumData : m_mapping ) + { + uiTextList.append( enumData.m_uiText ); + } + return uiTextList; + } + + QString text( T value ) const + { + auto it = std::find_if( m_mapping.cbegin(), + m_mapping.cend(), + [value]( const EnumData& enumData ) { return value == enumData.m_enumVal; } ); + + return it != m_mapping.cend() ? it->m_text : QString(); + } + +private: + AppEnumMapper() + : m_defaultValue( T() ) + , m_defaultValueIsSet( false ) + { + // Reserve some initial capacity to reduce allocations + m_mapping.reserve( 8 ); + } + + std::vector m_mapping; + T m_defaultValue; + bool m_defaultValueIsSet; +}; + +} // namespace caf diff --git a/Fwk/AppFwk/cafTests/cafTestApplication/ApplicationEnum.cpp b/Fwk/AppFwk/cafTests/cafTestApplication/ApplicationEnum.cpp index 637bf1b0885..2b0a95148eb 100644 --- a/Fwk/AppFwk/cafTests/cafTestApplication/ApplicationEnum.cpp +++ b/Fwk/AppFwk/cafTests/cafTestApplication/ApplicationEnum.cpp @@ -11,8 +11,8 @@ void caf::AppEnum::setUp() addItem( ApplicationEnum::MyEnumType::T3, "T3", "T 3" ); addItem( ApplicationEnum::MyEnumType::T4, "T4", "T 4" ); addItem( ApplicationEnum::MyEnumType::T5, "T5", "T 5" ); - addItem( ApplicationEnum::MyEnumType::T6, "T5", "T 6" ); - addItem( ApplicationEnum::MyEnumType::T7, "T6", "T 7" ); + addItem( ApplicationEnum::MyEnumType::T6, "T6", "T 6" ); + addItem( ApplicationEnum::MyEnumType::T7, "T7", "T 7" ); setDefault( ApplicationEnum::MyEnumType::T4 ); }