Properties (introspection)

Introduces classes:
- INSPECTED: base class for types taking advantage of
  generic properties system.
- PROPERTY*: meta-data storing information about properties
- PROPERTY_MANAGER: singleton class to get properties data
This commit is contained in:
Maciej Suminski 2020-02-02 19:29:33 +01:00 committed by Tomasz Wlostowski
parent 89698a727b
commit cbd5004fd4
7 changed files with 1428 additions and 0 deletions

View File

@ -327,6 +327,7 @@ set( COMMON_SRCS
printout.cpp
project.cpp
properties.cpp
property_mgr.cpp
ptree.cpp
rc_item.cpp
refdes_utils.cpp

194
common/property_mgr.cpp Normal file
View File

@ -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 <tomasz.wlostowski@cern.ch>
* @author Maciej Suminski <maciej.suminski@cern.ch>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "property_mgr.h"
#include "property.h"
#include <algorithm>
#include <utility>
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() );
}

121
include/inspectable.h Normal file
View File

@ -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 <tomasz.wlostowski@cern.ch>
* @author Maciej Suminski <maciej.suminski@cern.ch>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef INSPECTABLE_H
#define INSPECTABLE_H
#include "property_mgr.h"
#include "property.h"
#include <boost/optional.hpp>
/**
* 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<typename T>
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<T>( object, aValue );
return object != nullptr;
}
template<typename T>
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<T>( 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<typename T>
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<T>( object ) : wxAny();
}
template<typename T>
boost::optional<T> 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<T> ret;
if( prop )
{
void* object = propMgr.TypeCast( this, thisType, prop->OwnerHash() );
if( object )
ret = prop->get<T>( object );
}
return ret;
}
};
#endif /* INSPECTABLE_H */

557
include/property.h Normal file
View File

