Allow cross-referencing text substitutions.

This commit is contained in:
Jeff Young 2020-04-06 14:06:57 +01:00
parent 76bbb71402
commit aba0fa7bf8
18 changed files with 432 additions and 154 deletions

View File

@ -32,6 +32,8 @@
#include <gr_text.h>
#include <confirm.h>
#include <sch_text.h>
#include <sch_component.h>
#include <sch_reference_list.h>
#include <widgets/unit_binder.h>
#include <dialog_edit_label_base.h>
#include <kicad_string.h>
@ -180,15 +182,126 @@ DIALOG_LABEL_EDITOR::~DIALOG_LABEL_EDITOR()
}
wxString convertKIIDsToReferences( const wxString& aSource )
{
wxString newbuf;
size_t sourceLen = aSource.length();
for( size_t i = 0; i < sourceLen; ++i )
{
if( aSource[i] == '$' && i + 1 < sourceLen && aSource[i+1] == '{' )
{
wxString token;
bool isCrossRef = false;
for( i = i + 2; i < sourceLen; ++i )
{
if( aSource[i] == '}' )
break;
if( aSource[i] == ':' )
isCrossRef = true;
token.append( aSource[i] );
}
if( isCrossRef )
{
SCH_SHEET_LIST sheetList( g_RootSheet );
wxArrayString parts = wxSplit( token, ':' );
SCH_SHEET_PATH refSheetPath;
SCH_ITEM* refItem = sheetList.GetItem( KIID( parts[0] ), &refSheetPath );
if( refItem && refItem->Type() == SCH_COMPONENT_T )
{
SCH_COMPONENT* refComponent = static_cast<SCH_COMPONENT*>( refItem );
token = refComponent->GetRef( &refSheetPath, true ) + ":" + parts[1];
}
}
newbuf.append( "${" + token + "}" );
}
else
{
newbuf.append( aSource[i] );
}
}
return newbuf;
}
wxString convertReferencesToKIIDs( const wxString& aSource )
{
wxString newbuf;
size_t sourceLen = aSource.length();
for( size_t i = 0; i < sourceLen; ++i )
{
if( aSource[i] == '$' && i + 1 < sourceLen && aSource[i+1] == '{' )
{
wxString token;
bool isCrossRef = false;
for( i = i + 2; i < sourceLen; ++i )
{
if( aSource[i] == '}' )
break;
if( aSource[i] == ':' )
isCrossRef = true;
token.append( aSource[i] );
}
if( isCrossRef )
{
SCH_SHEET_LIST sheets( g_RootSheet );
wxArrayString parts = wxSplit( token, ':' );
SCH_REFERENCE_LIST references;
sheets.GetComponents( references );
for( size_t jj = 0; jj < references.GetCount(); jj++ )
{
SCH_COMPONENT* refComponent = references[ jj ].GetComp();
wxString ref = refComponent->GetRef( &references[ jj ].GetSheetPath() );
if( ref == parts[0] )
{
token = refComponent->m_Uuid.AsString() + ":" + parts[1];
break;
}
}
}
newbuf.append( "${" + token + "}" );
}
else
{
newbuf.append( aSource[i] );
}
}
return newbuf;
}
bool DIALOG_LABEL_EDITOR::TransferDataToWindow()
{
if( !wxDialog::TransferDataToWindow() )
return false;
if( m_activeTextEntry )
m_activeTextEntry->SetValue( UnescapeString( m_CurrentText->GetText() ) );
if( m_CurrentText->Type() == SCH_TEXT_T )
{
// show text variable cross-references in a human-readable format
m_valueMultiLine->SetValue( convertKIIDsToReferences( m_CurrentText->GetText() ) );
}
else
m_valueMultiLine->SetValue( UnescapeString( m_CurrentText->GetText() ) );
{
// show control characters in a human-readable format
m_activeTextEntry->SetValue( UnescapeString( m_CurrentText->GetText() ) );
}
if( m_valueCombo->IsShown() )
{
@ -308,11 +421,16 @@ bool DIALOG_LABEL_EDITOR::TransferDataFromWindow()
m_Parent->GetCanvas()->Refresh();
// Escape string only if is is a label. For a simple graphic text do not change anything
if( m_CurrentText->Type() == SCH_TEXT_T )
text = m_valueMultiLine->GetValue();
{
// convert any text variable cross-references to their UUIDs
text = convertReferencesToKIIDs( m_valueMultiLine->GetValue() );
}
else
{
// labels need escaping
text = EscapeString( m_activeTextEntry->GetValue(), CTX_NETNAME );
}
if( !text.IsEmpty() )
m_CurrentText->SetText( text );

View File

@ -931,6 +931,53 @@ void SCH_COMPONENT::SwapData( SCH_ITEM* aItem )
}
bool SCH_COMPONENT::ResolveTextVar( wxString* token, int aDepth ) const
{
for( int i = 0; i < MANDATORY_FIELDS; ++i )
{
if( token->IsSameAs( m_Fields[ i ].GetCanonicalName().Upper() ) )
{
*token = m_Fields[ i ].GetShownText( aDepth + 1 );
return true;
}
}
for( size_t i = MANDATORY_FIELDS; i < m_Fields.size(); ++i )
{
if( token->IsSameAs( m_Fields[ i ].GetName() )
|| token->IsSameAs( m_Fields[ i ].GetName().Upper() ) )
{
*token = m_Fields[ i ].GetShownText( aDepth + 1 );
return true;
}
}
if( token->IsSameAs( wxT( "FOOTPRINT_LIBRARY" ) ) )
{
const SCH_FIELD& field = m_Fields[ FOOTPRINT ];
wxArrayString parts = wxSplit( field.GetText(), ':' );
*token = parts[ 0 ];
return true;
}
else if( token->IsSameAs( wxT( "FOOTPRINT_NAME" ) ) )
{
const SCH_FIELD& field = m_Fields[ FOOTPRINT ];
wxArrayString parts = wxSplit( field.GetText(), ':' );
*token = parts[ std::min( 1, (int) parts.size() - 1 ) ];
return true;
}
else if( token->IsSameAs( wxT( "UNIT" ) ) )
{
*token = LIB_PART::SubReference( GetUnit() );
return true;
}
return false;
}
void SCH_COMPONENT::ClearAnnotation( SCH_SHEET_PATH* aSheetPath )
{
// Build a reference with no annotation,

View File

@ -305,6 +305,12 @@ public:
*/
int GetOrientation();
/**
* Resolve any references to system tokens supported by the component.
* @param aDepth a counter to limit recursion and circular references.
*/
bool ResolveTextVar( wxString* token, int aDepth = 0 ) const;
void GetMsgPanelInfo( EDA_UNITS aUnits, std::vector<MSG_PANEL_ITEM>& aList ) override;
/**

View File

@ -72,103 +72,20 @@ EDA_ITEM* SCH_FIELD::Clone() const
}
wxString SCH_FIELD::GetShownText() const
wxString SCH_FIELD::GetShownText( int aDepth ) const
{
std::function<bool( wxString* )> symbolResolver =
[this]( wxString* token ) -> bool
[&]( wxString* token ) -> bool
{
SCH_COMPONENT* component = static_cast<SCH_COMPONENT*>( m_Parent );
std::vector<SCH_FIELD>& fields = component->GetFields();
for( int i = 0; i < MANDATORY_FIELDS; ++i )
{
if( token->IsSameAs( fields[ i ].GetCanonicalName().Upper() ) )
{
// silently drop recursive references
if( &fields[ i ] == this )
*token = wxEmptyString;
else
*token = fields[ i ].GetShownText();
return true;
}
}
for( size_t i = MANDATORY_FIELDS; i < fields.size(); ++i )
{
if( token->IsSameAs( fields[ i ].GetName() )
|| token->IsSameAs( fields[ i ].GetName().Upper() ) )
{
// silently drop recursive references
if( &fields[ i ] == this )
*token = wxEmptyString;
else
*token = fields[ i ].GetShownText();
return true;
}
}
if( token->IsSameAs( wxT( "FOOTPRINT_LIBRARY" ) ) )
{
SCH_FIELD& f = component->GetFields()[ FOOTPRINT ];
wxArrayString parts = wxSplit( f.GetText(), ':' );
*token = parts[ 0 ];
return true;
}
else if( token->IsSameAs( wxT( "FOOTPRINT_NAME" ) ) )
{
SCH_FIELD& f = component->GetFields()[ FOOTPRINT ];
wxArrayString parts = wxSplit( f.GetText(), ':' );
*token = parts[ std::min( 1, (int) parts.size() - 1 ) ];
return true;
}
else if( token->IsSameAs( wxT( "UNIT" ) ) )
{
*token = LIB_PART::SubReference( component->GetUnit() );
return true;
}
return false;
return component->ResolveTextVar( token, aDepth + 1 );
};
std::function<bool( wxString* )> sheetResolver =
[&]( wxString* token ) -> bool
{
SCH_SHEET* sheet = static_cast<SCH_SHEET*>( m_Parent );
std::vector<SCH_FIELD>& fields = sheet->GetFields();
for( int i = 0; i < SHEET_MANDATORY_FIELDS; ++i )
{
if( token->IsSameAs( fields[ i ].GetCanonicalName().Upper() ) )
{
// silently drop recursive references
if( &fields[ i ] == this )
*token = wxEmptyString;
else
*token = fields[ i ].GetShownText();
return true;
}
}
for( size_t i = SHEET_MANDATORY_FIELDS; i < fields.size(); ++i )
{
if( token->IsSameAs( fields[ i ].GetName() ) )
{
// silently drop recursive references
if( &fields[ i ] == this )
*token = wxEmptyString;
else
*token = fields[ i ].GetShownText();
return true;
}
}
return false;
return sheet->ResolveTextVar( token, aDepth + 1 );
};
PROJECT* project = nullptr;
@ -177,10 +94,15 @@ wxString SCH_FIELD::GetShownText() const
if( g_RootSheet && g_RootSheet->GetScreen() )
project = &g_RootSheet->GetScreen()->Kiway().Prj();
if( m_Parent && m_Parent->Type() == SCH_COMPONENT_T )
text = ExpandTextVars( text, &symbolResolver, project );
else if( m_Parent && m_Parent->Type() == SCH_SHEET_T )
text = ExpandTextVars( text, &sheetResolver, project );
if( aDepth < 10 )
{
if( m_Parent && m_Parent->Type() == SCH_COMPONENT_T )
text = ExpandTextVars( text, &symbolResolver, project );
else if( m_Parent && m_Parent->Type() == SCH_SHEET_T )
text = ExpandTextVars( text, &sheetResolver, project );
else
text = ExpandTextVars( text, nullptr, project );
}
// WARNING: the IDs of FIELDS and SHEETS overlap, so one must check *both* the
// id and the parent's type.
@ -197,8 +119,7 @@ wxString SCH_FIELD::GetShownText() const
text << LIB_PART::SubReference( component->GetUnit() );
}
}
if( m_Parent && m_Parent->Type() == SCH_SHEET_T )
else if( m_Parent && m_Parent->Type() == SCH_SHEET_T )
{
if( m_id == SHEETFILENAME )
text = _( "File: " ) + text;

View File

@ -115,7 +115,7 @@ public:
void SetId( int aId ) { m_id = aId; }
wxString GetShownText() const override;
wxString GetShownText( int aDepth = 0 ) const override;
const EDA_RECT GetBoundingBox() const override;

View File

@ -190,6 +190,30 @@ SCH_SHEET* SCH_SHEET::GetRootSheet()
}
bool SCH_SHEET::ResolveTextVar( wxString* token, int aDepth ) const
{
for( int i = 0; i < SHEET_MANDATORY_FIELDS; ++i )
{
if( token->IsSameAs( m_fields[i].GetCanonicalName().Upper() ) )
{
*token = m_fields[i].GetShownText( aDepth + 1 );
return true;
}
}
for( size_t i = SHEET_MANDATORY_FIELDS; i < m_fields.size(); ++i )
{
if( token->IsSameAs( m_fields[i].GetName() ) )
{
*token = m_fields[i].GetShownText( aDepth + 1 );
return true;
}
}
return false;
}
bool SCH_SHEET::UsesDefaultStroke() const
{
return m_borderWidth == 0 && m_borderColor == COLOR4D::UNSPECIFIED;

View File

@ -336,6 +336,12 @@ public:
*/
int GetScreenCount() const;
/**
* Resolve any references to system tokens supported by the sheet.
* @param aDepth a counter to limit recursion and circular references.
*/
bool ResolveTextVar( wxString* token, int aDepth = 0 ) const;
void GetMsgPanelInfo( EDA_UNITS aUnits, std::vector<MSG_PANEL_ITEM>& aList ) override;
/* there is no member for orientation in sch_sheet, to preserve file

View File

@ -33,6 +33,7 @@
#include <macros.h>
#include <trigo.h>
#include <sch_draw_panel.h>
#include <sch_component.h>
#include <gr_text.h>
#include <sch_edit_frame.h>
#include <plotter.h>
@ -450,10 +451,10 @@ wxString getElectricalTypeLabel( PINSHEETLABEL_SHAPE aType )
}
wxString SCH_TEXT::GetShownText() const
wxString SCH_TEXT::GetShownText( int aDepth ) const
{
std::function<bool( wxString* )> textResolver =
[this]( wxString* token ) -> bool
[&]( wxString* token ) -> bool
{
if( ( Type() == SCH_GLOBAL_LABEL_T
|| Type() == SCH_HIER_LABEL_T
@ -467,23 +468,39 @@ wxString SCH_TEXT::GetShownText() const
if( Type() == SCH_SHEET_PIN_T && m_Parent )
{
SCH_SHEET* sheet = static_cast<SCH_SHEET*>( m_Parent );
std::vector<SCH_FIELD>& fields = sheet->GetFields();
for( int i = 0; i < SHEET_MANDATORY_FIELDS; ++i )
if( sheet->ResolveTextVar( token, aDepth ) )
return true;
}
if( Type() == SCH_TEXT_T )
{
if( token->Contains( ':' ) )
{
if( token->IsSameAs( fields[i].GetCanonicalName().Upper() ) )
SCH_SHEET_LIST sheetList( g_RootSheet );
wxArrayString parts = wxSplit( *token, ':' );
SCH_SHEET_PATH dummy;
SCH_ITEM* refItem = sheetList.GetItem( KIID( parts[0] ), &dummy );
if( refItem && refItem->Type() == SCH_COMPONENT_T )
{
*token = fields[i].GetShownText();
return true;
SCH_COMPONENT* refComponent = static_cast<SCH_COMPONENT*>( refItem );
if( refComponent->ResolveTextVar( &parts[1], aDepth + 1 ) )
{
*token = parts[1];
return true;
}
}
}
for( size_t i = SHEET_MANDATORY_FIELDS; i < fields.size(); ++i )
{
if( token->IsSameAs( fields[i].GetName() ) )
else if( refItem && refItem->Type() == SCH_SHEET_T )
{
*token = fields[i].GetShownText();
return true;
SCH_SHEET* refSheet = static_cast<SCH_SHEET*>( refItem );
if( refSheet->ResolveTextVar( &parts[1], aDepth + 1 ) )
{
*token = parts[1];
return true;
}
}
}
}
@ -496,7 +513,12 @@ wxString SCH_TEXT::GetShownText() const
if( g_RootSheet && g_RootSheet->GetScreen() )
project = &g_RootSheet->GetScreen()->Kiway().Prj();
return ExpandTextVars( EDA_TEXT::GetShownText(), &textResolver, project );
wxString text = EDA_TEXT::GetShownText( aDepth );
if( aDepth < 10 )
text = ExpandTextVars( text, &textResolver, project );
return text;
}

View File

@ -207,7 +207,7 @@ public:
return wxT( "SCH_TEXT" );
}
wxString GetShownText() const override;
wxString GetShownText( int aDepth = 0 ) const override;
/**
* Increment the label text, if it ends with a number.

View File

@ -123,10 +123,11 @@ public:
virtual const wxString& GetText() const { return m_text; }
/**
* Return the string actually shown after processing of the base
* text. Default is no processing
* Return the string actually shown after processing of the base text.
* @aParam aDepth is used to prevent infinite recusions and loops when expanding
* text variables.
*/
virtual wxString GetShownText() const { return m_shown_text; }
virtual wxString GetShownText( int aDepth = 0 ) const { return m_shown_text; }
/**
* Returns a shortened version (max 15 characters) of the shown text

View File

@ -260,6 +260,28 @@ MODULE& MODULE::operator=( const MODULE& aOther )
}
bool MODULE::ResolveTextVar( wxString* token, int aDepth ) const
{
if( token->IsSameAs( wxT( "REFERENCE" ) ) )
{
*token = m_Reference->GetShownText( aDepth + 1 );
return true;
}
else if( token->IsSameAs( wxT( "VALUE" ) ) )
{
*token = m_Value->GetShownText( aDepth + 1 );
return true;
}
else if( token->IsSameAs( wxT( "LAYER" ) ) )
{
*token = GetLayerName();
return true;
}
return false;
}
void MODULE::ClearAllNets()
{
// Force the ORPHANED dummy net info for all pads.

View File

@ -411,6 +411,12 @@ public:
void TransformGraphicTextWithClearanceToPolygonSet( PCB_LAYER_ID aLayer,
SHAPE_POLY_SET& aCornerBuffer, int aInflateValue, int aError = ARC_HIGH_DEF ) const;
/**
* Resolve any references to system tokens supported by the component.
* @param aDepth a counter to limit recursion and circular references.
*/
bool ResolveTextVar( wxString* token, int aDepth = 0 ) const;
///> @copydoc EDA_ITEM::GetMsgPanelInfo
void GetMsgPanelInfo( EDA_UNITS aUnits, std::vector<MSG_PANEL_ITEM>& aList ) override;

View File

@ -61,13 +61,13 @@ TEXTE_PCB::~TEXTE_PCB()
}
wxString TEXTE_PCB::GetShownText() const
wxString TEXTE_PCB::GetShownText( int aDepth ) const
{
const BOARD* board = static_cast<BOARD*>( GetParent() );
BOARD* board = static_cast<BOARD*>( GetParent() );
wxASSERT( board );
std::function<bool( wxString* )> moduleResolver =
[ this ]( wxString* token ) -> bool
std::function<bool( wxString* )> pcbTextResolver =
[&]( wxString* token ) -> bool
{
if( token->IsSameAs( wxT( "LAYER" ) ) )
{
@ -75,10 +75,31 @@ wxString TEXTE_PCB::GetShownText() const
return true;
}
if( token->Contains( ':' ) )
{
wxArrayString parts = wxSplit( *token, ':' );
BOARD_ITEM* refItem = board->GetItem( KIID( parts[0] ) );
if( refItem && refItem->Type() == PCB_MODULE_T )
{
MODULE* refModule = static_cast<MODULE*>( refItem );
if( refModule->ResolveTextVar( &parts[1], aDepth + 1 ) )
{
*token = parts[1];
return true;
}
}
}
return false;
};
return ExpandTextVars( EDA_TEXT::GetShownText(), &moduleResolver, board->GetProject() );
wxString text = EDA_TEXT::GetShownText( aDepth );
if( aDepth < 10 )
text = ExpandTextVars( text, &pcbTextResolver, board->GetProject() );
return text;
}

View File

@ -53,7 +53,7 @@ public:
return aItem && PCB_TEXT_T == aItem->Type();
}
wxString GetShownText() const override;
wxString GetShownText( int aDepth = 0 ) const override;
bool Matches( wxFindReplaceData& aSearchData, void* aAuxData ) override
{

View File

@ -488,38 +488,25 @@ unsigned int TEXTE_MODULE::ViewGetLOD( int aLayer, KIGFX::VIEW* aView ) const
}
wxString TEXTE_MODULE::GetShownText() const
wxString TEXTE_MODULE::GetShownText( int aDepth ) const
{
const MODULE* module = static_cast<MODULE*>( GetParent() );
wxASSERT( module );
const BOARD* board = static_cast<BOARD*>( module->GetParent() );
wxASSERT( board );
wxASSERT( module );
std::function<bool( wxString* )> moduleResolver =
[ this, module ]( wxString* token ) -> bool
[&]( wxString* token ) -> bool
{
if( module )
{
if( token->IsSameAs( wxT( "REFERENCE" ) ) )
{
*token = module->GetReference();
return true;
}
else if( token->IsSameAs( wxT( "VALUE" ) ) )
{
*token = module->GetValue();
return true;
}
else if( token->IsSameAs( wxT( "LAYER" ) ) )
{
*token = GetLayerName();
return true;
}
}
return false;
return module && module->ResolveTextVar( token, aDepth );
};
return ExpandTextVars( EDA_TEXT::GetShownText(), &moduleResolver, board->GetProject() );
PROJECT* project = nullptr;
wxString text = EDA_TEXT::GetShownText( aDepth );
if( module && module->GetParent() )
project = static_cast<BOARD*>( module->GetParent() )->GetProject();
if( aDepth < 10 )
text = ExpandTextVars( text, &moduleResolver, project );
return text;
}

