Properties: introduce validators; re-enable zone hatch settings

(cherry picked from commit eb240fda9a)
This commit is contained in:
Jon Evans 2023-02-09 23:38:51 -05:00
parent d403e92315
commit 60a45241e6
10 changed files with 311 additions and 45 deletions

View File

@ -151,8 +151,8 @@ PROPERTY_BASE& PROPERTY_MANAGER::AddProperty( PROPERTY_BASE* aProperty, const wx
}
PROPERTY_BASE& PROPERTY_MANAGER::ReplaceProperty( size_t aBase, const wxString& aName, PROPERTY_BASE* aNew,
const wxString& aGroup )
PROPERTY_BASE& PROPERTY_MANAGER::ReplaceProperty( size_t aBase, const wxString& aName,
PROPERTY_BASE* aNew, const wxString& aGroup )
{
CLASS_DESC& classDesc = getClass( aNew->OwnerHash() );
classDesc.m_replaced.insert( std::make_pair( aBase, aName ) );

View File

@ -74,6 +74,7 @@ PROPERTIES_PANEL::PROPERTIES_PANEL( wxWindow* aParent, EDA_BASE_FRAME* aFrame )
wxPG_DEFAULT_STYLE );
m_grid->SetUnspecifiedValueAppearance( wxPGCell( wxT( "<...>" ) ) );
m_grid->SetExtraStyle( wxPG_EX_HELP_AS_TOOLTIPS );
m_grid->SetValidationFailureBehavior( wxPG_VFB_MARK_CELL );
m_grid->AddActionTrigger( wxPG_ACTION_NEXT_PROPERTY, WXK_RETURN );
m_grid->AddActionTrigger( wxPG_ACTION_NEXT_PROPERTY, WXK_NUMPAD_ENTER );
m_grid->AddActionTrigger( wxPG_ACTION_NEXT_PROPERTY, WXK_DOWN );

View File

