diff --git a/include/audacity/Types.h b/include/audacity/Types.h index 5027e7589..97c36ae21 100644 --- a/include/audacity/Types.h +++ b/include/audacity/Types.h @@ -46,11 +46,9 @@ #include #include #include // for wxASSERT -#include // type used in inline function +#include // type used in inline function and member variable #include // for wxCHECK_VERSION -class wxString; - #if !wxCHECK_VERSION(3, 1, 0) // For using std::unordered_map on wxString namespace std @@ -73,6 +71,199 @@ namespace std // until proper public headers are created for the stuff in here. // ---------------------------------------------------------------------------- +// An explicitly nonlocalized string, not meant for the user to see. +// String manipulations are discouraged, other than splitting and joining on +// separator characters. +// Wherever GET is used to fetch the underlying wxString, there should be a +// comment explaining the need for it. +class Identifier +{ +public: + + Identifier() = default; + + // Allow implicit conversion to this class, but not from + Identifier(const wxString &str) : value{ str } {} + + // Allow implicit conversion to this class, but not from + Identifier(const wxChar *str) : value{ str } {} + + // Allow implicit conversion to this class, but not from + Identifier(const char *str) : value{ str } {} + + // Copy construction and assignment + Identifier( const Identifier & ) = default; + Identifier &operator = ( const Identifier& ) = default; + + // Move construction and assignment + Identifier( wxString && str ) + { value.swap( str ); } + Identifier( Identifier && id ) + { swap( id ); } + Identifier &operator= ( Identifier&& id ) + { + if ( this != &id ) + value.clear(), swap( id ); + return *this; + } + + // Implements moves + void swap( Identifier &id ) { value.swap( id.value ); } + + // Convenience for building concatenated identifiers. + // The list must have at least two members + // (so you don't easily circumvent the restrictions on interconversions + // intended in TaggedIdentifier below) + explicit + Identifier(std::initializer_list components, wxChar separator); + + bool empty() const { return value.empty(); } + size_t size() const { return value.size(); } + size_t length() const { return value.length(); } + + // Explicit conversion to wxString, meant to be ugly-looking and + // demanding of a comment why it's correct + const wxString &GET() const { return value; } + + std::vector< Identifier > split( wxChar separator ) const; + +private: + wxString value; +}; + +// Comparisons of Identifiers are case-sensitive +inline bool operator == ( const Identifier &x, const Identifier &y ) +{ return x.GET() == y.GET(); } + +inline bool operator != ( const Identifier &x, const Identifier &y ) +{ return !(x == y); } + +inline bool operator < ( const Identifier &x, const Identifier &y ) +{ return x.GET() < y.GET(); } + +inline bool operator > ( const Identifier &x, const Identifier &y ) +{ return y < x; } + +inline bool operator <= ( const Identifier &x, const Identifier &y ) +{ return !(y < x); } + +inline bool operator >= ( const Identifier &x, const Identifier &y ) +{ return !(x < y); } + +namespace std +{ + template<> struct hash< Identifier > { + size_t operator () ( const Identifier &id ) const // noexcept + { return hash{}( id.GET() ); } + }; +} + +// This lets you pass Identifier into wxFileConfig::Read +inline bool wxFromString(const wxString& str, Identifier *id) + { if (id) { *id = str; return true; } else return false; } + +// This lets you pass Identifier into wxFileConfig::Write +inline wxString wxToString( const Identifier& str ) { return str.GET(); } + +// Template parameter allows generation of different TaggedIdentifier classes +// that don't interconvert implicitly +// The second template parameter determines whether comparisons are case +// sensitive; default is case sensitive +template +class TaggedIdentifier : public Identifier +{ +public: + + using TagType = Tag; + + using Identifier::Identifier; + TaggedIdentifier() = default; + + // Allowed for the same Tag class and case sensitivity + TaggedIdentifier( const TaggedIdentifier& ) = default; + TaggedIdentifier( TaggedIdentifier&& ) = default; + TaggedIdentifier& operator= ( const TaggedIdentifier& ) = default; + TaggedIdentifier& operator= ( TaggedIdentifier&& ) = default; + + // Prohibited for other Tag classes or case sensitivity + template< typename Tag2, bool b > + TaggedIdentifier( const TaggedIdentifier& ) = delete; + template< typename Tag2, bool b > + TaggedIdentifier( TaggedIdentifier&& ) = delete; + template< typename Tag2, bool b > + TaggedIdentifier& operator= ( const TaggedIdentifier& ) = delete; + template< typename Tag2, bool b > + TaggedIdentifier& operator= ( TaggedIdentifier&& ) = delete; + + // Allow implicit conversion to this class from un-tagged Identifier, + // but not from; resolution will use other overloads above if argument + // has a tag + TaggedIdentifier(const Identifier &str) : Identifier{ str } {} + + // Conversion to another kind of TaggedIdentifier + template + String CONVERT() const + { return String{ this->GET() }; } +}; + +// Comparison of a TaggedIdentifier with an Identifier is allowed, resolving +// to one of the operators on Identifiers defined above, but always case +// sensitive. + +// Comparison operators for two TaggedIdentifers, below, require the same tags +// and case sensitivity. +template< typename Tag1, typename Tag2, bool b1, bool b2 > +inline bool operator == ( + const TaggedIdentifier< Tag1, b1 > &x, const TaggedIdentifier< Tag2, b2 > &y ) +{ + static_assert( std::is_same< Tag1, Tag2 >::value && b1 == b2, + "TaggedIdentifiers with different tags or sensitivity are not comparable" ); + // This test should be eliminated at compile time: + if ( b1 ) + return x.GET(). Cmp ( y.GET() ) == 0; + else + return x.GET(). CmpNoCase ( y.GET() ) == 0; +} + +template< typename Tag1, typename Tag2, bool b1, bool b2 > +inline bool operator != ( + const TaggedIdentifier< Tag1, b1 > &x, const TaggedIdentifier< Tag2, b2 > &y ) +{ return !(x == y); } + +template< typename Tag1, typename Tag2, bool b1, bool b2 > +inline bool operator < ( + const TaggedIdentifier< Tag1, b1 > &x, const TaggedIdentifier< Tag2, b2 > &y ) +{ + static_assert( std::is_same< Tag1, Tag2 >::value && b1 == b2, + "TaggedIdentifiers with different tags or sensitivity are not comparable" ); + // This test should be eliminated at compile time: + if ( b1 ) + return x.GET(). Cmp ( y.GET() ) < 0; + else + return x.GET(). CmpNoCase ( y.GET() ) < 0; +} + +template< typename Tag1, typename Tag2, bool b1, bool b2 > +inline bool operator > ( + const TaggedIdentifier< Tag1, b1 > &x, const TaggedIdentifier< Tag2, b2 > &y ) +{ return y < x; } + +template< typename Tag1, typename Tag2, bool b1, bool b2 > +inline bool operator <= ( + const TaggedIdentifier< Tag1, b1 > &x, const TaggedIdentifier< Tag2, b2 > &y ) +{ return !(y < x); } + +template< typename Tag1, typename Tag2, bool b1, bool b2 > +inline bool operator >= ( + const TaggedIdentifier< Tag1, b1 > &x, const TaggedIdentifier< Tag2, b2 > &y ) +{ return !(x < y); } + +namespace std +{ + template struct hash< TaggedIdentifier > + : hash< Identifier > {}; +} + /**************************************************************************//** \brief type alias for identifying a Plugin supplied by a module, each module diff --git a/src/Internat.cpp b/src/Internat.cpp index 6d83680e2..587e6e38a 100644 --- a/src/Internat.cpp +++ b/src/Internat.cpp @@ -310,3 +310,25 @@ wxArrayStringEx LocalizedStrings( std::mem_fn( &EnumValueSymbol::Translation ) ); } + +// Find a better place for this? +#include "audacity/Types.h" +Identifier::Identifier( + std::initializer_list components, wxChar separator ) +{ + if( components.size() < 2 ) + { + wxASSERT( false ); + return; + } + auto iter = components.begin(), end = components.end(); + value = (*iter++).value; + while (iter != end) + value += separator + (*iter++).value; +} + +std::vector< Identifier > Identifier::split( wxChar separator ) const +{ + auto strings = ::wxSplit( value, separator ); + return { strings.begin(), strings.end() }; +} diff --git a/src/Internat.h b/src/Internat.h index 3eb97eb74..f32f18d69 100644 --- a/src/Internat.h +++ b/src/Internat.h @@ -21,7 +21,6 @@ class wxArrayString; class wxArrayStringEx; -class wxString; extern AUDACITY_DLL_API const wxString& GetCustomTranslation(const wxString& str1 ); extern AUDACITY_DLL_API const wxString& GetCustomSubstitution(const wxString& str1 ); diff --git a/src/xml/XMLWriter.h b/src/xml/XMLWriter.h index 7e8575177..885cf9f98 100644 --- a/src/xml/XMLWriter.h +++ b/src/xml/XMLWriter.h @@ -30,6 +30,12 @@ class AUDACITY_DLL_API XMLWriter /* not final */ { virtual void StartTag(const wxString &name); virtual void EndTag(const wxString &name); + // nonvirtual pass-through + void WriteAttr(const wxString &name, const Identifier &value) + // using GET once here, permitting Identifiers in XML, + // so no need for it at each WriteAttr call + { WriteAttr( name, value.GET() ); } + virtual void WriteAttr(const wxString &name, const wxString &value); virtual void WriteAttr(const wxString &name, const wxChar *value);