View File

@ -216,7 +216,7 @@ public:
EDA_ITEM* Clone() const override;
virtual wxString GetShownText() const override;
virtual wxString GetShownText( int aDepth = 0 ) const override;
virtual const BOX2I ViewBBox() const override;

View File

@ -291,6 +291,99 @@ void DIALOG_TEXT_PROPERTIES::OnDimensionUnitsChange( wxCommandEvent& event )
}
wxString DIALOG_TEXT_PROPERTIES::convertKIIDsToReferences( const wxString& aSource )
{
wxString newbuf;
size_t sourceLen = aSource.length();
for( size_t i = 0; i < sourceLen; ++i )
{
if( aSource[i] == '$' && i + 1 < sourceLen && aSource[i+1] == '{' )
{
wxString token;
bool isCrossRef = false;
for( i = i + 2; i < sourceLen; ++i )
{
if( aSource[i] == '}' )
break;
if( aSource[i] == ':' )
isCrossRef = true;
token.append( aSource[i] );
}
if( isCrossRef )
{
wxArrayString parts = wxSplit( token, ':' );
BOARD_ITEM* refItem = m_Parent->GetBoard()->GetItem( KIID( parts[0] ) );
if( refItem && refItem->Type() == PCB_MODULE_T )
token = static_cast<MODULE*>( refItem )->GetReference() + ":" + parts[1];
}
newbuf.append( "${" + token + "}" );
}
else
{
newbuf.append( aSource[i] );
}
}
return newbuf;
}
wxString DIALOG_TEXT_PROPERTIES::convertReferencesToKIIDs( const wxString& aSource )
{
wxString newbuf;
size_t sourceLen = aSource.length();
for( size_t i = 0; i < sourceLen; ++i )
{
if( aSource[i] == '$' && i + 1 < sourceLen && aSource[i+1] == '{' )
{
wxString token;
bool isCrossRef = false;
for( i = i + 2; i < sourceLen; ++i )
{
if( aSource[i] == '}' )
break;
if( aSource[i] == ':' )
isCrossRef = true;
token.append( aSource[i] );
}
if( isCrossRef )
{
wxArrayString parts = wxSplit( token, ':' );
for( MODULE* mod : m_Parent->GetBoard()->Modules() )
{
if( mod->GetReference().CmpNoCase( parts[0] ) == 0 )
{
token = mod->m_Uuid.AsString() + ":" + parts[1];
break;
}
}
}
newbuf.append( "${" + token + "}" );
}
else
{
newbuf.append( aSource[i] );
}
}
return newbuf;
}
bool DIALOG_TEXT_PROPERTIES::TransferDataToWindow()
{
if( m_SingleLineText->IsShown() )
@ -304,7 +397,7 @@ bool DIALOG_TEXT_PROPERTIES::TransferDataToWindow()
}
else if( m_MultiLineText->IsShown() )
{
m_MultiLineText->SetValue( m_edaText->GetText() );
m_MultiLineText->SetValue( convertKIIDsToReferences( m_edaText->GetText() ) );
m_MultiLineText->SetSelection( -1, -1 );
}
else if (m_DimensionText->IsShown() )
@ -403,10 +496,11 @@ bool DIALOG_TEXT_PROPERTIES::TransferDataFromWindow()
{
if( !m_MultiLineText->GetValue().IsEmpty() )
{
wxString txt = convertReferencesToKIIDs( m_MultiLineText->GetValue() );
// On Windows, a new line is coded as \r\n.
// We use only \n in kicad files and in drawing routines.
// so strip the \r char
wxString txt = m_MultiLineText->GetValue();
#ifdef __WINDOWS__
txt.Replace( "\r", "" );
#endif

View File

@ -60,6 +60,9 @@ private:
wxFloatingPointValidator<double> m_OrientValidator;
double m_OrientValue;
wxString convertReferencesToKIIDs( const wxString& aSource );
wxString convertKIIDsToReferences( const wxString& aSource );
bool TransferDataToWindow() override;
bool TransferDataFromWindow() override;