diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 5ecbdfd49f..49b0d5b55f 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -327,6 +327,7 @@ set( COMMON_SRCS printout.cpp project.cpp properties.cpp + property_mgr.cpp ptree.cpp rc_item.cpp refdes_utils.cpp diff --git a/common/property_mgr.cpp b/common/property_mgr.cpp new file mode 100644 index 0000000000..271ad9a131 --- /dev/null +++ b/common/property_mgr.cpp @@ -0,0 +1,194 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2020 CERN + * @author Tomasz Wlostowski + * @author Maciej Suminski + * + * This program 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 program 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 for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "property_mgr.h" +#include "property.h" + +#include +#include + +static wxString EMPTY_STRING( wxEmptyString ); + + +void PROPERTY_MANAGER::RegisterType( TYPE_ID aType, const wxString& aName ) +{ + wxASSERT( m_classNames.count( aType ) == 0 ); + m_classNames.emplace( aType, aName ); +} + + +const wxString& PROPERTY_MANAGER::ResolveType( TYPE_ID aType ) const +{ + auto it = m_classNames.find( aType ); + return it != m_classNames.end() ? it->second : EMPTY_STRING; +} + + +PROPERTY_BASE* PROPERTY_MANAGER::GetProperty( TYPE_ID aType, const wxString& aProperty ) const +{ + wxASSERT_MSG( !m_dirty, "Have not called PROPERTY_MANAGER::Rebuild(), " + "property list not up-to-date" ); + auto it = m_classes.find( aType ); + + if( it == m_classes.end() ) + return nullptr; + + const CLASS_DESC& classDesc = it->second; + + for( const auto& property : classDesc.m_allProperties ) + { + if( property->Name() == aProperty ) + return property; + } + + return nullptr; +} + + +const PROPERTY_LIST& PROPERTY_MANAGER::GetProperties( TYPE_ID aType ) const +{ + wxASSERT_MSG( !m_dirty, "Have not called PROPERTY_MANAGER::Rebuild(), " + "property list not up-to-date" ); + + static const PROPERTY_LIST empty; + auto it = m_classes.find( aType ); + + if( it == m_classes.end() ) + return empty; + + return it->second.m_allProperties; +} + + +const void* PROPERTY_MANAGER::TypeCast( const void* aSource, TYPE_ID aBase, TYPE_ID aTarget ) const +{ + if( aBase == aTarget ) + return aSource; + + auto classDesc = m_classes.find( aBase ); + + if( classDesc == m_classes.end() ) + return aSource; + + auto& converters = classDesc->second.m_typeCasts; + auto converter = converters.find( aTarget ); + + if( converter == converters.end() ) // explicit type cast not found + return IsOfType( aBase, aTarget ) ? aSource : nullptr; + + return (*converter->second)( aSource ); +} + + +void PROPERTY_MANAGER::AddProperty( PROPERTY_BASE* aProperty ) +{ + const wxString& name = aProperty->Name(); + TYPE_ID hash = aProperty->OwnerHash(); + CLASS_DESC& classDesc = getClass( hash ); + classDesc.m_ownProperties.emplace( name, aProperty ); + m_dirty = true; +} + + +void PROPERTY_MANAGER::AddTypeCast( TYPE_CAST_BASE* aCast ) +{ + TYPE_ID derivedHash = aCast->DerivedHash(); + CLASS_DESC& classDesc = getClass( aCast->BaseHash() ); + auto& typeCasts = classDesc.m_typeCasts; + wxASSERT_MSG( typeCasts.count( derivedHash ) == 0, "Such converter already exists" ); + typeCasts.emplace( derivedHash, aCast ); +} + + +void PROPERTY_MANAGER::InheritsAfter( TYPE_ID aDerived, TYPE_ID aBase ) +{ + wxASSERT_MSG( aDerived != aBase, "Class cannot inherit from itself" ); + + CLASS_DESC& derived = getClass( aDerived ); + CLASS_DESC& base = getClass( aBase ); + derived.m_bases.push_back( base ); + m_dirty = true; + + wxASSERT_MSG( derived.m_bases.size() == 1 || derived.m_typeCasts.count( aBase ) == 1, + "You need to add a TYPE_CAST for classes inheriting from multiple bases" ); +} + + +bool PROPERTY_MANAGER::IsOfType( TYPE_ID aDerived, TYPE_ID aBase ) const +{ + if( aDerived == aBase ) + return true; + + auto derived = m_classes.find( aDerived ); + wxCHECK( derived != m_classes.end(), false ); // missing class description + + // traverse the hierarchy seeking for the base class + for( auto& base : derived->second.m_bases ) + { + if( IsOfType( base.get().m_id, aBase ) ) + return true; + } + + return false; +} + + +void PROPERTY_MANAGER::Rebuild() +{ + wxASSERT_MSG( m_dirty, "Excessive Rebuild() call" ); + + for( auto& classEntry : m_classes ) + { + classEntry.second.rebuild(); + } + + m_dirty = false; +} + + +PROPERTY_MANAGER::CLASS_DESC& PROPERTY_MANAGER::getClass( TYPE_ID aTypeId ) +{ + auto it = m_classes.find( aTypeId ); + + if( it == m_classes.end() ) + tie( it, std::ignore ) = m_classes.emplace( std::make_pair( aTypeId, CLASS_DESC( aTypeId ) ) ); + + return it->second; +} + + +void PROPERTY_MANAGER::CLASS_DESC::rebuild() +{ + m_allProperties.clear(); + collectPropsRecur( m_allProperties ); + // We need to keep properties sorted to be able to use std::set_* functions + sort( m_allProperties.begin(), m_allProperties.end() ); +} + + +void PROPERTY_MANAGER::CLASS_DESC::collectPropsRecur( PROPERTY_LIST& aResult ) const +{ + for( const auto& base : m_bases ) + base.get().collectPropsRecur( aResult ); + + for( auto& property : m_ownProperties ) + aResult.push_back( property.second.get() ); +} diff --git a/include/inspectable.h b/include/inspectable.h new file mode 100644 index 0000000000..ae03773557 --- /dev/null +++ b/include/inspectable.h @@ -0,0 +1,121 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2020 CERN + * @author Tomasz Wlostowski + * @author Maciej Suminski + * + * This program 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 program 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 for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef INSPECTABLE_H +#define INSPECTABLE_H + +#include "property_mgr.h" +#include "property.h" + +#include + +/** + * Class that other classes need to inherit from, in order to be inspectable. + */ +class INSPECTABLE +{ +public: + virtual ~INSPECTABLE() + { + } + + bool Set( PROPERTY_BASE* aProperty, wxAny& aValue ) + { + PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance(); + TYPE_ID thisType = TYPE_HASH( *this ); + void* object = propMgr.TypeCast( this, thisType, aProperty->OwnerHash() ); + + if( object ) + aProperty->setter( object, aValue ); + + return object != nullptr; + } + + template + bool Set( PROPERTY_BASE* aProperty, T aValue ) + { + PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance(); + TYPE_ID thisType = TYPE_HASH( *this ); + void* object = propMgr.TypeCast( this, thisType, aProperty->OwnerHash() ); + + if( object ) + aProperty->set( object, aValue ); + + return object != nullptr; + } + + template + bool Set( const wxString& aProperty, T aValue ) + { + PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance(); + TYPE_ID thisType = TYPE_HASH( *this ); + PROPERTY_BASE* prop = propMgr.GetProperty( thisType, aProperty ); + void* object = nullptr; + + if( prop ) + { + object = propMgr.TypeCast( this, thisType, prop->OwnerHash() ); + + if( object ) + prop->set( object, aValue ); + } + + return object != nullptr; + } + + wxAny Get( PROPERTY_BASE* aProperty ) + { + PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance(); + TYPE_ID thisType = TYPE_HASH( *this ); + void* object = propMgr.TypeCast( this, thisType, aProperty->OwnerHash() ); + return object ? aProperty->getter( object ) : wxAny(); + } + + template + T Get( PROPERTY_BASE* aProperty ) + { + PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance(); + TYPE_ID thisType = TYPE_HASH( *this ); + void* object = propMgr.TypeCast( this, thisType, aProperty->OwnerHash() ); + return object ? aProperty->get( object ) : wxAny(); + } + + template + boost::optional Get( const wxString& aProperty ) + { + PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance(); + TYPE_ID thisType = TYPE_HASH( *this ); + PROPERTY_BASE* prop = propMgr.GetProperty( thisType, aProperty ); + boost::optional ret; + + if( prop ) + { + void* object = propMgr.TypeCast( this, thisType, prop->OwnerHash() ); + + if( object ) + ret = prop->get( object ); + } + + return ret; + } +}; + +#endif /* INSPECTABLE_H */ diff --git a/include/property.h b/include/property.h new file mode 100644 index 0000000000..c54af7373a --- /dev/null +++ b/include/property.h @@ -0,0 +1,557 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2020 CERN + * @author Tomasz Wlostowski + * @author Maciej Suminski + * + * This program 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 program 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 for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef PROPERTY_H +#define PROPERTY_H + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +class wxPGProperty; +class INSPECTABLE; +class PROPERTY_BASE; + +template +class ENUM_MAP; + +///> Common property types +enum PROPERTY_DISPLAY +{ + DEFAULT, ///< Default property for a given type + DISTANCE, ///< Display value expressed in distance units (mm/inch) + DEGREE, ///< Display value expressed in degrees + DECIDEGREE ///< Convert decidegrees to degrees for display +}; + +///> Macro to generate unique identifier for a type +#define TYPE_HASH( x ) typeid( x ).hash_code() +#define TYPE_NAME( x ) typeid( x ).name() +//#define TYPE_HASH( x ) typeid( std::decay::type ).hash_code() + +template +class GETTER_BASE +{ +public: + virtual ~GETTER_BASE() {} + + virtual T operator()( Owner* aOwner ) const = 0; +}; + +template +class GETTER : public GETTER_BASE +{ +public: + GETTER( FuncType aFunc ) + : m_func( aFunc ) + { + } + + T operator()( Owner* aOwner ) const override + { + return ( aOwner->*m_func )(); + } + +private: + FuncType m_func; +}; + +template +class SETTER_BASE +{ +public: + virtual ~SETTER_BASE() {} + + virtual void operator()( Owner* aOwner, T aValue ) = 0; +}; + +template +class SETTER : public SETTER_BASE +{ +public: + SETTER( FuncType aFunc ) + : m_func( aFunc ) + { + } + + void operator()( Owner* aOwner, T aValue ) override + { + wxCHECK( m_func, /*void*/ ); + ( aOwner->*m_func )( aValue ); + } + +private: + FuncType m_func; +}; + + +template +class METHOD +{ +public: + constexpr static GETTER_BASE* Wrap( T (Base::*aFunc)() ) + { + return new GETTER( aFunc ); + } + + constexpr static GETTER_BASE* Wrap( const T (Base::*aFunc)() ) + { + return new GETTER( aFunc ); + } + + constexpr static GETTER_BASE* Wrap( const T& (Base::*aFunc)() ) + { + return new GETTER( aFunc ); + } + + constexpr static GETTER_BASE* Wrap( T (Base::*aFunc)() const ) + { + return new GETTER( aFunc ); + } + + constexpr static GETTER_BASE* Wrap( const T (Base::*aFunc)() const ) + { + return new GETTER( aFunc ); + } + + constexpr static GETTER_BASE* Wrap( const T& (Base::*aFunc)() const ) + { + return new GETTER( aFunc ); + } + + constexpr static SETTER_BASE* Wrap( void (Base::*aFunc)( T ) ) + { + return aFunc ? new SETTER( aFunc ) : nullptr; + } + + constexpr static SETTER_BASE* Wrap( void (Base::*aFunc)( T& ) ) + { + return aFunc ? new SETTER( aFunc ) : nullptr; + } + + constexpr static SETTER_BASE* Wrap( void (Base::*aFunc)( const T& ) ) + { + return aFunc ? new SETTER( aFunc ) : nullptr; + } + + METHOD() = delete; +}; + + +class PROPERTY_BASE +{ +public: + PROPERTY_BASE( const wxString& aName, PROPERTY_DISPLAY aDisplay = DEFAULT ) + : m_name( aName ), m_display( aDisplay ), + m_availFunc( [](INSPECTABLE*)->bool { return true; } ) + { + } + + virtual ~PROPERTY_BASE() + { + } + + const wxString& Name() const + { + return m_name; + } + + /** + * Returns a limited set of possible values (e.g. enum). Check with HasChoices() if a particular + * PROPERTY provides such set. + */ + virtual const wxPGChoices& Choices() const + { + static wxPGChoices empty; + return empty; + } + + /** + * Returns true if this PROPERTY has a limited set of possible values. + * @see PROPERTY_BASE::Choices() + */ + virtual bool HasChoices() const + { + return false; + } + + /** + * Returns true if aObject offers this PROPERTY. + */ + bool Available( INSPECTABLE* aObject ) const + { + return m_availFunc( aObject ); + } + + /** + * Sets a callback function to determine whether an object provides this property. + */ + void SetAvailableFunc( std::function aFunc ) + { + m_availFunc = aFunc; + } + + /** + * Returns the type-id of the Owner class. + */ + virtual size_t OwnerHash() const = 0; + + /** + * Returns the type-id of the property type. + */ + virtual size_t TypeHash() const = 0; + + virtual bool IsReadOnly() const = 0; + + PROPERTY_DISPLAY GetDisplay() const + { + return m_display; + } + +protected: + template + void set( void* aObject, T aValue ) + { + wxAny a = aValue; + setter( aObject, a ); + } + + template + T get( void* aObject ) + { + wxAny a = getter( aObject ); + + if ( !(std::is_enum::value && a.CheckType() ) && !a.CheckType() ) + throw std::invalid_argument("Invalid requested type"); + + return wxANY_AS(a, T); + } + + virtual void setter( void* aObject, wxAny& aValue ) = 0; + virtual wxAny getter( void* aObject ) const = 0; + +private: + const wxString m_name; + const PROPERTY_DISPLAY m_display; + + ///> Condition that determines whether the property is available + std::function m_availFunc; + + friend class INSPECTABLE; +}; + + +template +class PROPERTY : public PROPERTY_BASE +{ +public: + typedef typename std::decay::type BASE_TYPE; + typedef void (Base::*SETTER)( T ); + + template + PROPERTY( const wxString& aName, + void ( Base::*aSetter )( SetType ), GetType( Base::*aGetter )(), + PROPERTY_DISPLAY aDisplay = DEFAULT ) + : PROPERTY( aName, METHOD::Wrap( aSetter ), + METHOD::Wrap( aGetter ), aDisplay ) + { + } + + template + PROPERTY( const wxString& aName, + void ( Base::*aSetter )( SetType ), GetType( Base::*aGetter )() const, + PROPERTY_DISPLAY aDisplay = DEFAULT ) + : PROPERTY( aName, METHOD::Wrap( aSetter ), + METHOD::Wrap( aGetter ), aDisplay ) + { + } + + size_t OwnerHash() const override + { + return m_ownerHash; + } + + size_t TypeHash() const override + { + return m_typeHash; + } + + bool IsReadOnly() const override + { + return !m_setter; + } + +protected: + PROPERTY( const wxString& aName, SETTER_BASE* s, GETTER_BASE* g, + PROPERTY_DISPLAY aDisplay ) + : PROPERTY_BASE( aName, aDisplay ), m_setter( s ), m_getter( g ), + m_ownerHash( TYPE_HASH( Owner ) ), m_typeHash( TYPE_HASH( BASE_TYPE ) ) + { + } + + virtual ~PROPERTY() {} + + virtual void setter( void* obj, wxAny& v ) override + { + wxCHECK( !IsReadOnly(), /*void*/ ); + + if( !v.CheckType() ) + throw std::invalid_argument( "Invalid type requested" ); + + Owner* o = reinterpret_cast( obj ); + BASE_TYPE value = wxANY_AS(v, BASE_TYPE); + (*m_setter)( o, value ); + } + + virtual wxAny getter( void* obj ) const override + { + Owner* o = reinterpret_cast( obj ); + wxAny res = (*m_getter)( o ); + return res; + } + + ///> Set method + std::unique_ptr> m_setter; + + ///> Get method + std::unique_ptr> m_getter; + + ///> Owner class type-id + const size_t m_ownerHash; + + ///> Property value type-id + const size_t m_typeHash; +}; + + +template +class PROPERTY_ENUM : public PROPERTY +{ +public: + template + PROPERTY_ENUM( const wxString& aName, + void ( Base::*aSetter )( SetType ), GetType( Base::*aGetter )(), + PROPERTY_DISPLAY aDisplay = PROPERTY_DISPLAY::DEFAULT ) + : PROPERTY( aName, METHOD::Wrap( aSetter ), + METHOD::Wrap( aGetter ), aDisplay ), + m_choices( ENUM_MAP::Instance().Choices() ) + { + wxASSERT_MSG( m_choices.GetCount() > 0, "No enum choices defined" ); + } + + template + PROPERTY_ENUM( const wxString& aName, + void ( Base::*aSetter )( SetType ), GetType( Base::*aGetter )() const, + PROPERTY_DISPLAY aDisplay = PROPERTY_DISPLAY::DEFAULT ) + : PROPERTY( aName, METHOD::Wrap( aSetter ), + METHOD::Wrap( aGetter ), aDisplay ), + m_choices( ENUM_MAP::Instance().Choices() ) + { + wxASSERT_MSG( m_choices.GetCount() > 0, "No enum choices defined" ); + } + + virtual void setter( void* obj, wxAny& v ) override + { + wxCHECK( !( PROPERTY::IsReadOnly() ), /*void*/ ); + Owner* o = reinterpret_cast( obj ); + + if( v.CheckType() ) + { + T value = wxANY_AS(v, T); + (*PROPERTY::m_setter)( o, value ); + } + else if (v.CheckType() ) + { + int value = wxANY_AS(v, int); + (*PROPERTY::m_setter)( o, static_cast( value ) ); + } + else + { + throw std::invalid_argument( "Invalid type requested" ); + } + } + + virtual wxAny getter( void* obj ) const override + { + Owner* o = reinterpret_cast( obj ); + wxAny res = static_cast( (*PROPERTY::m_getter)( o ) ); + return res; + } + + const wxPGChoices& Choices() const override + { + return m_choices; + } + + bool HasChoices() const override + { + return m_choices.GetCount() > 0; + } + +protected: + wxPGChoices m_choices; +}; + + +class TYPE_CAST_BASE +{ +public: + virtual ~TYPE_CAST_BASE() {} + virtual void* operator()( void* aPointer ) const = 0; + virtual const void* operator()( const void* aPointer ) const = 0; + virtual size_t BaseHash() const = 0; + virtual size_t DerivedHash() const = 0; +}; + + +template +class TYPE_CAST : public TYPE_CAST_BASE +{ +public: + TYPE_CAST() + { + } + + void* operator()( void* aPointer ) const override + { + Base* base = reinterpret_cast( aPointer ); + return static_cast( base ); + } + + const void* operator()( const void* aPointer ) const override + { + const Base* base = reinterpret_cast( aPointer ); + return static_cast( base ); + } + + size_t BaseHash() const override + { + return TYPE_HASH( Base ); + } + + size_t DerivedHash() const override + { + return TYPE_HASH( Derived ); + } +}; + + +template +class ENUM_MAP +{ +public: + static ENUM_MAP& Instance() + { + static ENUM_MAP inst; + return inst; + } + + ENUM_MAP& Map( T aValue, const wxString& aName ) + { + wxASSERT_MSG( m_choices.Index( aName ) == wxNOT_FOUND, "Redefined string for a value in ENUM_MAP" ); + m_choices.Add( aName, static_cast( aValue ) ); + return *this; + } + + void Reset() + { + m_choices.Clear(); + } + + const wxString& ToString( T value ) const + { + return m_choices.GetLabel( static_cast( value ) ); + } + + const wxPGChoices& Choices() const + { + return m_choices; + } + + void SetChoices( const wxPGChoices& aChoices ) + { + m_choices = aChoices; + } + +private: + wxPGChoices m_choices; + + ENUM_MAP() + { + } +}; + + +// Helper macros to handle enum types +#define DECLARE_ENUM_TO_WXANY(type)\ + template<>\ + class wxAnyValueTypeImpl : public wxAnyValueTypeImplBase\ + {\ + WX_DECLARE_ANY_VALUE_TYPE(wxAnyValueTypeImpl)\ + public:\ + wxAnyValueTypeImpl() : wxAnyValueTypeImplBase() {}\ + virtual ~wxAnyValueTypeImpl() {}\ + virtual bool ConvertValue( const wxAnyValueBuffer& src,\ + wxAnyValueType* dstType, wxAnyValueBuffer& dst ) const override\ + {\ + type value = GetValue(src);\ + ENUM_MAP conv = ENUM_MAP::Instance();\ + if( dstType->CheckType() )\ + {\ + wxAnyValueTypeImpl::SetValue( conv.ToString( value ), dst );\ + return true;\ + }\ + if( dstType->CheckType() )\ + {\ + wxAnyValueTypeImpl::SetValue( static_cast(value), dst );\ + return true;\ + }\ + else\ + {\ + return false;\ + }\ + }\ + }; + +#define IMPLEMENT_ENUM_TO_WXANY(type)\ + WX_IMPLEMENT_ANY_VALUE_TYPE(wxAnyValueTypeImpl) + +#define ENUM_TO_WXANY(type)\ + DECLARE_ENUM_TO_WXANY(type)\ + IMPLEMENT_ENUM_TO_WXANY(type) + +///> Macro to define read-only fields (no setter method available) +#define NO_SETTER(owner, type) ((void (owner::*)(type))nullptr) + +/* +#define DECLARE_PROPERTY(owner,type,name,getter,setter) \ +namespace anonymous {\ +static PROPERTY prop##_owner##_name_( "##name#", setter, getter );\ +}; +*/ +#endif /* PROPERTY_H */ diff --git a/include/property_mgr.h b/include/property_mgr.h new file mode 100644 index 0000000000..a9e4cd17ba --- /dev/null +++ b/include/property_mgr.h @@ -0,0 +1,215 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2020 CERN + * @author Tomasz Wlostowski + * @author Maciej Suminski + * + * This program 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 program 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 for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef PROPERTY_MGR_H +#define PROPERTY_MGR_H + +#include + +#include +#include +#include +#include +#include + +class PROPERTY_BASE; +class TYPE_CAST_BASE; + +///> Unique type identifier +using TYPE_ID = size_t; + +using PROPERTY_LIST = std::vector; + +/** + * Provides class metadata. Each class handled by PROPERTY_MANAGER + * needs to be described using AddProperty(), AddTypeCast() and InheritsAfter() methods. + * + * Enum types use a dedicated property type (PROPERTY_ENUM), define its possible values + * with ENUM_MAP class, then describe the type using macros: + * - DECLARE_ENUM_TO_WXANY (in header files) + * - IMPLEMENT_ENUM_TO_WXANY (in source files) + * - ENUM_TO_WXANY (*most often used*; combines DECLARE and IMPLEMENT macros, + * if there is no need to share the description using header files) + * + * Once all classes are described, the property list must be build using + * Rebuild() method. + */ +class PROPERTY_MANAGER +{ +public: + static PROPERTY_MANAGER& Instance() + { + static PROPERTY_MANAGER pm; + return pm; + } + + /** + * Associates a name with a type. + * + * Builds a map to provide faster type look-up. + * + * @param aType is the type identifier (obtained using TYPE_HASH()). + * @param aName is the type name. + */ + void RegisterType( TYPE_ID aType, const wxString& aName ); + + /** + * Returns name of a type. + * + * @param aType is the type identifier (obtained using TYPE_HASH()). + * @return Name of the type or empty string, if not available. + */ + const wxString& ResolveType( TYPE_ID aType ) const; + + /** + * Returns a property for a specific type. + * + * @param aType is the type identifier (obtained using TYPE_HASH()). + * @param aProperty is the property name used during class registration. + * @return Requested property or null pointer if requested property does not exist. + */ + PROPERTY_BASE* GetProperty( TYPE_ID aType, const wxString& aProperty ) const; + + /** + * Returns all properties for a specific type. + * + * @param aType is the type identifier (obtained using TYPE_HASH()). + * @return Vector storing all properties of the requested type. + */ + const PROPERTY_LIST& GetProperties( TYPE_ID aType ) const; + + /** + * Casts a type to another type. Used for correct type-casting of types with + * multi-inheritance. Requires registration of an appropriate converter (AddTypeCast). + * + * @param aSource is a pointer to the casted object. + * @param aBase is aSource type identifier (obtained using TYPE_HASH()). + * @param aTarget is the desired type identifier (obtained using TYPE_HASH()). + * @return Properly casted pointer of aTarget type. * + * + * @see AddTypeCast + */ + const void* TypeCast( const void* aSource, TYPE_ID aBase, TYPE_ID aTarget ) const; + + void* TypeCast( void* aSource, TYPE_ID aBase, TYPE_ID aTarget ) const + { + return const_cast( TypeCast( (const void*) aSource, aBase, aTarget ) ); + } + + /** + * Registers a property. + * + * @param aProperty is the property to register. + */ + void AddProperty( PROPERTY_BASE* aProperty ); + + /** + * Registers a type converter. Required prior TypeCast() usage. + * + * @param aCast is the type converter to register. + */ + void AddTypeCast( TYPE_CAST_BASE* aCast ); + + /** + * Declares an inheritance relationship between types. + * + * @param aBase is the base type identifier (obtained using TYPE_HASH()). + * @param aDerived is the derived type identifier (obtained using TYPE_HASH()). + */ + void InheritsAfter( TYPE_ID aDerived, TYPE_ID aBase ); + + /** + * Returns true if aDerived is inherited from aBase. + */ + bool IsOfType( TYPE_ID aDerived, TYPE_ID aBase ) const; + + EDA_UNITS GetUnits() const + { + return m_units; + } + + void SetUnits( EDA_UNITS aUnits ) + { + m_units = aUnits; + } + + /** + * Rebuilds the list of all registered properties. Needs to be called + * once before GetProperty()/GetProperties() are used. + */ + void Rebuild(); + +private: + PROPERTY_MANAGER() + : m_dirty( false ), m_units( EDA_UNITS::MILLIMETRES ) + { + } + + ///> Structure holding type meta-data + struct CLASS_DESC + { + CLASS_DESC( TYPE_ID aId ) + : m_id( aId ) + { + } + + ///> Unique type identifier (obtained using TYPE_HASH) + const TYPE_ID m_id; + + ///> Types after which this type inherits + std::vector> m_bases; + + ///> Properties unique to this type (i.e. not inherited) + std::map> m_ownProperties; + + ///> Type converters available for this type + std::map> m_typeCasts; + + ///> All properties (both unique to the type and inherited) + std::vector m_allProperties; + + ///> Recreates the list of properties + void rebuild(); + + ///> Traverses the class inheritance hierarchy bottom-to-top, gathering + ///> all properties available to a type + void collectPropsRecur( PROPERTY_LIST& aResult ) const; + }; + + ///> Returns metadata for a specific type + CLASS_DESC& getClass( TYPE_ID aTypeId ); + + std::unordered_map m_classNames; + + ///> Map of all available types + std::unordered_map m_classes; + + /// Flag indicating that the list of properties needs to be rebuild (RebuildProperties()) + bool m_dirty; + + EDA_UNITS m_units; +}; + + +///> Helper macro to map type hashes to names +#define REGISTER_TYPE(x) PROPERTY_MANAGER::Instance().RegisterType(TYPE_HASH(x), TYPE_NAME(x)) + +#endif /* PROPERTY_MGR_H */ diff --git a/qa/common/CMakeLists.txt b/qa/common/CMakeLists.txt index d79dc5e3c0..7bfae6055d 100644 --- a/qa/common/CMakeLists.txt +++ b/qa/common/CMakeLists.txt @@ -45,6 +45,7 @@ set( common_srcs test_format_units.cpp test_lib_table.cpp test_kicad_string.cpp + test_property.cpp test_refdes_utils.cpp test_title_block.cpp test_utf8.cpp diff --git a/qa/common/test_property.cpp b/qa/common/test_property.cpp new file mode 100644 index 0000000000..b87274b5c0 --- /dev/null +++ b/qa/common/test_property.cpp @@ -0,0 +1,339 @@ +/* +* This program source code file is part of KICAD, a free EDA CAD application. + * + * Copyright (C) 2020 CERN + * @author Maciej Suminski + * + * This program 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 program 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 for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include +#include // wxPoint + +#include +#include + +using namespace std; + +class A : public INSPECTABLE +{ +public: + virtual void setA( int a ) = 0; + virtual int getA() const = 0; + virtual const int& getA2() const { return m_a; } + + virtual void setPoint( const wxPoint& p ) { m_p = p; } + void setPoint2( wxPoint& p ) { m_p = p; } + void setPoint3( wxPoint p ) { m_p = p; } + void setPoint4( wxPoint p ) { m_p = p; } + + const wxPoint& getPoint() const { return m_p; } + wxPoint getPoint2() const { return m_p; } + wxPoint getPoint3() { return m_p; } + const wxPoint& getPoint4() const { return m_p; } + +protected: + int m_a; + wxPoint m_p; +}; + +class B : public A +{ +public: + void setA( int a ) override { m_a = a; } + int getA() const override { return m_a; } + + void setC( int a ) { m_c = a; } + int getC() const { return m_c; } + +private: + int m_c; +}; + +class C : public INSPECTABLE +{ +public: + bool getBool() const { return m_bool; } + void setBool( bool a ) { m_bool = a; } + + int getNew() const { return m_m; } + void setNew( int m ) { m_m = m; } + + int m_m; + bool m_bool; +}; + +enum enum_glob { TEST1 = 0, TEST2 = 1, TEST3 = 4 }; + +class D : public A, public C +{ +public: + enum enum_class { TESTA = 0, TESTB = 1, TESTC = 4 }; + + // note 2x factor + virtual void setA( int a ) override { m_aa = 2 * a; } + virtual int getA() const override { return m_aa; } + + enum_glob getGlobEnum() const { return m_enum_glob; } + void setGlobEnum( enum_glob val ) { m_enum_glob = val; } + + enum_class getClassEnum() const { return m_enum_class; } + void setClassEnum( enum_class val ) { m_enum_class = val; } + + void setCond( int a ) { m_cond = a; } + int getCond() const { return m_cond; } + + enum_glob m_enum_glob; + enum_class m_enum_class; + int m_aa; + int m_cond; +}; + +static struct ENUM_GLOB_DESC +{ + ENUM_GLOB_DESC() + { + ENUM_MAP::Instance() + .Map( enum_glob::TEST1, "TEST1" ) + .Map( enum_glob::TEST2, "TEST2" ) + .Map( enum_glob::TEST3, "TEST3" ); + } +} _ENUM_GLOB_DESC; + +ENUM_TO_WXANY( enum_glob ); + +static struct CLASS_A_DESC +{ + CLASS_A_DESC() + { + PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance(); + propMgr.AddProperty( new PROPERTY( "A", &A::setA, &A::getA ) ); + propMgr.AddProperty( new PROPERTY( "A2", &A::setA, &A::getA2 ) ); + propMgr.AddProperty( new PROPERTY( "point", &A::setPoint, &A::getPoint ) ); + propMgr.AddProperty( new PROPERTY( "point2", &A::setPoint, &A::getPoint2 ) ); + + propMgr.AddProperty( new PROPERTY( "point3", &A::setPoint3, &A::getPoint3 ) ); + propMgr.AddProperty( new PROPERTY( "point4", &A::setPoint4, &A::getPoint4 ) ); + } +} _CLASS_A_DESC; + +static struct CLASS_B_DESC +{ + CLASS_B_DESC() + { + PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance(); + propMgr.InheritsAfter( TYPE_HASH( B ), TYPE_HASH( A ) ); + propMgr.AddProperty( new PROPERTY( "C", &B::setC, &B::getC ) ); + } +} _CLASS_B_DESC; + +static struct CLASS_C_DESC +{ + CLASS_C_DESC() + { + PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance(); + propMgr.AddProperty( new PROPERTY( "bool", &C::setBool, &C::getBool ) ); + propMgr.AddProperty( new PROPERTY( "new", &C::setNew, &C::getNew ) ); + } +} _CLASS_C_DESC; + +static struct CLASS_D_DESC +{ + CLASS_D_DESC() + { + ENUM_MAP::Instance() + .Map( D::enum_class::TESTA, "TESTA" ) + .Map( D::enum_class::TESTB, "TESTB" ) + .Map( D::enum_class::TESTC, "TESTC" ); + + PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance(); + propMgr.AddProperty( new PROPERTY_ENUM( "enumGlob", &D::setGlobEnum, &D::getGlobEnum ) ); + propMgr.AddProperty( new PROPERTY_ENUM( "enumClass", &D::setClassEnum, &D::getClassEnum ) ); + propMgr.AddProperty( new PROPERTY( "point_alias", &D::setPoint, &D::getPoint ) ); + + // lines below are needed to indicate multiple inheritance + propMgr.AddTypeCast( new TYPE_CAST ); + propMgr.AddTypeCast( new TYPE_CAST ); + propMgr.InheritsAfter( TYPE_HASH( D ), TYPE_HASH( A ) ); + propMgr.InheritsAfter( TYPE_HASH( D ), TYPE_HASH( C ) ); + + auto cond = new PROPERTY( "cond", &D::setCond, &D::getCond ); + cond->SetAvailableFunc( [=](INSPECTABLE* aItem)->bool { return *aItem->Get( "A" ) > 50; } ); + propMgr.AddProperty( cond ); + } +} _CLASS_D_DESC; + +ENUM_TO_WXANY( D::enum_class ); + + +struct PropertiesFixture +{ + PropertiesFixture() + : propMgr( PROPERTY_MANAGER::Instance() ) + { + } + + B b; + D d; + A* ptr; + PROPERTY_MANAGER& propMgr; +}; + +BOOST_FIXTURE_TEST_SUITE( Properties, PropertiesFixture ) + +BOOST_AUTO_TEST_CASE( Init ) +{ + propMgr.Rebuild(); +} + +// Basic Set() & Get() +BOOST_AUTO_TEST_CASE( SetGet ) +{ + ptr = &b; + ptr->Set( "A", 100 ); + ptr->Set( "point", wxPoint( 100, 200 ) ); + BOOST_CHECK_EQUAL( *ptr->Get( "A" ), 100 ); + BOOST_CHECK_EQUAL( *ptr->Get( "point" ), wxPoint( 100, 200 ) ); + + ptr = &d; + ptr->Set( "enumGlob", enum_glob::TEST2 ); + ptr->Set( "enumClass", D::enum_class::TESTC ); + BOOST_CHECK_EQUAL( *ptr->Get( "enumGlob" ), enum_glob::TEST2 ); + BOOST_CHECK_EQUAL( *ptr->Get( "enumClass" ), D::enum_class::TESTC ); +} + +// Virtual methods +BOOST_AUTO_TEST_CASE( VirtualMethods ) +{ + // D::setA() saves a doubled value, while B::setA() saves unmodified value + ptr = &b; + ptr->Set( "A", 23 ); + BOOST_CHECK_EQUAL( *ptr->Get( "A" ), 23 ); // unmodified == 23 + + ptr = &d; + ptr->Set( "A", 23 ); + BOOST_CHECK_EQUAL( *ptr->Get( "A" ), 46 ); // doubled == 46 +} + +// Non-existing properties +BOOST_AUTO_TEST_CASE( NotexistingProperties ) +{ + BOOST_CHECK_EQUAL( ptr->Set( "does not exist", 5 ), false ); + BOOST_CHECK_EQUAL( ptr->Get( "neither" ).has_value(), false ); +} + +// Request data using incorrect type +BOOST_AUTO_TEST_CASE( IncorrectType ) +{ + BOOST_CHECK_THROW( ptr->Get( "bool" ), std::invalid_argument ); +} + +// Type-casting (for types with multiple inheritance) +BOOST_AUTO_TEST_CASE( TypeCasting ) +{ + ptr = &d; + A* D_to_A = static_cast( propMgr.TypeCast( ptr, TYPE_HASH( D ), TYPE_HASH( A ) ) ); + BOOST_CHECK_EQUAL( D_to_A, dynamic_cast( ptr ) ); + + C* D_to_C = static_cast( propMgr.TypeCast( ptr, TYPE_HASH( D ), TYPE_HASH( C ) ) ); + BOOST_CHECK_EQUAL( D_to_C, dynamic_cast( ptr ) ); +} + +BOOST_AUTO_TEST_CASE( EnumGlob ) +{ + PROPERTY_BASE* prop = propMgr.GetProperty( TYPE_HASH( D ), "enumGlob" ); + BOOST_CHECK( prop->HasChoices() ); + + wxArrayInt values; + values.Add( static_cast( enum_glob::TEST1 ) ); + values.Add( static_cast( enum_glob::TEST2 ) ); + values.Add( static_cast( enum_glob::TEST3 ) ); + wxArrayString labels; + labels.Add( "TEST1" ); + labels.Add( "TEST2" ); + labels.Add( "TEST3" ); + + const wxPGChoices& v = prop->Choices(); + BOOST_CHECK_EQUAL( v.GetCount(), values.GetCount() ); + BOOST_CHECK_EQUAL( v.GetCount(), labels.GetCount() ); + + for (int i = 0; i < values.GetCount(); ++i ) + { + BOOST_CHECK_EQUAL( v.GetValue( i ), values[i] ); + } + + for (int i = 0; i < labels.GetCount(); ++i ) + { + BOOST_CHECK_EQUAL( v.GetLabel( i ), labels[i] ); + } +} + +BOOST_AUTO_TEST_CASE( EnumClass ) +{ + PROPERTY_BASE* prop = propMgr.GetProperty( TYPE_HASH( D ), "enumClass" ); + BOOST_CHECK( prop->HasChoices() ); + + wxArrayInt values; + values.Add( static_cast( D::enum_class::TESTA ) ); + values.Add( static_cast( D::enum_class::TESTB ) ); + values.Add( static_cast( D::enum_class::TESTC ) ); + wxArrayString labels; + labels.Add( "TESTA" ); + labels.Add( "TESTB" ); + labels.Add( "TESTC" ); + + const wxPGChoices& v = prop->Choices(); + BOOST_CHECK_EQUAL( v.GetCount(), values.GetCount() ); + BOOST_CHECK_EQUAL( v.GetCount(), labels.GetCount() ); + + for (int i = 0; i < values.GetCount(); ++i ) + { + BOOST_CHECK_EQUAL( v.GetValue( i ), values[i] ); + } + + for (int i = 0; i < labels.GetCount(); ++i ) + { + BOOST_CHECK_EQUAL( v.GetLabel( i ), labels[i] ); + } +} + +// Tests conditional properties (which may depend on values or other properties) +BOOST_AUTO_TEST_CASE( Availability ) +{ + PROPERTY_BASE* propCond = propMgr.GetProperty( TYPE_HASH( D ), "cond" ); + ptr = &d; + + // "cond" property is available only when "a" field is greater than 50 + d.setA( 0 ); + BOOST_CHECK( !propCond->Available( ptr ) ); + + d.setA( 100 ); + BOOST_CHECK( propCond->Available( ptr ) ); +} + +// Using a different name for a parent propety +BOOST_AUTO_TEST_CASE( Alias ) +{ + ptr = &d; + + ptr->Set( "point", wxPoint( 100, 100 ) ); + BOOST_CHECK_EQUAL( *ptr->Get( "point" ), wxPoint( 100, 100 ) ); + BOOST_CHECK_EQUAL( *ptr->Get( "point_alias" ), wxPoint( 100, 100 ) ); + + ptr->Set( "point_alias", wxPoint( 300, 300 ) ); + BOOST_CHECK_EQUAL( *ptr->Get( "point" ), wxPoint( 300, 300 ) ); + BOOST_CHECK_EQUAL( *ptr->Get( "point_alias" ), wxPoint( 300, 300 ) ); +} + +BOOST_AUTO_TEST_SUITE_END()