@ -27,6 +27,7 @@
#include <core/wx_stl_compat.h>
#include <origin_transforms.h>
#include <properties/eda_angle_variant.h>
#include <properties/property_validator.h>
#include <wx/any.h>
#include <wx/string.h>
@ -187,7 +188,8 @@ PROPERTY_BASE( const wxString& aName, PROPERTY_DISPLAY aDisplay = PT_DEFAULT,
m_isInternal( false ),
m_isDeprecated( false ),
m_availFunc( [](INSPECTABLE*)->bool { return true; } ),
m_writeableFunc( [](INSPECTABLE*)->bool { return true; } )
m_writeableFunc( [](INSPECTABLE*)->bool { return true; } ),
m_validator( NullValidator )
{
}
@ -294,6 +296,22 @@ PROPERTY_BASE( const wxString& aName, PROPERTY_DISPLAY aDisplay = PT_DEFAULT,
wxString Group() const { return m_group; }
PROPERTY_BASE& SetGroup( const wxString& aGroup ) { m_group = aGroup; return *this; }
PROPERTY_BASE& SetValidator( PROPERTY_VALIDATOR_FN&& aValidator )
{
m_validator = aValidator;
return *this;
}
VALIDATOR_RESULT Validate( const wxAny&& aValue, EDA_ITEM* aItem )
{
return m_validator( std::move( aValue ), aItem );
}
static VALIDATOR_RESULT NullValidator( const wxAny&& aValue, EDA_ITEM* aItem )
{
return std::nullopt;
}
protected:
template<typename T>
void set( void* aObject, T aValue )
@ -338,8 +356,17 @@ private:
virtual wxAny getter( const void* aObject ) const = 0;
private:
const wxString m_name;
/**
* Permanent identifier for this property. Property names are an API contract; changing them
* after release will impact the Custom DRC Rules system as well as the automatic API binding
* system. Never rename properties; instead deprecate them and hide them from the GUI.
*/
const wxString m_name;
/// The display style controls how properties are edited in the properties manager GUI
PROPERTY_DISPLAY m_display;
/// The coordinate type controls how distances are mapped to the user coordinate system
ORIGIN_TRANSFORMS::COORD_TYPES_T m_coordType;
/// Internal properties are hidden from the GUI but not from the rules editor autocomplete
@ -355,6 +382,8 @@ private:
std::function<bool(INSPECTABLE*)> m_writeableFunc; ///< Eval to determine if prop is read-only
PROPERTY_VALIDATOR_FN m_validator;
friend class INSPECTABLE;
};

View File

@ -0,0 +1,53 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2023 Jon Evans <jon@craftyjon.com>
* Copyright (C) 2023 KiCad Developers, see AUTHORS.txt for contributors.
*
* 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 KICAD_PROPERTY_VALIDATOR_H
#define KICAD_PROPERTY_VALIDATOR_H
#include <functional>
#include <optional>
#include <wx/any.h>
#include <wx/string.h>
class EDA_ITEM;
class UNITS_PROVIDER;
/// Represents an error returned by a validator and contains enough data to format an error message
class VALIDATION_ERROR
{
public:
virtual ~VALIDATION_ERROR() = default;
virtual wxString Format( UNITS_PROVIDER* aUnits ) const = 0;
};
// TODO: This is a bit of code smell; maybe create a wrapper class to hide the ptr
/// Null optional means validation succeeded
using VALIDATOR_RESULT = std::optional<std::unique_ptr<VALIDATION_ERROR>>;
/**
* A property validator function takes in the data type of the owning property, and returns a
* VALIDATOR_RESULT that will be empty (null) if validation succeeded, and contain an error message
* otherwise.
*/
using PROPERTY_VALIDATOR_FN = std::function<VALIDATOR_RESULT( const wxAny&&, EDA_ITEM* aItem )>;
#endif //KICAD_PROPERTY_VALIDATOR_H

View File

@ -0,0 +1,132 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2023 Jon Evans <jon@craftyjon.com>
* Copyright (C) 2023 KiCad Developers, see AUTHORS.txt for contributors.
*
* 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 KICAD_PROPERTY_VALIDATORS_H
#define KICAD_PROPERTY_VALIDATORS_H
#include <properties/property_validator.h>
#include <units_provider.h>
/*
* This file includes some standard validators and errors. Include it only where needed, use
* properties/property_validator.h for most things.
*/
template<typename T>
class VALIDATION_ERROR_TOO_LARGE : public VALIDATION_ERROR
{
public:
T Actual;
T Maximum;
EDA_DATA_TYPE DataType;
VALIDATION_ERROR_TOO_LARGE( T aActual, T aMaximum,
EDA_DATA_TYPE aType = EDA_DATA_TYPE::DISTANCE ) :
Actual( aActual ),
Maximum( aMaximum ),
DataType( aType )
{}
wxString Format( UNITS_PROVIDER* aUnits ) const override
{
return wxString::Format( wxS( "Value must be less than or equal to %s" ),
aUnits->StringFromValue( Maximum, true ) );
}
};
template<typename T>
class VALIDATION_ERROR_TOO_SMALL : public VALIDATION_ERROR
{
public:
T Actual;
T Minimum;
EDA_DATA_TYPE DataType;
VALIDATION_ERROR_TOO_SMALL( T aActual, T aMinimum,
EDA_DATA_TYPE aType = EDA_DATA_TYPE::DISTANCE ) :
Actual( aActual ),
Minimum( aMinimum ),
DataType( aType )
{}
wxString Format( UNITS_PROVIDER* aUnits ) const override
{
return wxString::Format( wxS( "Value must be greater than or equal to %s" ),
aUnits->StringFromValue( Minimum, true ) );
}
};
/**
* A validator for use when you just need to return an error string rather than also packaging some
* other data (for example, a limit number)
*/
class VALIDATION_ERROR_MSG : public VALIDATION_ERROR
{
public:
wxString Message;
VALIDATION_ERROR_MSG( const wxString& aMessage ) : Message( aMessage ) {}
wxString Format( UNITS_PROVIDER* aUnits ) const override
{
return Message;
}
};
/**
* A set of generic validators
*/
class PROPERTY_VALIDATORS
{
public:
template<int Min, int Max>
static VALIDATOR_RESULT RangeIntValidator( const wxAny&& aValue, EDA_ITEM* aItem )
{
wxASSERT_MSG( aValue.CheckType<int>(), "Expecting int-containing value" );
int val = aValue.As<int>();
if( val > Max )
return std::make_unique<VALIDATION_ERROR_TOO_LARGE<int>>( val, Max );
else if( val < Min )
return std::make_unique<VALIDATION_ERROR_TOO_SMALL<int>>( val, Min );
return std::nullopt;
}
static VALIDATOR_RESULT PositiveIntValidator( const wxAny&& aValue, EDA_ITEM* aItem )
{
wxASSERT_MSG( aValue.CheckType<int>(), "Expecting int-containing value" );
int val = aValue.As<int>();
if( val < 0 )
return std::make_unique<VALIDATION_ERROR_TOO_SMALL<int>>( val, 0 );
return std::nullopt;
}
};
#endif //KICAD_PROPERTY_VALIDATORS_H

View File

@ -347,26 +347,8 @@ bool DIALOG_COPPER_ZONE::TransferDataToWindow()
m_gridStyleRotation.SetUnits( EDA_UNITS::DEGREES );
m_gridStyleRotation.SetAngleValue( m_settings.m_HatchOrientation );
// Gives a reasonable value to grid style parameters, if currently there are no defined
// parameters for grid pattern thickness and gap (if the value is 0)
// the grid pattern thickness default value is (arbitrary) m_ZoneMinThickness * 4
// or 1mm
// the grid pattern gap default value is (arbitrary) m_ZoneMinThickness * 6
// or 1.5 mm
int bestvalue = m_settings.m_HatchThickness;
if( bestvalue <= 0 ) // No defined value for m_HatchThickness
bestvalue = std::max( m_settings.m_ZoneMinThickness * 4, pcbIUScale.mmToIU( 1.0 ) );
m_gridStyleThickness.SetValue( std::max( bestvalue, m_settings.m_ZoneMinThickness ) );
bestvalue = m_settings.m_HatchGap;
if( bestvalue <= 0 ) // No defined value for m_HatchGap
bestvalue = std::max( m_settings.m_ZoneMinThickness * 6, pcbIUScale.mmToIU( 1.5 ) );
m_gridStyleGap.SetValue( std::max( bestvalue, m_settings.m_ZoneMinThickness ) );
m_gridStyleThickness.SetValue( m_settings.m_HatchThickness );
m_gridStyleGap.SetValue( m_settings.m_HatchGap );
m_spinCtrlSmoothLevel->SetValue( m_settings.m_HatchSmoothingLevel );
m_spinCtrlSmoothValue->SetValue( m_settings.m_HatchSmoothingValue );

View File

@ -172,19 +172,54 @@ wxPGProperty* PCB_PROPERTIES_PANEL::createPGProperty( const PROPERTY_BASE* aProp
}
void PCB_PROPERTIES_PANEL::valueChanged( wxPropertyGridEvent& aEvent )
PROPERTY_BASE* PCB_PROPERTIES_PANEL::getPropertyFromEvent( const wxPropertyGridEvent& aEvent ) const
{
PCB_SELECTION_TOOL* selectionTool = m_frame->GetToolManager()->GetTool<PCB_SELECTION_TOOL>();
const SELECTION& selection = selectionTool->GetSelection();
BOARD_ITEM* firstItem = static_cast<BOARD_ITEM*>( selection.Front() );
wxCHECK_MSG( firstItem, /* void */,
wxT( "valueChanged for a property with nothing selected!") );
wxCHECK_MSG( firstItem, nullptr,
wxT( "getPropertyFromEvent for a property with nothing selected!") );
PROPERTY_BASE* property = m_propMgr.GetProperty( TYPE_HASH( *firstItem ),
aEvent.GetPropertyName() );
wxCHECK_MSG( property, /* void */,
wxT( "valueChanged for a property not found on the selected item!" ) );
wxCHECK_MSG( property, nullptr,
wxT( "getPropertyFromEvent for a property not found on the selected item!" ) );
return property;
}
void PCB_PROPERTIES_PANEL::valueChanging( wxPropertyGridEvent& aEvent )
{
PCB_SELECTION_TOOL* selectionTool = m_frame->GetToolManager()->GetTool<PCB_SELECTION_TOOL>();
const SELECTION& selection = selectionTool->GetSelection();
EDA_ITEM* item = selection.Front();
PROPERTY_BASE* property = getPropertyFromEvent( aEvent );
wxCHECK( property, /* void */ );
wxCHECK( item, /* void */ );
wxVariant newValue = aEvent.GetPropertyValue();
if( VALIDATOR_RESULT validationFailure = property->Validate( newValue.GetAny(), item ) )
{
wxString errorMsg = wxString::Format( wxS( "%s: %s" ), wxGetTranslation( property->Name() ),
validationFailure->get()->Format( m_frame ) );
m_frame->ShowInfoBarError( errorMsg );
aEvent.Veto();
return;
}
}
void PCB_PROPERTIES_PANEL::valueChanged( wxPropertyGridEvent& aEvent )
{
PCB_SELECTION_TOOL* selectionTool = m_frame->GetToolManager()->GetTool<PCB_SELECTION_TOOL>();
const SELECTION& selection = selectionTool->GetSelection();
PROPERTY_BASE* property = getPropertyFromEvent( aEvent );
wxCHECK( property, /* void */ );
wxVariant newValue = aEvent.GetPropertyValue();
BOARD_COMMIT changes( m_frame );

View File

@ -44,6 +44,9 @@ public:
protected:
wxPGProperty* createPGProperty( const PROPERTY_BASE* aProperty ) const override;
PROPERTY_BASE* getPropertyFromEvent( const wxPropertyGridEvent& aEvent ) const;
void valueChanging( wxPropertyGridEvent& aEvent ) override;
void valueChanged( wxPropertyGridEvent& aEvent ) override;
///> Regenerates caches storing layer and net names

View File

@ -34,6 +34,7 @@
#include <zone.h>
#include <string_utils.h>
#include <math_for_graphics.h>
#include <properties/property_validators.h>
#include <settings/color_settings.h>
#include <settings/settings_manager.h>
#include <trigo.h>
@ -1431,6 +1432,8 @@ static struct ZONE_DESC
return false;
};
// Layer property is internal because it only holds a single layer and zones actually use
// a layer set
propMgr.ReplaceProperty( TYPE_HASH( BOARD_CONNECTED_ITEM ), _HKI( "Layer" ),
new PROPERTY_ENUM<ZONE, PCB_LAYER_ID>( _HKI( "Layer" ),
&ZONE::SetLayer,
@ -1452,36 +1455,51 @@ static struct ZONE_DESC
const wxString groupFill = _HKI( "Fill Style" );
// Fill mode can't be exposed to the UI until validation is moved to the ZONE class.
// see https://gitlab.com/kicad/code/kicad/-/issues/13811
propMgr.AddProperty( new PROPERTY_ENUM<ZONE, ZONE_FILL_MODE>(
_HKI( "Fill Mode" ), &ZONE::SetFillMode, &ZONE::GetFillMode ),
groupFill )
.SetIsInternal();
propMgr.AddProperty( new PROPERTY_ENUM<ZONE, ZONE_FILL_MODE>( _HKI( "Fill Mode" ),
&ZONE::SetFillMode,
&ZONE::GetFillMode ),
groupFill );
propMgr.AddProperty( new PROPERTY<ZONE, EDA_ANGLE>(
_HKI( "Orientation" ), &ZONE::SetHatchOrientation,
&ZONE::GetHatchOrientation, PROPERTY_DISPLAY::PT_DEGREE ),
propMgr.AddProperty( new PROPERTY<ZONE, EDA_ANGLE>( _HKI( "Orientation" ),
&ZONE::SetHatchOrientation,
&ZONE::GetHatchOrientation,
PROPERTY_DISPLAY::PT_DEGREE ),
groupFill )
.SetWriteableFunc( isHatchedFill )
.SetIsInternal();
.SetWriteableFunc( isHatchedFill );
//TODO: Switch to translated
// TODO: Switch to translated
auto atLeastMinWidthValidator =
[]( const wxAny&& aValue, EDA_ITEM* aZone ) -> VALIDATOR_RESULT
{
int val = aValue.As<int>();
ZONE* zone = dynamic_cast<ZONE*>( aZone );
wxCHECK( zone, std::nullopt );
if( val < zone->GetMinThickness() )
{
return std::make_unique<VALIDATION_ERROR_MSG>(
wxT( "Cannot be less than zone minimum width" ) );
}
return std::nullopt;
};
// TODO: Switch to translated
propMgr.AddProperty( new PROPERTY<ZONE, int>( wxT( "Hatch Width" ),
&ZONE::SetHatchThickness,
&ZONE::GetHatchThickness,
PROPERTY_DISPLAY::PT_SIZE ),
groupFill )
.SetWriteableFunc( isHatchedFill )
.SetIsInternal();
.SetValidator( atLeastMinWidthValidator );
//TODO: Switch to translated
// TODO: Switch to translated
propMgr.AddProperty( new PROPERTY<ZONE, int>( wxT( "Hatch Gap" ), &ZONE::SetHatchGap,
&ZONE::GetHatchGap,
PROPERTY_DISPLAY::PT_SIZE ),
groupFill )
.SetWriteableFunc( isHatchedFill )
.SetIsInternal();
.SetValidator( atLeastMinWidthValidator );
// TODO: Smoothing effort needs to change to enum (in dialog too)
// TODO: Smoothing amount (double)
@ -1493,11 +1511,16 @@ static struct ZONE_DESC
&ZONE::SetLocalClearance, &ZONE::GetLocalClearance,
PROPERTY_DISPLAY::PT_SIZE );
clearanceOverride->SetAvailableFunc( isCopperZone );
constexpr int maxClearance = pcbIUScale.mmToIU( ZONE_CLEARANCE_MAX_VALUE_MM );
clearanceOverride->SetValidator( PROPERTY_VALIDATORS::RangeIntValidator<0, maxClearance> );
auto minWidth = new PROPERTY<ZONE, int>( _HKI( "Minimum Width" ),
&ZONE::SetMinThickness, &ZONE::GetMinThickness,
PROPERTY_DISPLAY::PT_SIZE );
minWidth->SetAvailableFunc( isCopperZone );
constexpr int minMinWidth = pcbIUScale.mmToIU( ZONE_THICKNESS_MIN_VALUE_MM );
clearanceOverride->SetValidator( PROPERTY_VALIDATORS::RangeIntValidator<minMinWidth,
INT_MAX> );
auto padConnections = new PROPERTY_ENUM<ZONE, ZONE_CONNECTION>( _HKI( "Pad Connections" ),
&ZONE::SetPadConnection, &ZONE::GetPadConnection );
@ -1507,11 +1530,13 @@ static struct ZONE_DESC
&ZONE::SetThermalReliefGap, &ZONE::GetThermalReliefGap,
PROPERTY_DISPLAY::PT_SIZE );
thermalGap->SetAvailableFunc( isCopperZone );
thermalGap->SetValidator( PROPERTY_VALIDATORS::PositiveIntValidator );
auto thermalSpokeWidth = new PROPERTY<ZONE, int>( _HKI( "Thermal Relief Spoke Width" ),
&ZONE::SetThermalReliefSpokeWidth, &ZONE::GetThermalReliefSpokeWidth,
PROPERTY_DISPLAY::PT_SIZE );
thermalSpokeWidth->SetAvailableFunc( isCopperZone );
thermalSpokeWidth->SetValidator( atLeastMinWidthValidator );
propMgr.AddProperty( clearanceOverride, groupOverrides );
propMgr.AddProperty( minWidth, groupOverrides );

View File

@ -251,10 +251,16 @@ public:
int GetMinThickness() const { return m_ZoneMinThickness; }
void SetMinThickness( int aMinThickness )
{
if( m_ZoneMinThickness != aMinThickness )
if( m_ZoneMinThickness != aMinThickness
|| ( m_fillMode == ZONE_FILL_MODE::HATCH_PATTERN
&& ( m_hatchThickness < aMinThickness || m_hatchGap < aMinThickness ) ) )
{
SetNeedRefill( true );
}
m_ZoneMinThickness = aMinThickness;
m_hatchThickness = std::max( m_hatchThickness, aMinThickness );
m_hatchGap = std::max( m_hatchGap, aMinThickness );
}
int GetHatchThickness() const { return m_hatchThickness; }