@ -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 <tomasz.wlostowski@cern.ch>
* @author Maciej Suminski <maciej.suminski@cern.ch>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef PROPERTY_H
#define PROPERTY_H
#include <wx/wx.h>
#include <wx/any.h>
#include <wx/string.h>
#include <wx/propgrid/property.h>
#include <functional>
#include <map>
#include <memory>
#include <typeindex>
#include <type_traits>
class wxPGProperty;
class INSPECTABLE;
class PROPERTY_BASE;
template<typename T>
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<x>::type ).hash_code()
template<typename Owner, typename T>
class GETTER_BASE
{
public:
virtual ~GETTER_BASE() {}
virtual T operator()( Owner* aOwner ) const = 0;
};
template<typename Owner, typename T, typename FuncType>
class GETTER : public GETTER_BASE<Owner, T>
{
public:
GETTER( FuncType aFunc )
: m_func( aFunc )
{
}
T operator()( Owner* aOwner ) const override
{
return ( aOwner->*m_func )();
}
private:
FuncType m_func;
};
template<typename Owner, typename T>
class SETTER_BASE
{
public:
virtual ~SETTER_BASE() {}
virtual void operator()( Owner* aOwner, T aValue ) = 0;
};
template<typename Owner, typename T, typename FuncType>
class SETTER : public SETTER_BASE<Owner, T>
{
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<typename Owner, typename T, typename Base = Owner>
class METHOD
{
public:
constexpr static GETTER_BASE<Owner, T>* Wrap( T (Base::*aFunc)() )
{
return new GETTER<Owner, T, T (Base::*)()>( aFunc );
}
constexpr static GETTER_BASE<Owner, T>* Wrap( const T (Base::*aFunc)() )
{
return new GETTER<Owner, T, T (Base::*)()>( aFunc );
}
constexpr static GETTER_BASE<Owner, T>* Wrap( const T& (Base::*aFunc)() )
{
return new GETTER<Owner, T, const T& (Base::*)()>( aFunc );
}
constexpr static GETTER_BASE<Owner, T>* Wrap( T (Base::*aFunc)() const )
{
return new GETTER<Owner, T, T (Base::*)() const>( aFunc );
}
constexpr static GETTER_BASE<Owner, T>* Wrap( const T (Base::*aFunc)() const )
{
return new GETTER<Owner, T, const T (Base::*)() const>( aFunc );
}
constexpr static GETTER_BASE<Owner, T>* Wrap( const T& (Base::*aFunc)() const )
{
return new GETTER<Owner, T, const T& (Base::*)() const>( aFunc );
}
constexpr static SETTER_BASE<Owner, T>* Wrap( void (Base::*aFunc)( T ) )
{
return aFunc ? new SETTER<Owner, T, void (Base::*)( T )>( aFunc ) : nullptr;
}
constexpr static SETTER_BASE<Owner, T>* Wrap( void (Base::*aFunc)( T& ) )
{
return aFunc ? new SETTER<Owner, T, void (Base::*)( T& )>( aFunc ) : nullptr;
}
constexpr static SETTER_BASE<Owner, T>* Wrap( void (Base::*aFunc)( const T& ) )
{
return aFunc ? new SETTER<Owner, T, void (Base::*)( const T& )>( 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<bool(INSPECTABLE*)> 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<typename T>
void set( void* aObject, T aValue )
{
wxAny a = aValue;
setter( aObject, a );
}
template<typename T>
T get( void* aObject )
{
wxAny a = getter( aObject );
if ( !(std::is_enum<T>::value && a.CheckType<int>() ) && !a.CheckType<T>() )
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<bool(INSPECTABLE*)> m_availFunc;
friend class INSPECTABLE;
};
template<typename Owner, typename T, typename Base = Owner>
class PROPERTY : public PROPERTY_BASE
{
public:
typedef typename std::decay<T>::type BASE_TYPE;
typedef void (Base::*SETTER)( T );
template<typename SetType, typename GetType>
PROPERTY( const wxString& aName,
void ( Base::*aSetter )( SetType ), GetType( Base::*aGetter )(),
PROPERTY_DISPLAY aDisplay = DEFAULT )
: PROPERTY( aName, METHOD<Owner, T, Base>::Wrap( aSetter ),
METHOD<Owner, T, Base>::Wrap( aGetter ), aDisplay )
{
}
template<typename SetType, typename GetType>
PROPERTY( const wxString& aName,
void ( Base::*aSetter )( SetType ), GetType( Base::*aGetter )() const,
PROPERTY_DISPLAY aDisplay = DEFAULT )
: PROPERTY( aName, METHOD<Owner, T, Base>::Wrap( aSetter ),
METHOD<Owner, T, Base>::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<Owner, T>* s, GETTER_BASE<Owner, T>* 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<T>() )
throw std::invalid_argument( "Invalid type requested" );
Owner* o = reinterpret_cast<Owner*>( obj );
BASE_TYPE value = wxANY_AS(v, BASE_TYPE);
(*m_setter)( o, value );
}
virtual wxAny getter( void* obj ) const override
{
Owner* o = reinterpret_cast<Owner*>( obj );
wxAny res = (*m_getter)( o );
return res;
}
///> Set method
std::unique_ptr<SETTER_BASE<Owner, T>> m_setter;
///> Get method
std::unique_ptr<GETTER_BASE<Owner, T>> m_getter;
///> Owner class type-id
const size_t m_ownerHash;
///> Property value type-id
const size_t m_typeHash;
};
template<typename Owner, typename T, typename Base = Owner>
class PROPERTY_ENUM : public PROPERTY<Owner, T, Base>
{
public:
template<typename SetType, typename GetType>
PROPERTY_ENUM( const wxString& aName,
void ( Base::*aSetter )( SetType ), GetType( Base::*aGetter )(),
PROPERTY_DISPLAY aDisplay = PROPERTY_DISPLAY::DEFAULT )
: PROPERTY<Owner, T>( aName, METHOD<Owner, T, Base>::Wrap( aSetter ),
METHOD<Owner, T, Base>::Wrap( aGetter ), aDisplay ),
m_choices( ENUM_MAP<T>::Instance().Choices() )
{
wxASSERT_MSG( m_choices.GetCount() > 0, "No enum choices defined" );
}
template<typename SetType, typename GetType>
PROPERTY_ENUM( const wxString& aName,
void ( Base::*aSetter )( SetType ), GetType( Base::*aGetter )() const,
PROPERTY_DISPLAY aDisplay = PROPERTY_DISPLAY::DEFAULT )
: PROPERTY<Owner, T>( aName, METHOD<Owner, T, Base>::Wrap( aSetter ),
METHOD<Owner, T, Base>::Wrap( aGetter ), aDisplay ),
m_choices( ENUM_MAP<T>::Instance().Choices() )
{
wxASSERT_MSG( m_choices.GetCount() > 0, "No enum choices defined" );
}
virtual void setter( void* obj, wxAny& v ) override
{
wxCHECK( !( PROPERTY<Owner, T, Base>::IsReadOnly() ), /*void*/ );
Owner* o = reinterpret_cast<Owner*>( obj );
if( v.CheckType<T>() )
{
T value = wxANY_AS(v, T);
(*PROPERTY<Owner, T, Base>::m_setter)( o, value );
}
else if (v.CheckType<int>() )
{
int value = wxANY_AS(v, int);
(*PROPERTY<Owner, T, Base>::m_setter)( o, static_cast<T>( value ) );
}
else
{
throw std::invalid_argument( "Invalid type requested" );
}
}
virtual wxAny getter( void* obj ) const override
{
Owner* o = reinterpret_cast<Owner*>( obj );
wxAny res = static_cast<T>( (*PROPERTY<Owner, T, Base>::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<typename Base, typename Derived>
class TYPE_CAST : public TYPE_CAST_BASE
{
public:
TYPE_CAST()
{
}
void* operator()( void* aPointer ) const override
{
Base* base = reinterpret_cast<Base*>( aPointer );
return static_cast<Derived*>( base );
}
const void* operator()( const void* aPointer ) const override
{
const Base* base = reinterpret_cast<const Base*>( aPointer );
return static_cast<const Derived*>( base );
}
size_t BaseHash() const override
{
return TYPE_HASH( Base );
}
size_t DerivedHash() const override
{
return TYPE_HASH( Derived );
}
};
template<typename T>
class ENUM_MAP
{
public:
static ENUM_MAP<T>& Instance()
{
static ENUM_MAP<T> 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<int>( aValue ) );
return *this;
}
void Reset()
{
m_choices.Clear();
}
const wxString& ToString( T value ) const
{
return m_choices.GetLabel( static_cast<int>( value ) );
}
const wxPGChoices& Choices() const
{
return m_choices;
}
void SetChoices( const wxPGChoices& aChoices )
{
m_choices = aChoices;
}
private:
wxPGChoices m_choices;
ENUM_MAP<T>()
{
}
};
// Helper macros to handle enum types
#define DECLARE_ENUM_TO_WXANY(type)\
template<>\
class wxAnyValueTypeImpl<type> : public wxAnyValueTypeImplBase<type>\
{\
WX_DECLARE_ANY_VALUE_TYPE(wxAnyValueTypeImpl<type>)\
public:\
wxAnyValueTypeImpl() : wxAnyValueTypeImplBase<type>() {}\
virtual ~wxAnyValueTypeImpl() {}\
virtual bool ConvertValue( const wxAnyValueBuffer& src,\
wxAnyValueType* dstType, wxAnyValueBuffer& dst ) const override\
{\
type value = GetValue(src);\
ENUM_MAP<type> conv = ENUM_MAP<type>::Instance();\
if( dstType->CheckType<wxString>() )\
{\
wxAnyValueTypeImpl<wxString>::SetValue( conv.ToString( value ), dst );\
return true;\
}\
if( dstType->CheckType<int>() )\
{\
wxAnyValueTypeImpl<int>::SetValue( static_cast<int>(value), dst );\
return true;\
}\
else\
{\
return false;\
}\
}\
};
#define IMPLEMENT_ENUM_TO_WXANY(type)\
WX_IMPLEMENT_ANY_VALUE_TYPE(wxAnyValueTypeImpl<type>)
#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<owner,type> prop##_owner##_name_( "##name#", setter, getter );\
};
*/
#endif /* PROPERTY_H */

215
include/property_mgr.h Normal file
View File

@ -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 <tomasz.wlostowski@cern.ch>
* @author Maciej Suminski <maciej.suminski@cern.ch>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef PROPERTY_MGR_H
#define PROPERTY_MGR_H
#include <wx/string.h>
#include <map>
#include <unordered_map>
#include <vector>
#include <memory>
#include <common.h>
class PROPERTY_BASE;
class TYPE_CAST_BASE;
///> Unique type identifier
using TYPE_ID = size_t;
using PROPERTY_LIST = std::vector<PROPERTY_BASE*>;
/**
* 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<void*>( 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<std::reference_wrapper<CLASS_DESC>> m_bases;
///> Properties unique to this type (i.e. not inherited)
std::map<wxString, std::unique_ptr<PROPERTY_BASE>> m_ownProperties;
///> Type converters available for this type
std::map<TYPE_ID, std::unique_ptr<TYPE_CAST_BASE>> m_typeCasts;
///> All properties (both unique to the type and inherited)
std::vector<PROPERTY_BASE*> 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<TYPE_ID, wxString> m_classNames;
///> Map of all available types
std::unordered_map<TYPE_ID, CLASS_DESC> 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 */

View File

@ -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

339
qa/common/test_property.cpp Normal file
View File

@ -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 <maciej.suminski@cern.ch>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <unit_test_utils/unit_test_utils.h>
#include <wx/gdicmn.h> // wxPoint
#include <inspectable.h>
#include <property_mgr.h>
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<enum_glob>::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, int>( "A", &A::setA, &A::getA ) );
propMgr.AddProperty( new PROPERTY<A, int>( "A2", &A::setA, &A::getA2 ) );
propMgr.AddProperty( new PROPERTY<A, wxPoint>( "point", &A::setPoint, &A::getPoint ) );
propMgr.AddProperty( new PROPERTY<A, wxPoint>( "point2", &A::setPoint, &A::getPoint2 ) );
propMgr.AddProperty( new PROPERTY<A, wxPoint>( "point3", &A::setPoint3, &A::getPoint3 ) );
propMgr.AddProperty( new PROPERTY<A, wxPoint>( "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<B, int>( "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<C, bool>( "bool", &C::setBool, &C::getBool ) );
propMgr.AddProperty( new PROPERTY<C, int>( "new", &C::setNew, &C::getNew ) );
}
} _CLASS_C_DESC;
static struct CLASS_D_DESC
{
CLASS_D_DESC()
{
ENUM_MAP<D::enum_class>::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<D, enum_glob>( "enumGlob", &D::setGlobEnum, &D::getGlobEnum ) );
propMgr.AddProperty( new PROPERTY_ENUM<D, D::enum_class>( "enumClass", &D::setClassEnum, &D::getClassEnum ) );
propMgr.AddProperty( new PROPERTY<D, wxPoint, A>( "point_alias", &D::setPoint, &D::getPoint ) );
// lines below are needed to indicate multiple inheritance
propMgr.AddTypeCast( new TYPE_CAST<D, A> );
propMgr.AddTypeCast( new TYPE_CAST<D, C> );
propMgr.InheritsAfter( TYPE_HASH( D ), TYPE_HASH( A ) );
propMgr.InheritsAfter( TYPE_HASH( D ), TYPE_HASH( C ) );
auto cond = new PROPERTY<D, int>( "cond", &D::setCond, &D::getCond );
cond->SetAvailableFunc( [=](INSPECTABLE* aItem)->bool { return *aItem->Get<int>( "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<int>( "A" ), 100 );
BOOST_CHECK_EQUAL( *ptr->Get<wxPoint>( "point" ), wxPoint( 100, 200 ) );
ptr = &d;
ptr->Set( "enumGlob", enum_glob::TEST2 );
ptr->Set( "enumClass", D::enum_class::TESTC );
BOOST_CHECK_EQUAL( *ptr->Get<enum_glob>( "enumGlob" ), enum_glob::TEST2 );
BOOST_CHECK_EQUAL( *ptr->Get<D::enum_class>( "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<int>( "A" ), 23 ); // unmodified == 23
ptr = &d;
ptr->Set( "A", 23 );
BOOST_CHECK_EQUAL( *ptr->Get<int>( "A" ), 46 ); // doubled == 46
}
// Non-existing properties
BOOST_AUTO_TEST_CASE( NotexistingProperties )
{
BOOST_CHECK_EQUAL( ptr->Set<int>( "does not exist", 5 ), false );
BOOST_CHECK_EQUAL( ptr->Get<int>( "neither" ).has_value(), false );
}
// Request data using incorrect type
BOOST_AUTO_TEST_CASE( IncorrectType )
{
BOOST_CHECK_THROW( ptr->Get<wxPoint>( "bool" ), std::invalid_argument );
}
// Type-casting (for types with multiple inheritance)
BOOST_AUTO_TEST_CASE( TypeCasting )
{
ptr = &d;
A* D_to_A = static_cast<A*>( propMgr.TypeCast( ptr, TYPE_HASH( D ), TYPE_HASH( A ) ) );
BOOST_CHECK_EQUAL( D_to_A, dynamic_cast<A*>( ptr ) );
C* D_to_C = static_cast<C*>( propMgr.TypeCast( ptr, TYPE_HASH( D ), TYPE_HASH( C ) ) );
BOOST_CHECK_EQUAL( D_to_C, dynamic_cast<C*>( 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<int>( enum_glob::TEST1 ) );
values.Add( static_cast<int>( enum_glob::TEST2 ) );
values.Add( static_cast<int>( 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<int>( D::enum_class::TESTA ) );
values.Add( static_cast<int>( D::enum_class::TESTB ) );
values.Add( static_cast<int>( 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<wxPoint>( "point" ), wxPoint( 100, 100 ) );
BOOST_CHECK_EQUAL( *ptr->Get<wxPoint>( "point_alias" ), wxPoint( 100, 100 ) );
ptr->Set( "point_alias", wxPoint( 300, 300 ) );
BOOST_CHECK_EQUAL( *ptr->Get<wxPoint>( "point" ), wxPoint( 300, 300 ) );
BOOST_CHECK_EQUAL( *ptr->Get<wxPoint>( "point_alias" ), wxPoint( 300, 300 ) );
}
BOOST_AUTO_TEST_SUITE_END()