/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2015 Jean-Pierre Charras, jp.charras at wanadoo.fr * Copyright (C) 2004-2024 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 2 * 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, you may find one here: * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * or you may search the http://www.gnu.org website for the version 2 license, * or you may write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ /* * Fields are texts attached to a symbol, some of which have a special meaning. * Fields 0 and 1 are very important: reference and value. * Field 2 is used as default footprint name. * Field 3 is used to point to a datasheet (usually a URL). * Fields 4+ are user fields. They can be renamed and can appear in reports. */ #include #include #include #include // for ExpandTextVars #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sim/sim_lib_mgr.h" SCH_FIELD::SCH_FIELD( const VECTOR2I& aPos, int aFieldId, SCH_ITEM* aParent, const wxString& aName ) : SCH_ITEM( aParent, SCH_FIELD_T ), EDA_TEXT( schIUScale, wxEmptyString ), m_id( 0 ), m_showName( false ), m_allowAutoPlace( true ), m_autoAdded( false ), m_showInChooser( true ), m_renderCacheValid( false ), m_lastResolvedColor( COLOR4D::UNSPECIFIED ) { if( aName.IsEmpty() ) SetName( TEMPLATE_FIELDNAME::GetDefaultFieldName( aFieldId ) ); else SetName( aName ); SetTextPos( aPos ); SetId( aFieldId ); // will also set the layer SetVisible( true ); } SCH_FIELD::SCH_FIELD( SCH_ITEM* aParent, int aFieldId, const wxString& aName) : SCH_FIELD( VECTOR2I(), aFieldId, aParent, aName ) { } SCH_FIELD::SCH_FIELD( const SCH_FIELD& aField ) : SCH_ITEM( aField ), EDA_TEXT( aField ) { m_id = aField.m_id; m_name = aField.m_name; m_showName = aField.m_showName; m_allowAutoPlace = aField.m_allowAutoPlace; m_isNamedVariable = aField.m_isNamedVariable; m_autoAdded = aField.m_autoAdded; m_showInChooser = aField.m_showInChooser; m_renderCache.clear(); for( const std::unique_ptr& glyph : aField.m_renderCache ) { if( KIFONT::OUTLINE_GLYPH* outline = dynamic_cast( glyph.get() ) ) m_renderCache.emplace_back( std::make_unique( *outline ) ); else if( KIFONT::STROKE_GLYPH* stroke = dynamic_cast( glyph.get() ) ) m_renderCache.emplace_back( std::make_unique( *stroke ) ); } m_renderCacheValid = aField.m_renderCacheValid; m_renderCachePos = aField.m_renderCachePos; m_lastResolvedColor = aField.m_lastResolvedColor; } SCH_FIELD& SCH_FIELD::operator=( const SCH_FIELD& aField ) { EDA_TEXT::operator=( aField ); m_id = aField.m_id; m_name = aField.m_name; m_showName = aField.m_showName; m_allowAutoPlace = aField.m_allowAutoPlace; m_isNamedVariable = aField.m_isNamedVariable; m_renderCache.clear(); for( const std::unique_ptr& glyph : aField.m_renderCache ) { if( KIFONT::OUTLINE_GLYPH* outline = dynamic_cast( glyph.get() ) ) m_renderCache.emplace_back( std::make_unique( *outline ) ); else if( KIFONT::STROKE_GLYPH* stroke = dynamic_cast( glyph.get() ) ) m_renderCache.emplace_back( std::make_unique( *stroke ) ); } m_renderCacheValid = aField.m_renderCacheValid; m_renderCachePos = aField.m_renderCachePos; m_lastResolvedColor = aField.m_lastResolvedColor; return *this; } EDA_ITEM* SCH_FIELD::Clone() const { return new SCH_FIELD( *this ); } void SCH_FIELD::Copy( SCH_FIELD* aTarget ) const { *aTarget = *this; } void SCH_FIELD::SetId( int aId ) { m_id = aId; if( m_parent && m_parent->Type() == SCH_SHEET_T ) { switch( m_id ) { case SHEETNAME: SetLayer( LAYER_SHEETNAME ); break; case SHEETFILENAME: SetLayer( LAYER_SHEETFILENAME ); break; default: SetLayer( LAYER_SHEETFIELDS ); break; } } else if( m_parent && ( m_parent->Type() == SCH_SYMBOL_T || m_parent->Type() == LIB_SYMBOL_T ) ) { switch( m_id ) { case REFERENCE_FIELD: SetLayer( LAYER_REFERENCEPART ); break; case VALUE_FIELD: SetLayer( LAYER_VALUEPART ); break; default: SetLayer( LAYER_FIELDS ); break; } } else if( m_parent && m_parent->IsType( { SCH_LABEL_LOCATE_ANY_T } ) ) { // We can't use defined IDs for labels because there can be multiple net class // assignments. if( GetCanonicalName() == wxT( "Netclass" ) ) SetLayer( LAYER_NETCLASS_REFS ); else if( GetCanonicalName() == wxT( "Intersheetrefs" ) ) SetLayer( LAYER_INTERSHEET_REFS ); else SetLayer( LAYER_FIELDS ); } } wxString SCH_FIELD::GetShownName() const { return m_isNamedVariable ? GetTextVars( GetName() ) : GetName(); } wxString SCH_FIELD::GetShownText( const SCH_SHEET_PATH* aPath, bool aAllowExtraText, int aDepth ) const { std::function symbolResolver = [&]( wxString* token ) -> bool { SCH_SYMBOL* symbol = static_cast( m_parent ); return symbol->ResolveTextVar( aPath, token, aDepth + 1 ); }; std::function schematicResolver = [&]( wxString* token ) -> bool { if( SCHEMATIC* schematic = Schematic() ) return schematic->ResolveTextVar( aPath, token, aDepth + 1 ); return false; }; std::function sheetResolver = [&]( wxString* token ) -> bool { SCH_SHEET* sheet = static_cast( m_parent ); SCHEMATIC* schematic = Schematic(); SCH_SHEET_PATH path = *aPath; path.push_back( sheet ); bool retval = sheet->ResolveTextVar( &path, token, aDepth + 1 ); if( schematic ) retval |= schematic->ResolveTextVar( &path, token, aDepth + 1 ); return retval; }; std::function labelResolver = [&]( wxString* token ) -> bool { SCH_LABEL_BASE* label = static_cast( m_parent ); return label->ResolveTextVar( aPath, token, aDepth + 1 ); }; wxString text = EDA_TEXT::GetShownText( aAllowExtraText, aDepth ); if( IsNameShown() && aAllowExtraText ) text = GetShownName() << wxS( ": " ) << text; if( !aPath ) return text; if( text == wxS( "~" ) ) // Legacy placeholder for empty string { text = wxS( "" ); } for( int ii = 0; ii < 10 && text.Contains( wxT( "${" ) ); ++ii ) { if( aDepth < 10 ) { if( m_parent && m_parent->Type() == SCH_SYMBOL_T ) text = ExpandTextVars( text, &symbolResolver ); else if( m_parent && m_parent->Type() == SCH_SHEET_T ) text = ExpandTextVars( text, &sheetResolver ); else if( m_parent && m_parent->IsType( { SCH_LABEL_LOCATE_ANY_T } ) ) text = ExpandTextVars( text, &labelResolver ); else if( Schematic() ) { text = ExpandTextVars( text, &Schematic()->Prj() ); text = ExpandTextVars( text, &schematicResolver ); } } } // WARNING: the IDs of FIELDS and SHEETS overlap, so one must check *both* the // id and the parent's type. if( m_parent && m_parent->Type() == SCH_SYMBOL_T ) { SCH_SYMBOL* parentSymbol = static_cast( m_parent ); if( m_id == REFERENCE_FIELD ) { // For more than one part per package, we must add the part selection // A, B, ... or 1, 2, .. to the reference. if( parentSymbol->GetUnitCount() > 1 ) text << parentSymbol->SubReference( parentSymbol->GetUnitSelection( aPath ) ); } } else if( m_parent && m_parent->Type() == SCH_SHEET_T ) { if( m_id == SHEETFILENAME && aAllowExtraText && !IsNameShown() ) text = _( "File:" ) + wxS( " " ) + text; } return text; } wxString SCH_FIELD::GetShownText( bool aAllowExtraText, int aDepth ) const { if( SCHEMATIC* schematic = Schematic() ) return GetShownText( &schematic->CurrentSheet(), aAllowExtraText, aDepth ); else return GetShownText( nullptr, aAllowExtraText, aDepth ); } wxString SCH_FIELD::GetFullText( int unit ) const { if( GetId() != REFERENCE_FIELD ) return GetText(); wxString text = GetText(); text << wxT( "?" ); if( GetParentSymbol() && GetParentSymbol()->IsMulti() ) text << LIB_SYMBOL::LetterSubReference( unit, 'A' ); return text; } int SCH_FIELD::GetPenWidth() const { return GetEffectiveTextPenWidth(); } KIFONT::FONT* SCH_FIELD::getDrawFont() const { KIFONT::FONT* font = EDA_TEXT::GetFont(); if( !font ) font = KIFONT::FONT::GetFont( GetDefaultFont(), IsBold(), IsItalic() ); return font; } void SCH_FIELD::ClearCaches() { ClearRenderCache(); EDA_TEXT::ClearBoundingBoxCache(); } void SCH_FIELD::ClearRenderCache() { EDA_TEXT::ClearRenderCache(); m_renderCacheValid = false; } std::vector>* SCH_FIELD::GetRenderCache( const wxString& forResolvedText, const VECTOR2I& forPosition, TEXT_ATTRIBUTES& aAttrs ) const { KIFONT::FONT* font = GetFont(); if( !font ) font = KIFONT::FONT::GetFont( GetDefaultFont(), IsBold(), IsItalic() ); if( font->IsOutline() ) { KIFONT::OUTLINE_FONT* outlineFont = static_cast( font ); if( m_renderCache.empty() || !m_renderCacheValid ) { m_renderCache.clear(); outlineFont->GetLinesAsGlyphs( &m_renderCache, forResolvedText, forPosition, aAttrs, GetFontMetrics() ); m_renderCachePos = forPosition; m_renderCacheValid = true; } if( m_renderCachePos != forPosition ) { VECTOR2I delta = forPosition - m_renderCachePos; for( std::unique_ptr& glyph : m_renderCache ) { if( glyph->IsOutline() ) static_cast( glyph.get() )->Move( delta ); else static_cast( glyph.get() )->Move( delta ); } m_renderCachePos = forPosition; } return &m_renderCache; } return nullptr; } void SCH_FIELD::Print( const SCH_RENDER_SETTINGS* aSettings, int aUnit, int aBodyStyle, const VECTOR2I& aOffset, bool aForceNoFill, bool aDimmed ) { wxString text; if( Schematic() ) text = GetShownText( &Schematic()->CurrentSheet(), true ); else text = GetShownText( true ); if( ( !IsVisible() && !IsForceVisible() ) || text.IsEmpty() ) return; wxDC* DC = aSettings->GetPrintDC(); COLOR4D color = aSettings->GetLayerColor( IsForceVisible() ? LAYER_HIDDEN : m_layer ); bool blackAndWhiteMode = GetGRForceBlackPenState(); int penWidth = GetEffectiveTextPenWidth( aSettings->GetDefaultPenWidth() ); COLOR4D bg = aSettings->GetBackgroundColor(); if( bg == COLOR4D::UNSPECIFIED || GetGRForceBlackPenState() ) bg = COLOR4D::WHITE; if( IsForceVisible() ) bg = aSettings->GetLayerColor( LAYER_HIDDEN ); if( !blackAndWhiteMode && GetTextColor() != COLOR4D::UNSPECIFIED ) color = GetTextColor(); if( aDimmed ) { color.Desaturate( ); color = color.Mix( bg, 0.5f ); } // Calculate the text orientation according to the symbol orientation. EDA_ANGLE orient = GetTextAngle(); VECTOR2I textpos = GetTextPos(); GR_TEXT_H_ALIGN_T hjustify = GetHorizJustify(); GR_TEXT_V_ALIGN_T vjustify = GetVertJustify(); KIFONT::FONT* font = GetFont(); if( !font ) font = KIFONT::FONT::GetFont( aSettings->GetDefaultFont(), IsBold(), IsItalic() ); if( m_parent && m_parent->Type() == LIB_SYMBOL_T ) { textpos = aSettings->TransformCoordinate( GetTextPos() ) + aOffset; } else if( m_parent && m_parent->Type() == SCH_SYMBOL_T ) { /* * Calculate the text justification, according to the symbol orientation/mirror. * This is a bit complicated due to cumulative calculations: * - numerous cases (mirrored or not, rotation) * - the GRText function will also recalculate H and V justifications according to the * text orientation. * - when symbol is mirrored, the text is not mirrored and justifications are complicated * to calculate so the easier way is to use no justifications (centered text) and use * GetBoundingBox to know the text coordinate considered as centered */ hjustify = GR_TEXT_H_ALIGN_CENTER; vjustify = GR_TEXT_V_ALIGN_CENTER; textpos = GetBoundingBox().Centre() + aOffset; if( aSettings->m_Transform.y1 ) // Rotate symbol 90 degrees. { if( orient == ANGLE_HORIZONTAL ) orient = ANGLE_VERTICAL; else orient = ANGLE_HORIZONTAL; } } else if( m_parent && m_parent->Type() == SCH_GLOBAL_LABEL_T ) { SCH_GLOBALLABEL* label = static_cast( GetParent() ); textpos += label->GetSchematicTextOffset( aSettings ); } GRPrintText( DC, textpos, color, text, orient, GetTextSize(), hjustify, vjustify, penWidth, IsItalic(), IsBold(), font, GetFontMetrics() ); } void SCH_FIELD::ImportValues( const SCH_FIELD& aSource ) { SetAttributes( aSource ); SetNameShown( aSource.IsNameShown() ); SetCanAutoplace( aSource.CanAutoplace() ); } void SCH_FIELD::SwapData( SCH_ITEM* aItem ) { wxCHECK_RET( aItem && aItem->Type() == SCH_FIELD_T, wxT( "Cannot swap with invalid item." ) ); SCH_ITEM::SwapFlags( aItem ); SCH_FIELD* item = static_cast( aItem ); std::swap( m_layer, item->m_layer ); std::swap( m_showName, item->m_showName ); std::swap( m_allowAutoPlace, item->m_allowAutoPlace ); std::swap( m_isNamedVariable, item->m_isNamedVariable ); SwapText( *item ); SwapAttributes( *item ); std::swap( m_lastResolvedColor, item->m_lastResolvedColor ); } COLOR4D SCH_FIELD::GetFieldColor() const { if( GetTextColor() != COLOR4D::UNSPECIFIED ) { m_lastResolvedColor = GetTextColor(); } else { SCH_LABEL_BASE* parentLabel = dynamic_cast( GetParent() ); if( parentLabel && !parentLabel->IsConnectivityDirty() ) m_lastResolvedColor = parentLabel->GetEffectiveNetClass()->GetSchematicColor(); else m_lastResolvedColor = GetTextColor(); } return m_lastResolvedColor; } void SCH_FIELD::ViewGetLayers( int aLayers[], int& aCount ) const { aCount = 2; aLayers[0] = GetDefaultLayer(); aLayers[1] = LAYER_SELECTION_SHADOWS; } SCH_LAYER_ID SCH_FIELD::GetDefaultLayer() const { if( m_parent && ( m_parent->Type() == LIB_SYMBOL_T || m_parent->Type() == SCH_SYMBOL_T ) ) { if( m_id == REFERENCE_FIELD ) return LAYER_REFERENCEPART; else if( m_id == VALUE_FIELD ) return LAYER_VALUEPART; } else if( m_parent && m_parent->Type() == SCH_SHEET_T ) { if( m_id == SHEETNAME ) return LAYER_SHEETNAME; else if( m_id == SHEETFILENAME ) return LAYER_SHEETFILENAME; else return LAYER_SHEETFIELDS; } else if( m_parent && m_parent->Type() == SCH_LABEL_T ) { if( GetCanonicalName() == wxT( "Netclass" ) ) return LAYER_NETCLASS_REFS; } return LAYER_FIELDS; } EDA_ANGLE SCH_FIELD::GetDrawRotation() const { // Calculate the text orientation according to the symbol orientation. EDA_ANGLE orient = GetTextAngle(); if( m_parent && m_parent->Type() == SCH_SYMBOL_T ) { SCH_SYMBOL* parentSymbol = static_cast( m_parent ); if( parentSymbol && parentSymbol->GetTransform().y1 ) // Rotate symbol 90 degrees. { if( orient.IsHorizontal() ) orient = ANGLE_VERTICAL; else orient = ANGLE_HORIZONTAL; } } return orient; } const BOX2I SCH_FIELD::GetBoundingBox() const { BOX2I bbox; if( m_parent && m_parent->Type() == LIB_SYMBOL_T ) { /* * Y coordinates for LIB_ITEMS are bottom to top, so we must invert the Y position * when calling GetTextBox() that works using top to bottom Y axis orientation. */ bbox = GetTextBox( -1, true ); bbox.RevertYAxis(); // We are using now a bottom to top Y axis. VECTOR2I orig = bbox.GetOrigin(); VECTOR2I end = bbox.GetEnd(); RotatePoint( orig, GetTextPos(), -GetTextAngle() ); RotatePoint( end, GetTextPos(), -GetTextAngle() ); bbox.SetOrigin( orig ); bbox.SetEnd( end ); // We are using now a top to bottom Y axis: bbox.RevertYAxis(); } else { bbox = GetTextBox(); // Calculate the bounding box position relative to the parent: VECTOR2I origin = GetParentPosition(); VECTOR2I pos = GetTextPos() - origin; VECTOR2I begin = bbox.GetOrigin() - origin; VECTOR2I end = bbox.GetEnd() - origin; RotatePoint( begin, pos, GetTextAngle() ); RotatePoint( end, pos, GetTextAngle() ); // Now, apply the symbol transform (mirror/rot) TRANSFORM transform; if( m_parent && m_parent->Type() == SCH_SYMBOL_T ) { SCH_SYMBOL* parentSymbol = static_cast( m_parent ); // Due to the Y axis direction, we must mirror the bounding box, relative to the // text position: MIRROR( begin.y, pos.y ); MIRROR( end.y, pos.y ); transform = parentSymbol->GetTransform(); } else { transform = TRANSFORM( 1, 0, 0, 1 ); // identity transform } bbox.SetOrigin( transform.TransformCoordinate( begin ) ); bbox.SetEnd( transform.TransformCoordinate( end ) ); bbox.Move( origin ); bbox.Normalize(); } return bbox; } bool SCH_FIELD::IsHorizJustifyFlipped() const { VECTOR2I render_center = GetBoundingBox().Centre(); VECTOR2I pos = GetPosition(); switch( GetHorizJustify() ) { case GR_TEXT_H_ALIGN_LEFT: if( GetDrawRotation().IsVertical() ) return render_center.y > pos.y; else return render_center.x < pos.x; case GR_TEXT_H_ALIGN_RIGHT: if( GetDrawRotation().IsVertical() ) return render_center.y < pos.y; else return render_center.x > pos.x; default: return false; } } GR_TEXT_H_ALIGN_T SCH_FIELD::GetEffectiveHorizJustify() const { switch( GetHorizJustify() ) { case GR_TEXT_H_ALIGN_LEFT: return IsHorizJustifyFlipped() ? GR_TEXT_H_ALIGN_RIGHT : GR_TEXT_H_ALIGN_LEFT; case GR_TEXT_H_ALIGN_RIGHT: return IsHorizJustifyFlipped() ? GR_TEXT_H_ALIGN_LEFT : GR_TEXT_H_ALIGN_RIGHT; default: return GR_TEXT_H_ALIGN_CENTER; } } bool SCH_FIELD::IsVertJustifyFlipped() const { VECTOR2I render_center = GetBoundingBox().Centre(); VECTOR2I pos = GetPosition(); switch( GetVertJustify() ) { case GR_TEXT_V_ALIGN_TOP: if( GetDrawRotation().IsVertical() ) return render_center.x < pos.x; else return render_center.y < pos.y; case GR_TEXT_V_ALIGN_BOTTOM: if( GetDrawRotation().IsVertical() ) return render_center.x > pos.x; else return render_center.y > pos.y; default: return false; } } GR_TEXT_V_ALIGN_T SCH_FIELD::GetEffectiveVertJustify() const { switch( GetVertJustify() ) { case GR_TEXT_V_ALIGN_TOP: return IsVertJustifyFlipped() ? GR_TEXT_V_ALIGN_BOTTOM : GR_TEXT_V_ALIGN_TOP; case GR_TEXT_V_ALIGN_BOTTOM: return IsVertJustifyFlipped() ? GR_TEXT_V_ALIGN_TOP : GR_TEXT_V_ALIGN_BOTTOM; default: return GR_TEXT_V_ALIGN_CENTER; } } bool SCH_FIELD::Matches( const EDA_SEARCH_DATA& aSearchData, void* aAuxData ) const { bool searchHiddenFields = false; bool searchAndReplace = false; bool replaceReferences = false; try { const SCH_SEARCH_DATA& schSearchData = dynamic_cast( aSearchData ); // downcast searchHiddenFields = schSearchData.searchAllFields; searchAndReplace = schSearchData.searchAndReplace; replaceReferences = schSearchData.replaceReferences; } catch( const std::bad_cast& ) { } wxString text = UnescapeString( GetText() ); if( !IsVisible() && !searchHiddenFields ) return false; if( m_parent && m_parent->Type() == SCH_SYMBOL_T && m_id == REFERENCE_FIELD ) { if( searchAndReplace && !replaceReferences ) return false; SCH_SYMBOL* parentSymbol = static_cast( m_parent ); wxASSERT( aAuxData ); // Take sheet path into account which effects the reference field and the unit for // symbols with multiple parts. if( aAuxData ) { SCH_SHEET_PATH* sheet = (SCH_SHEET_PATH*) aAuxData; text = parentSymbol->GetRef( sheet ); if( SCH_ITEM::Matches( text, aSearchData ) ) return true; if( parentSymbol->GetUnitCount() > 1 ) text << parentSymbol->SubReference( parentSymbol->GetUnitSelection( sheet ) ); } } return SCH_ITEM::Matches( text, aSearchData ); } void SCH_FIELD::OnScintillaCharAdded( SCINTILLA_TRICKS* aScintillaTricks, wxStyledTextEvent &aEvent ) const { SCH_ITEM* parent = dynamic_cast( GetParent() ); SCHEMATIC* schematic = parent ? parent->Schematic() : nullptr; if( !schematic ) return; wxStyledTextCtrl* scintilla = aScintillaTricks->Scintilla(); int key = aEvent.GetKey(); wxArrayString autocompleteTokens; int pos = scintilla->GetCurrentPos(); int start = scintilla->WordStartPosition( pos, true ); wxString partial; // Multi-line fields are not allowed. So remove '\n' if entered. if( key == '\n' ) { wxString text = scintilla->GetText(); int currpos = scintilla->GetCurrentPos(); text.Replace( wxS( "\n" ), wxS( "" ) ); scintilla->SetText( text ); scintilla->GotoPos( currpos-1 ); return; } auto textVarRef = [&]( int pt ) { return pt >= 2 && scintilla->GetCharAt( pt - 2 ) == '$' && scintilla->GetCharAt( pt - 1 ) == '{'; }; // Check for cross-reference if( start > 1 && scintilla->GetCharAt( start - 1 ) == ':' ) { int refStart = scintilla->WordStartPosition( start - 1, true ); if( textVarRef( refStart ) ) { partial = scintilla->GetRange( start, pos ); wxString ref = scintilla->GetRange( refStart, start - 1 ); if( ref == wxS( "OP" ) ) { // SPICE operating points use ':' syntax for ports if( SCH_SYMBOL* symbol = dynamic_cast( parent ) ) { NULL_REPORTER devnull; SCH_SHEET_PATH& sheet = schematic->CurrentSheet(); SIM_LIB_MGR mgr( &schematic->Prj() ); SIM_MODEL& model = mgr.CreateModel( &sheet, *symbol, devnull ).model; for( wxString pin : model.GetPinNames() ) { if( pin.StartsWith( '<' ) && pin.EndsWith( '>' ) ) autocompleteTokens.push_back( pin.Mid( 1, pin.Length() - 2 ) ); else autocompleteTokens.push_back( pin ); } } } else { SCH_SHEET_LIST sheets = schematic->GetSheets(); SCH_REFERENCE_LIST refs; SCH_SYMBOL* refSymbol = nullptr; sheets.GetSymbols( refs ); for( size_t jj = 0; jj < refs.GetCount(); jj++ ) { if( refs[ jj ].GetSymbol()->GetRef( &refs[ jj ].GetSheetPath(), true ) == ref ) { refSymbol = refs[ jj ].GetSymbol(); break; } } if( refSymbol ) refSymbol->GetContextualTextVars( &autocompleteTokens ); } } } else if( textVarRef( start ) ) { partial = scintilla->GetTextRange( start, pos ); SCH_SYMBOL* symbol = dynamic_cast( parent ); SCH_SHEET* sheet = dynamic_cast( parent ); SCH_LABEL_BASE* label = dynamic_cast( parent ); if( symbol ) { symbol->GetContextualTextVars( &autocompleteTokens ); if( schematic->CurrentSheet().Last() ) schematic->CurrentSheet().Last()->GetContextualTextVars( &autocompleteTokens ); } if( sheet ) sheet->GetContextualTextVars( &autocompleteTokens ); if( label ) label->GetContextualTextVars( &autocompleteTokens ); for( std::pair entry : schematic->Prj().GetTextVars() ) autocompleteTokens.push_back( entry.first ); } aScintillaTricks->DoAutocomplete( partial, autocompleteTokens ); scintilla->SetFocus(); } bool SCH_FIELD::IsReplaceable() const { if( m_parent && m_parent->Type() == SCH_SHEET_T ) { // See comments in SCH_FIELD::Replace(), below. if( m_id == SHEETFILENAME ) return false; } else if( m_parent && m_parent->Type() == SCH_GLOBAL_LABEL_T ) { if( m_id == 0 /* IntersheetRefs */ ) return false; } return true; } bool SCH_FIELD::Replace( const EDA_SEARCH_DATA& aSearchData, void* aAuxData ) { bool replaceReferences = false; try { const SCH_SEARCH_DATA& schSearchData = dynamic_cast( aSearchData ); replaceReferences = schSearchData.replaceReferences; } catch( const std::bad_cast& ) { } wxString text; bool isReplaced = false; if( m_parent && m_parent->Type() == SCH_SYMBOL_T ) { SCH_SYMBOL* parentSymbol = static_cast( m_parent ); switch( m_id ) { case REFERENCE_FIELD: wxCHECK_MSG( aAuxData, false, wxT( "Need sheetpath to replace in refdes." ) ); if( !replaceReferences ) return false; text = parentSymbol->GetRef( (SCH_SHEET_PATH*) aAuxData ); isReplaced = EDA_ITEM::Replace( aSearchData, text ); if( isReplaced ) parentSymbol->SetRef( (SCH_SHEET_PATH*) aAuxData, text ); break; case VALUE_FIELD: wxCHECK_MSG( aAuxData, false, wxT( "Need sheetpath to replace in value field." ) ); text = parentSymbol->GetField( VALUE_FIELD )->GetText(); isReplaced = EDA_ITEM::Replace( aSearchData, text ); if( isReplaced ) parentSymbol->SetValueFieldText( text ); break; case FOOTPRINT_FIELD: wxCHECK_MSG( aAuxData, false, wxT( "Need sheetpath to replace in footprint field." ) ); text = parentSymbol->GetField( FOOTPRINT_FIELD )->GetText(); isReplaced = EDA_ITEM::Replace( aSearchData, text ); if( isReplaced ) parentSymbol->SetFootprintFieldText( text ); break; default: isReplaced = EDA_TEXT::Replace( aSearchData ); } } else if( m_parent && m_parent->Type() == SCH_SHEET_T ) { isReplaced = EDA_TEXT::Replace( aSearchData ); if( m_id == SHEETFILENAME && isReplaced ) { // If we allowed this we'd have a bunch of work to do here, including warning // about it not being undoable, checking for recursive hierarchies, reloading // sheets, etc. See DIALOG_SHEET_PROPERTIES::TransferDataFromWindow(). } } else { isReplaced = EDA_TEXT::Replace( aSearchData ); } return isReplaced; } void SCH_FIELD::Rotate( const VECTOR2I& aCenter, bool aRotateCCW ) { VECTOR2I pt = GetPosition(); RotatePoint( pt, aCenter, aRotateCCW ? ANGLE_270 : ANGLE_90 ); SetPosition( pt ); SetTextAngle( GetTextAngle() != ANGLE_HORIZONTAL ? ANGLE_HORIZONTAL : ANGLE_VERTICAL ); } void SCH_FIELD::MirrorHorizontally( int aCenter ) { int x = GetTextPos().x; x -= aCenter; x *= -1; x += aCenter; SetTextX( x ); } void SCH_FIELD::MirrorVertically( int aCenter ) { int y = GetTextPos().y; y -= aCenter; y *= -1; y += aCenter; SetTextY( y ); } void SCH_FIELD::BeginEdit( const VECTOR2I& aPosition ) { SetTextPos( aPosition ); } void SCH_FIELD::CalcEdit( const VECTOR2I& aPosition ) { SetTextPos( aPosition ); } wxString SCH_FIELD::GetItemDescription( UNITS_PROVIDER* aUnitsProvider ) const { return wxString::Format( "%s '%s'", UnescapeString( GetName() ), KIUI::EllipsizeMenuText( GetText() ) ); } void SCH_FIELD::GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector& aList ) { wxString msg; aList.emplace_back( _( "Symbol Field" ), UnescapeString( GetName() ) ); // Don't use GetShownText() here; we want to show the user the variable references aList.emplace_back( _( "Text" ), KIUI::EllipsizeStatusText( aFrame, GetText() ) ); aList.emplace_back( _( "Visible" ), IsVisible() ? _( "Yes" ) : _( "No" ) ); aList.emplace_back( _( "Font" ), GetFont() ? GetFont()->GetName() : _( "Default" ) ); aList.emplace_back( _( "Style" ), GetTextStyleName() ); aList.emplace_back( _( "Text Size" ), aFrame->MessageTextFromValue( GetTextWidth() ) ); switch ( GetHorizJustify() ) { case GR_TEXT_H_ALIGN_LEFT: msg = _( "Left" ); break; case GR_TEXT_H_ALIGN_CENTER: msg = _( "Center" ); break; case GR_TEXT_H_ALIGN_RIGHT: msg = _( "Right" ); break; case GR_TEXT_H_ALIGN_INDETERMINATE: msg = INDETERMINATE_STATE; break; } aList.emplace_back( _( "H Justification" ), msg ); switch ( GetVertJustify() ) { case GR_TEXT_V_ALIGN_TOP: msg = _( "Top" ); break; case GR_TEXT_V_ALIGN_CENTER: msg = _( "Center" ); break; case GR_TEXT_V_ALIGN_BOTTOM: msg = _( "Bottom" ); break; case GR_TEXT_V_ALIGN_INDETERMINATE: msg = INDETERMINATE_STATE; break; } aList.emplace_back( _( "V Justification" ), msg ); } void SCH_FIELD::DoHypertextAction( EDA_DRAW_FRAME* aFrame ) const { constexpr int START_ID = 1; if( IsHypertext() ) { SCH_LABEL_BASE* label = static_cast( m_parent ); SCH_SHEET_PATH* sheet = &label->Schematic()->CurrentSheet(); wxMenu menu; wxString href; std::vector> pages; label->GetIntersheetRefs( sheet, &pages ); for( int i = 0; i < (int) pages.size(); ++i ) { menu.Append( i + START_ID, wxString::Format( _( "Go to Page %s (%s)" ), pages[i].first, pages[i].second ) ); } menu.AppendSeparator(); menu.Append( 999 + START_ID, _( "Back to Previous Selected Sheet" ) ); int sel = aFrame->GetPopupMenuSelectionFromUser( menu ) - START_ID; if( sel >= 0 && sel < (int) pages.size() ) href = wxT( "#" ) + pages[ sel ].first; else if( sel == 999 ) href = SCH_NAVIGATE_TOOL::g_BackLink; if( !href.IsEmpty() ) { SCH_NAVIGATE_TOOL* navTool = aFrame->GetToolManager()->GetTool(); navTool->HypertextCommand( href ); } } } void SCH_FIELD::SetName( const wxString& aName ) { m_name = aName; m_isNamedVariable = m_name.StartsWith( wxT( "${" ) ); if( m_isNamedVariable ) EDA_TEXT::SetText( aName ); } void SCH_FIELD::SetText( const wxString& aText ) { // Don't allow modification of text value when using named variables // as field name. if( m_isNamedVariable ) return; EDA_TEXT::SetText( aText ); } wxString SCH_FIELD::GetName( bool aUseDefaultName ) const { if( m_parent && ( m_parent->Type() == SCH_SYMBOL_T || m_parent->Type() == LIB_SYMBOL_T ) ) { if( IsMandatory() ) return GetCanonicalFieldName( m_id ); else if( m_name.IsEmpty() && aUseDefaultName ) return TEMPLATE_FIELDNAME::GetDefaultFieldName( m_id ); } else if( m_parent && m_parent->Type() == SCH_SHEET_T ) { if( IsMandatory() ) return SCH_SHEET::GetDefaultFieldName( m_id ); else if( m_name.IsEmpty() && aUseDefaultName ) return SCH_SHEET::GetDefaultFieldName( m_id ); } else if( m_parent && m_parent->IsType( { SCH_LABEL_LOCATE_ANY_T } ) ) { return SCH_LABEL_BASE::GetDefaultFieldName( m_name, aUseDefaultName ); } return m_name; } wxString SCH_FIELD::GetCanonicalName() const { if( m_parent && ( m_parent->Type() == SCH_SYMBOL_T || m_parent->Type() == LIB_SYMBOL_T ) ) { if( IsMandatory() ) return GetCanonicalFieldName( m_id ); } else if( m_parent && m_parent->Type() == SCH_SHEET_T ) { if( m_id == SHEETNAME ) return wxT( "Sheetname" ); else if( m_id == SHEETFILENAME ) return wxT( "Sheetfile" ); } else if( m_parent && m_parent->IsType( { SCH_LABEL_LOCATE_ANY_T } ) ) { // These should be stored in canonical format, but just in case: if( m_name == _( "Net Class" ) || m_name == wxT( "Net Class" ) ) { return wxT( "Netclass" ); } else if( m_name == _( "Sheet References" ) || m_name == wxT( "Sheet References" ) || m_name == wxT( "Intersheet References" ) ) { return wxT( "Intersheetrefs" ); } } return m_name; } BITMAPS SCH_FIELD::GetMenuImage() const { if( m_parent && ( m_parent->Type() == SCH_SYMBOL_T || m_parent->Type() == LIB_SYMBOL_T ) ) { switch( m_id ) { case REFERENCE_FIELD: return BITMAPS::edit_comp_ref; case VALUE_FIELD: return BITMAPS::edit_comp_value; case FOOTPRINT_FIELD: return BITMAPS::edit_comp_footprint; default: return BITMAPS::text; } } return BITMAPS::text; } bool SCH_FIELD::HitTest( const VECTOR2I& aPosition, int aAccuracy ) const { if( GetShownText( true ).IsEmpty() ) return false; BOX2I rect = GetBoundingBox(); // Text in symbol editor can have additional chars (ie: reference designators U? or U?A) if( m_parent && m_parent->Type() == LIB_SYMBOL_T ) { SCH_FIELD temp( *this ); temp.SetText( GetFullText() ); rect = temp.GetBoundingBox(); } rect.Inflate( aAccuracy ); if( m_parent && m_parent->Type() == SCH_GLOBAL_LABEL_T ) { SCH_GLOBALLABEL* label = static_cast( GetParent() ); rect.Offset( label->GetSchematicTextOffset( nullptr ) ); } return rect.Contains( aPosition ); } bool SCH_FIELD::HitTest( const BOX2I& aRect, bool aContained, int aAccuracy ) const { if( GetShownText( true ).IsEmpty() ) return false; if( m_flags & (STRUCT_DELETED | SKIP_STRUCT ) ) return false; BOX2I rect = aRect; rect.Inflate( aAccuracy ); if( GetParent() && GetParent()->Type() == SCH_GLOBAL_LABEL_T ) { SCH_GLOBALLABEL* label = static_cast( GetParent() ); rect.Offset( label->GetSchematicTextOffset( nullptr ) ); } if( aContained ) return rect.Contains( GetBoundingBox() ); return rect.Intersects( GetBoundingBox() ); } void SCH_FIELD::Plot( PLOTTER* aPlotter, bool aBackground, const SCH_PLOT_OPTS& aPlotOpts, int aUnit, int aBodyStyle, const VECTOR2I& aOffset, bool aDimmed ) { wxString text; if( Schematic() ) text = GetShownText( &Schematic()->CurrentSheet(), true ); else text = GetShownText( true ); if( ( !IsVisible() && !IsForceVisible() ) || text.IsEmpty() || aBackground ) return; SCH_RENDER_SETTINGS* renderSettings = getRenderSettings( aPlotter ); COLOR4D color = renderSettings->GetLayerColor( GetLayer() ); int penWidth = GetEffectiveTextPenWidth( renderSettings->GetDefaultPenWidth() ); COLOR4D bg = renderSettings->GetBackgroundColor();; if( bg == COLOR4D::UNSPECIFIED || !aPlotter->GetColorMode() ) bg = COLOR4D::WHITE; if( aPlotter->GetColorMode() && GetTextColor() != COLOR4D::UNSPECIFIED ) color = GetTextColor(); if( aDimmed ) { color.Desaturate( ); color = color.Mix( bg, 0.5f ); } penWidth = std::max( penWidth, renderSettings->GetMinPenWidth() ); // clamp the pen width to be sure the text is readable penWidth = std::min( penWidth, std::min( GetTextSize().x, GetTextSize().y ) / 4 ); if( !IsVisible() && !renderSettings->m_ShowHiddenFields ) return; // Calculate the text orientation, according to the symbol orientation/mirror EDA_ANGLE orient = GetTextAngle(); VECTOR2I textpos = GetTextPos(); GR_TEXT_H_ALIGN_T hjustify = GetHorizJustify(); GR_TEXT_V_ALIGN_T vjustify = GetVertJustify(); if( renderSettings->m_Transform.y1 ) // Rotate symbol 90 deg. { if( orient.IsHorizontal() ) orient = ANGLE_VERTICAL; else orient = ANGLE_HORIZONTAL; } if( m_parent && m_parent->Type() == SCH_SYMBOL_T ) { /* * Calculate the text justification, according to the symbol orientation/mirror. This is * a bit complicated due to cumulative calculations: * - numerous cases (mirrored or not, rotation) * - the plotter's Text() function will also recalculate H and V justifications according * to the text orientation * - when a symbol is mirrored the text is not, and justifications become a nightmare * * So the easier way is to use no justifications (centered text) and use GetBoundingBox * to know the text coordinate considered as centered. */ hjustify = GR_TEXT_H_ALIGN_CENTER; vjustify = GR_TEXT_V_ALIGN_CENTER; textpos = GetBoundingBox().Centre(); } else if( m_parent && m_parent->Type() == SCH_GLOBAL_LABEL_T ) { SCH_GLOBALLABEL* label = static_cast( m_parent ); textpos += label->GetSchematicTextOffset( renderSettings ); } KIFONT::FONT* font = GetFont(); if( !font ) font = KIFONT::FONT::GetFont( renderSettings->GetDefaultFont(), IsBold(), IsItalic() ); TEXT_ATTRIBUTES attrs = GetAttributes(); attrs.m_StrokeWidth = penWidth; attrs.m_Halign = hjustify; attrs.m_Valign = vjustify; attrs.m_Angle = orient; attrs.m_Multiline = false; aPlotter->PlotText( textpos, color, text, attrs, font, GetFontMetrics() ); if( IsHypertext() && Schematic() ) { SCH_LABEL_BASE* label = static_cast( m_parent ); std::vector> pages; std::vector pageHrefs; BOX2I bbox = GetBoundingBox(); wxCHECK( label, /* void */ ); label->GetIntersheetRefs( &Schematic()->CurrentSheet(), &pages ); for( const auto& [ pageNumber, sheetName ] : pages ) pageHrefs.push_back( wxT( "#" ) + pageNumber ); bbox.Offset( label->GetSchematicTextOffset( renderSettings ) ); aPlotter->HyperlinkMenu( bbox, pageHrefs ); } } void SCH_FIELD::SetPosition( const VECTOR2I& aPosition ) { // Actual positions are calculated by the rotation/mirror transform of the parent symbol // of the field. The inverse transform is used to calculate the position relative to the // parent symbol. if( m_parent && m_parent->Type() == SCH_SYMBOL_T ) { SCH_SYMBOL* parentSymbol = static_cast( m_parent ); VECTOR2I relPos = aPosition - parentSymbol->GetPosition(); relPos = parentSymbol->GetTransform().InverseTransform().TransformCoordinate( relPos ); SetTextPos( relPos + parentSymbol->GetPosition() ); return; } SetTextPos( aPosition ); } VECTOR2I SCH_FIELD::GetPosition() const { if( m_parent && m_parent->Type() == SCH_SYMBOL_T ) { SCH_SYMBOL* parentSymbol = static_cast( m_parent ); VECTOR2I relativePos = GetTextPos() - parentSymbol->GetPosition(); relativePos = parentSymbol->GetTransform().TransformCoordinate( relativePos ); return relativePos + parentSymbol->GetPosition(); } return GetTextPos(); } VECTOR2I SCH_FIELD::GetParentPosition() const { return m_parent ? m_parent->GetPosition() : VECTOR2I( 0, 0 ); } bool SCH_FIELD::IsMandatory() const { if( m_parent && m_parent->Type() == SCH_SHEET_T ) return m_id >= 0 && m_id < SHEET_MANDATORY_FIELDS; else return m_id >= 0 && m_id < MANDATORY_FIELDS; } bool SCH_FIELD::operator<( const SCH_ITEM& aItem ) const { if( Type() != aItem.Type() ) return Type() < aItem.Type(); auto field = static_cast( &aItem ); if( GetId() != field->GetId() ) return GetId() < field->GetId(); if( GetText() != field->GetText() ) return GetText() < field->GetText(); if( GetLibPosition().x != field->GetLibPosition().x ) return GetLibPosition().x < field->GetLibPosition().x; if( GetLibPosition().y != field->GetLibPosition().y ) return GetLibPosition().y < field->GetLibPosition().y; return GetName() < field->GetName(); } bool SCH_FIELD::operator==(const SCH_ITEM& aOther) const { if( Type() != aOther.Type() ) return false; const SCH_FIELD& field = static_cast( aOther ); return *this == field; } bool SCH_FIELD::operator==( const SCH_FIELD& aOther ) const { // Identical fields of different symbols are not equal. if( !GetParentSymbol() || !aOther.GetParentSymbol() || GetParentSymbol()->m_Uuid != aOther.GetParentSymbol()->m_Uuid ) { return false; } if( GetId() != aOther.GetId() ) return false; if( GetPosition() != aOther.GetPosition() ) return false; if( IsNamedVariable() != aOther.IsNamedVariable() ) return false; if( IsNameShown() != aOther.IsNameShown() ) return false; if( CanAutoplace() != aOther.CanAutoplace() ) return false; return EDA_TEXT::operator==( aOther ); } double SCH_FIELD::Similarity( const SCH_ITEM& aOther ) const { if( Type() != aOther.Type() ) return 0.0; if( m_Uuid == aOther.m_Uuid ) return 1.0; const SCH_FIELD& field = static_cast( aOther ); double similarity = 0.99; // The UUIDs are different, so we start with non-identity if( GetId() != field.GetId() ) { // We don't allow swapping of mandatory fields, so these cannot be the same item if( GetId() < MANDATORY_FIELDS || field.GetId() < MANDATORY_FIELDS ) return 0.0; else similarity *= 0.5; } similarity *= SimilarityBase( aOther ); similarity *= EDA_TEXT::Similarity( field ); if( GetPosition() != field.GetPosition() ) similarity *= 0.5; if( IsNamedVariable() != field.IsNamedVariable() ) similarity *= 0.5; if( IsNameShown() != field.IsNameShown() ) similarity *= 0.5; if( CanAutoplace() != field.CanAutoplace() ) similarity *= 0.5; return similarity; } int SCH_FIELD::compare( const SCH_ITEM& aOther, int aCompareFlags ) const { wxASSERT( aOther.Type() == SCH_FIELD_T ); int retv = SCH_ITEM::compare( aOther, aCompareFlags ); if( retv ) return retv; const SCH_FIELD* tmp = static_cast( &aOther ); // Equality test will vary depending whether or not the field is mandatory. Otherwise, // sorting is done by ordinal. if( aCompareFlags & SCH_ITEM::COMPARE_FLAGS::EQUALITY ) { // Mandatory fields have fixed ordinals and their names can vary due to translated field // names. Optional fields have fixed names and their ordinals can vary. if( IsMandatory() ) { if( m_id != tmp->m_id ) return m_id - tmp->m_id; } else { retv = m_name.Cmp( tmp->m_name ); if( retv ) return retv; } } else // assume we're sorting { if( m_id != tmp->m_id ) return m_id - tmp->m_id; } bool ignoreFieldText = false; if( m_id == REFERENCE_FIELD && !( aCompareFlags & SCH_ITEM::COMPARE_FLAGS::EQUALITY ) ) ignoreFieldText = true; if( m_id == VALUE_FIELD && ( aCompareFlags & SCH_ITEM::COMPARE_FLAGS::ERC ) ) ignoreFieldText = true; if( !ignoreFieldText ) { retv = GetText().CmpNoCase( tmp->GetText() ); if( retv != 0 ) return retv; } if( aCompareFlags & SCH_ITEM::COMPARE_FLAGS::EQUALITY ) { if( GetTextPos().x != tmp->GetTextPos().x ) return GetTextPos().x - tmp->GetTextPos().x; if( GetTextPos().y != tmp->GetTextPos().y ) return GetTextPos().y - tmp->GetTextPos().y; } if( GetTextWidth() != tmp->GetTextWidth() ) return GetTextWidth() - tmp->GetTextWidth(); if( GetTextHeight() != tmp->GetTextHeight() ) return GetTextHeight() - tmp->GetTextHeight(); return 0; } static struct SCH_FIELD_DESC { SCH_FIELD_DESC() { PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance(); REGISTER_TYPE( SCH_FIELD ); propMgr.AddTypeCast( new TYPE_CAST ); propMgr.AddTypeCast( new TYPE_CAST ); propMgr.InheritsAfter( TYPE_HASH( SCH_FIELD ), TYPE_HASH( SCH_ITEM ) ); propMgr.InheritsAfter( TYPE_HASH( SCH_FIELD ), TYPE_HASH( EDA_TEXT ) ); propMgr.AddProperty( new PROPERTY( _HKI( "Show Field Name" ), &SCH_FIELD::SetNameShown, &SCH_FIELD::IsNameShown ) ); propMgr.AddProperty( new PROPERTY( _HKI( "Allow Autoplacement" ), &SCH_FIELD::SetCanAutoplace, &SCH_FIELD::CanAutoplace ) ); propMgr.Mask( TYPE_HASH( SCH_FIELD ), TYPE_HASH( EDA_TEXT ), _HKI( "Hyperlink" ) ); propMgr.Mask( TYPE_HASH( SCH_FIELD ), TYPE_HASH( EDA_TEXT ), _HKI( "Thickness" ) ); propMgr.Mask( TYPE_HASH( SCH_FIELD ), TYPE_HASH( EDA_TEXT ), _HKI( "Mirrored" ) ); propMgr.Mask( TYPE_HASH( SCH_FIELD ), TYPE_HASH( EDA_TEXT ), _HKI( "Width" ) ); propMgr.Mask( TYPE_HASH( SCH_FIELD ), TYPE_HASH( EDA_TEXT ), _HKI( "Height" ) ); propMgr.AddProperty( new PROPERTY( _HKI( "Text Size" ), &SCH_FIELD::SetSchTextSize, &SCH_FIELD::GetSchTextSize, PROPERTY_DISPLAY::PT_SIZE ), _HKI( "Text Properties" ) ); propMgr.Mask( TYPE_HASH( SCH_FIELD ), TYPE_HASH( EDA_TEXT ), _HKI( "Orientation" ) ); auto isNotNamedVariable = []( INSPECTABLE* aItem ) -> bool { if( SCH_FIELD* field = dynamic_cast( aItem ) ) return !field->IsNamedVariable(); return true; }; propMgr.OverrideWriteability( TYPE_HASH( SCH_FIELD ), TYPE_HASH( EDA_TEXT ), _HKI( "Text" ), isNotNamedVariable ); } } _SCH_FIELD_DESC;