diff --git a/eeschema/CMakeLists.txt b/eeschema/CMakeLists.txt
index 7db0b4903a..d42bd4fe53 100644
--- a/eeschema/CMakeLists.txt
+++ b/eeschema/CMakeLists.txt
@@ -245,9 +245,11 @@ set( EESCHEMA_SRCS
netlist_exporters/netlist_exporter_xml.cpp
netlist_exporters/netlist_generator.cpp
+ sch_plugins/sch_lib_plugin_cache.cpp
sch_plugins/eagle/sch_eagle_plugin.cpp
sch_plugins/kicad/sch_sexpr_parser.cpp
sch_plugins/kicad/sch_sexpr_plugin.cpp
+ sch_plugins/legacy/sch_legacy_lib_plugin_cache.cpp
sch_plugins/legacy/sch_legacy_plugin.cpp
sch_plugins/legacy/sch_legacy_plugin_helpers.cpp
diff --git a/eeschema/sch_plugins/legacy/sch_legacy_lib_plugin_cache.cpp b/eeschema/sch_plugins/legacy/sch_legacy_lib_plugin_cache.cpp
new file mode 100644
index 0000000000..cdb86f77c9
--- /dev/null
+++ b/eeschema/sch_plugins/legacy/sch_legacy_lib_plugin_cache.cpp
@@ -0,0 +1,1797 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2022 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, see .
+ */
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "sch_legacy_lib_plugin_cache.h"
+#include "sch_legacy_plugin_helpers.h"
+
+
+#define LIB_VERSION_MAJOR 2 ///< Legacy symbol library major version.
+#define LIB_VERSION_MINOR 4 ///< Legacy symbol library minor version.
+
+#define LIB_VERSION( major, minor ) ( major * 100 + minor )
+
+/** Legacy symbol library (.lib) file header. */
+#define LIBFILE_IDENT "EESchema-LIBRARY Version"
+
+/** Legacy symbol library document (.dcm) file header. */
+#define DOCFILE_IDENT "EESchema-DOCLIB Version 2.0"
+
+/**
+ * Library versions 2.4 and lower use the old separate library (.lib) and
+ * document (.dcm) files. Symbol libraries after 2.4 merged the library
+ * and document files into a single library file. This macro checks if the
+ * library version supports the old format.
+ */
+#define USE_OLD_DOC_FILE_FORMAT( major, minor ) \
+ ( LIB_VERSION( major, minor ) <= LIB_VERSION( 2, 4 ) )
+
+
+SCH_LEGACY_PLUGIN_CACHE::SCH_LEGACY_PLUGIN_CACHE( const wxString& aFullPathAndFileName ) :
+ SCH_LIB_PLUGIN_CACHE( aFullPathAndFileName )
+{
+ m_versionMajor = -1;
+ m_versionMinor = -1;
+}
+
+
+void SCH_LEGACY_PLUGIN_CACHE::Load()
+{
+ if( !m_libFileName.FileExists() )
+ {
+ THROW_IO_ERROR( wxString::Format( _( "Library file '%s' not found." ),
+ m_libFileName.GetFullPath() ) );
+ }
+
+ wxCHECK_RET( m_libFileName.IsAbsolute(),
+ wxString::Format( "Cannot use relative file paths in legacy plugin to "
+ "open library '%s'.", m_libFileName.GetFullPath() ) );
+
+ wxLogTrace( traceSchLegacyPlugin, "Loading legacy symbol file '%s'",
+ m_libFileName.GetFullPath() );
+
+ FILE_LINE_READER reader( m_libFileName.GetFullPath() );
+
+ if( !reader.ReadLine() )
+ THROW_IO_ERROR( _( "Unexpected end of file." ) );
+
+ const char* line = reader.Line();
+
+ if( !strCompare( "EESchema-LIBRARY Version", line, &line ) )
+ {
+ // Old .sym files (which are libraries with only one symbol, used to store and reuse shapes)
+ // EESchema-LIB Version x.x SYMBOL. They are valid files.
+ if( !strCompare( "EESchema-LIB Version", line, &line ) )
+ SCH_PARSE_ERROR( "file is not a valid symbol or symbol library file", reader, line );
+ }
+
+ m_versionMajor = parseInt( reader, line, &line );
+
+ if( *line != '.' )
+ SCH_PARSE_ERROR( "invalid file version formatting in header", reader, line );
+
+ line++;
+
+ m_versionMinor = parseInt( reader, line, &line );
+
+ if( m_versionMajor < 1 || m_versionMinor < 0 || m_versionMinor > 99 )
+ SCH_PARSE_ERROR( "invalid file version in header", reader, line );
+
+ // Check if this is a symbol library which is the same as a symbol library but without
+ // any alias, documentation, footprint filters, etc.
+ if( strCompare( "SYMBOL", line, &line ) )
+ {
+ // Symbol files add date and time stamp info to the header.
+ m_libType = SCH_LIB_TYPE::LT_SYMBOL;
+
+ /// @todo Probably should check for a valid date and time stamp even though it's not used.
+ }
+ else
+ {
+ m_libType = SCH_LIB_TYPE::LT_EESCHEMA;
+ }
+
+ while( reader.ReadLine() )
+ {
+ line = reader.Line();
+
+ if( *line == '#' || isspace( *line ) ) // Skip comments and blank lines.
+ continue;
+
+ // Headers where only supported in older library file formats.
+ if( m_libType == SCH_LIB_TYPE::LT_EESCHEMA && strCompare( "$HEADER", line ) )
+ loadHeader( reader );
+
+ if( strCompare( "DEF", line ) )
+ {
+ // Read one DEF/ENDDEF symbol entry from library:
+ LIB_SYMBOL* symbol = LoadPart( reader, m_versionMajor, m_versionMinor, &m_symbols );
+
+ m_symbols[ symbol->GetName() ] = symbol;
+ }
+ }
+
+ SCH_LEGACY_PLUGIN_CACHE::IncrementModifyHash();
+
+ // Remember the file modification time of library file when the
+ // cache snapshot was made, so that in a networked environment we will
+ // reload the cache as needed.
+ m_fileModTime = GetLibModificationTime();
+
+ if( USE_OLD_DOC_FILE_FORMAT( m_versionMajor, m_versionMinor ) )
+ loadDocs();
+}
+
+
+void SCH_LEGACY_PLUGIN_CACHE::loadDocs()
+{
+ const char* line;
+ wxString text;
+ wxString aliasName;
+ wxFileName fn = m_libFileName;
+ LIB_SYMBOL* symbol = nullptr;;
+
+ fn.SetExt( LegacySymbolDocumentFileExtension );
+
+ // Not all libraries will have a document file.
+ if( !fn.FileExists() )
+ return;
+
+ if( !fn.IsFileReadable() )
+ {
+ THROW_IO_ERROR( wxString::Format( _( "Insufficient permissions to read library '%s'." ),
+ fn.GetFullPath() ) );
+ }
+
+ FILE_LINE_READER reader( fn.GetFullPath() );
+
+ line = reader.ReadLine();
+
+ if( !line )
+ THROW_IO_ERROR( _( "symbol document library file is empty" ) );
+
+ if( !strCompare( DOCFILE_IDENT, line, &line ) )
+ SCH_PARSE_ERROR( "invalid document library file version formatting in header",
+ reader, line );
+
+ while( reader.ReadLine() )
+ {
+ line = reader.Line();
+
+ if( *line == '#' ) // Comment line.
+ continue;
+
+ if( !strCompare( "$CMP", line, &line ) != 0 )
+ SCH_PARSE_ERROR( "$CMP command expected", reader, line );
+
+ aliasName = wxString::FromUTF8( line );
+ aliasName.Trim();
+
+ LIB_SYMBOL_MAP::iterator it = m_symbols.find( aliasName );
+
+ if( it == m_symbols.end() )
+ wxLogWarning( "Symbol '%s' not found in library:\n\n"
+ "'%s'\n\nat line %d offset %d", aliasName, fn.GetFullPath(),
+ reader.LineNumber(), (int) (line - reader.Line() ) );
+ else
+ symbol = it->second;
+
+ // Read the current alias associated doc.
+ // if the alias does not exist, just skip the description
+ // (Can happen if a .dcm is not synchronized with the corresponding .lib file)
+ while( reader.ReadLine() )
+ {
+ line = reader.Line();
+
+ if( !line )
+ SCH_PARSE_ERROR( "unexpected end of file", reader, line );
+
+ if( strCompare( "$ENDCMP", line, &line ) )
+ break;
+
+ text = FROM_UTF8( line + 2 );
+ // Remove spaces at eol, and eol chars:
+ text = text.Trim();
+
+ switch( line[0] )
+ {
+ case 'D':
+ if( symbol )
+ symbol->SetDescription( text );
+ break;
+
+ case 'K':
+ if( symbol )
+ symbol->SetKeyWords( text );
+ break;
+
+ case 'F':
+ if( symbol )
+ symbol->GetFieldById( DATASHEET_FIELD )->SetText( text );
+ break;
+
+ case 0:
+ case '\n':
+ case '\r':
+ case '#':
+ // Empty line or commment
+ break;
+
+ default:
+ SCH_PARSE_ERROR( "expected token in symbol definition", reader, line );
+ }
+ }
+ }
+}
+
+
+void SCH_LEGACY_PLUGIN_CACHE::loadHeader( FILE_LINE_READER& aReader )
+{
+ const char* line = aReader.Line();
+
+ wxASSERT( strCompare( "$HEADER", line, &line ) );
+
+ while( aReader.ReadLine() )
+ {
+ line = (char*) aReader;
+
+ // The time stamp saved in old library files is not used or saved in the latest
+ // library file version.
+ if( strCompare( "TimeStamp", line, &line ) )
+ continue;
+ else if( strCompare( "$ENDHEADER", line, &line ) )
+ return;
+ }
+
+ SCH_PARSE_ERROR( "$ENDHEADER not found", aReader, line );
+}
+
+
+LIB_SYMBOL* SCH_LEGACY_PLUGIN_CACHE::LoadPart( LINE_READER& aReader, int aMajorVersion,
+ int aMinorVersion, LIB_SYMBOL_MAP* aMap )
+{
+ const char* line = aReader.Line();
+
+ while( *line == '#' )
+ aReader.ReadLine();
+
+ if( !strCompare( "DEF", line, &line ) )
+ SCH_PARSE_ERROR( "invalid symbol definition", aReader, line );
+
+ long num;
+ size_t pos = 4; // "DEF" plus the first space.
+ wxString utf8Line = wxString::FromUTF8( line );
+ wxStringTokenizer tokens( utf8Line, " \r\n\t" );
+
+ if( tokens.CountTokens() < 8 )
+ SCH_PARSE_ERROR( "invalid symbol definition", aReader, line );
+
+ // Read DEF line:
+ std::unique_ptr symbol = std::make_unique( wxEmptyString );
+
+ wxString name, prefix, tmp;
+
+ name = tokens.GetNextToken();
+ name = EscapeString( name, CTX_LIBID );
+ pos += name.size() + 1;
+
+ prefix = tokens.GetNextToken();
+ pos += prefix.size() + 1;
+
+ tmp = tokens.GetNextToken();
+ pos += tmp.size() + 1; // NumOfPins, unused.
+
+ tmp = tokens.GetNextToken(); // Pin name offset.
+
+ if( !tmp.ToLong( &num ) )
+ THROW_PARSE_ERROR( "invalid pin offset", aReader.GetSource(), aReader.Line(),
+ aReader.LineNumber(), pos );
+
+ pos += tmp.size() + 1;
+ symbol->SetPinNameOffset( Mils2Iu( (int)num ) );
+
+ tmp = tokens.GetNextToken(); // Show pin numbers.
+
+ if( !( tmp == "Y" || tmp == "N") )
+ THROW_PARSE_ERROR( "expected Y or N", aReader.GetSource(), aReader.Line(),
+ aReader.LineNumber(), pos );
+
+ pos += tmp.size() + 1;
+ symbol->SetShowPinNumbers( ( tmp == "N" ) ? false : true );
+
+ tmp = tokens.GetNextToken(); // Show pin names.
+
+ if( !( tmp == "Y" || tmp == "N") )
+ THROW_PARSE_ERROR( "expected Y or N", aReader.GetSource(), aReader.Line(),
+ aReader.LineNumber(), pos );
+
+ pos += tmp.size() + 1;
+ symbol->SetShowPinNames( ( tmp == "N" ) ? false : true );
+
+ tmp = tokens.GetNextToken(); // Number of units.
+
+ if( !tmp.ToLong( &num ) )
+ THROW_PARSE_ERROR( "invalid unit count", aReader.GetSource(), aReader.Line(),
+ aReader.LineNumber(), pos );
+
+ pos += tmp.size() + 1;
+ symbol->SetUnitCount( (int)num );
+
+ // Ensure m_unitCount is >= 1. Could be read as 0 in old libraries.
+ if( symbol->GetUnitCount() < 1 )
+ symbol->SetUnitCount( 1 );
+
+ // Copy symbol name and prefix.
+
+ // The root alias is added to the alias list by SetName() which is called by SetText().
+ if( name.IsEmpty() )
+ {
+ symbol->SetName( "~" );
+ }
+ else if( name[0] != '~' )
+ {
+ symbol->SetName( name );
+ }
+ else
+ {
+ symbol->SetName( name.Right( name.Length() - 1 ) );
+ symbol->GetValueField().SetVisible( false );
+ }
+
+ // Don't set the library alias, this is determined by the symbol library table.
+ symbol->SetLibId( LIB_ID( wxEmptyString, symbol->GetName() ) );
+
+ LIB_FIELD& reference = symbol->GetReferenceField();
+
+ if( prefix == "~" )
+ {
+ reference.Empty();
+ reference.SetVisible( false );
+ }
+ else
+ {
+ reference.SetText( prefix );
+ }
+
+ // In version 2.2 and earlier, this parameter was a '0' which was just a place holder.
+ // The was no concept of interchangeable multiple unit symbols.
+ if( LIB_VERSION( aMajorVersion, aMinorVersion ) > 0
+ && LIB_VERSION( aMajorVersion, aMinorVersion ) <= LIB_VERSION( 2, 2 ) )
+ {
+ // Nothing needs to be set since the default setting for symbols with multiple
+ // units were never interchangeable. Just parse the 0 an move on.
+ tmp = tokens.GetNextToken();
+ pos += tmp.size() + 1;
+ }
+ else
+ {
+ tmp = tokens.GetNextToken();
+
+ if( tmp == "L" )
+ symbol->LockUnits( true );
+ else if( tmp == "F" || tmp == "0" )
+ symbol->LockUnits( false );
+ else
+ THROW_PARSE_ERROR( "expected L, F, or 0", aReader.GetSource(), aReader.Line(),
+ aReader.LineNumber(), pos );
+
+ pos += tmp.size() + 1;
+ }
+
+ // There is the optional power symbol flag.
+ if( tokens.HasMoreTokens() )
+ {
+ tmp = tokens.GetNextToken();
+
+ if( tmp == "P" )
+ symbol->SetPower();
+ else if( tmp == "N" )
+ symbol->SetNormal();
+ else
+ THROW_PARSE_ERROR( "expected P or N", aReader.GetSource(), aReader.Line(),
+ aReader.LineNumber(), pos );
+ }
+
+ line = aReader.ReadLine();
+
+ // Read lines until "ENDDEF" is found.
+ while( line )
+ {
+ if( *line == '#' ) // Comment
+ ;
+ else if( strCompare( "Ti", line, &line ) ) // Modification date is ignored.
+ continue;
+ else if( strCompare( "ALIAS", line, &line ) ) // Aliases
+ loadAliases( symbol, aReader, aMap );
+ else if( *line == 'F' ) // Fields
+ loadField( symbol, aReader );
+ else if( strCompare( "DRAW", line, &line ) ) // Drawing objects.
+ loadDrawEntries( symbol, aReader, aMajorVersion, aMinorVersion );
+ else if( strCompare( "$FPLIST", line, &line ) ) // Footprint filter list
+ loadFootprintFilters( symbol, aReader );
+ else if( strCompare( "ENDDEF", line, &line ) ) // End of symbol description
+ {
+ return symbol.release();
+ }
+
+ line = aReader.ReadLine();
+ }
+
+ SCH_PARSE_ERROR( "missing ENDDEF", aReader, line );
+}
+
+
+void SCH_LEGACY_PLUGIN_CACHE::loadAliases( std::unique_ptr& aSymbol,
+ LINE_READER& aReader,
+ LIB_SYMBOL_MAP* aMap )
+{
+ wxString newAliasName;
+ const char* line = aReader.Line();
+
+ wxCHECK_RET( strCompare( "ALIAS", line, &line ), "Invalid ALIAS section" );
+
+ wxString utf8Line = wxString::FromUTF8( line );
+ wxStringTokenizer tokens( utf8Line, " \r\n\t" );
+
+ // Parse the ALIAS list.
+ while( tokens.HasMoreTokens() )
+ {
+ newAliasName = tokens.GetNextToken();
+
+ if( aMap )
+ {
+ LIB_SYMBOL* newSymbol = new LIB_SYMBOL( newAliasName );
+
+ // Inherit the parent mandatory field attributes.
+ for( int id = 0; id < MANDATORY_FIELDS; ++id )
+ {
+ LIB_FIELD* field = newSymbol->GetFieldById( id );
+
+ // the MANDATORY_FIELDS are exactly that in RAM.
+ wxASSERT( field );
+
+ LIB_FIELD* parentField = aSymbol->GetFieldById( id );
+
+ wxASSERT( parentField );
+
+ *field = *parentField;
+
+ if( id == VALUE_FIELD )
+ field->SetText( newAliasName );
+
+ field->SetParent( newSymbol );
+ }
+
+ newSymbol->SetParent( aSymbol.get() );
+
+ // This will prevent duplicate aliases.
+ (*aMap)[ newSymbol->GetName() ] = newSymbol;
+ }
+ }
+}
+
+
+void SCH_LEGACY_PLUGIN_CACHE::loadField( std::unique_ptr& aSymbol,
+ LINE_READER& aReader )
+{
+ const char* line = aReader.Line();
+
+ wxCHECK_RET( *line == 'F', "Invalid field line" );
+
+ wxString text;
+ int id;
+
+ if( sscanf( line + 1, "%d", &id ) != 1 || id < 0 )
+ SCH_PARSE_ERROR( "invalid field ID", aReader, line + 1 );
+
+ LIB_FIELD* field;
+
+ if( id >= 0 && id < MANDATORY_FIELDS )
+ {
+ field = aSymbol->GetFieldById( id );
+
+ // this will fire only if somebody broke a constructor or editor.
+ // MANDATORY_FIELDS are always present in ram resident symbols, no
+ // exceptions, and they always have their names set, even fixed fields.
+ wxASSERT( field );
+ }
+ else
+ {
+ field = new LIB_FIELD( aSymbol.get(), id );
+ aSymbol->AddDrawItem( field, false );
+ }
+
+ // Skip to the first double quote.
+ while( *line != '"' && *line != 0 )
+ line++;
+
+ if( *line == 0 )
+ SCH_PARSE_ERROR( _( "unexpected end of line" ), aReader, line );
+
+ parseQuotedString( text, aReader, line, &line, true );
+
+ // The value field needs to be "special" escaped. The other fields are
+ // escaped normally and don't need special handling
+ if( id == VALUE_FIELD )
+ text = EscapeString( text, CTX_QUOTED_STR );
+
+ // Doctor the *.lib file field which has a "~" in blank fields. New saves will
+ // not save like this.
+ if( text.size() == 1 && text[0] == '~' )
+ field->SetText( wxEmptyString );
+ else
+ field->SetText( ConvertToNewOverbarNotation( text ) );
+
+ VECTOR2I pos;
+
+ pos.x = Mils2Iu( parseInt( aReader, line, &line ) );
+ pos.y = Mils2Iu( parseInt( aReader, line, &line ) );
+ field->SetPosition( pos );
+
+ wxSize textSize;
+
+ textSize.x = textSize.y = Mils2Iu( parseInt( aReader, line, &line ) );
+ field->SetTextSize( textSize );
+
+ char textOrient = parseChar( aReader, line, &line );
+
+ if( textOrient == 'H' )
+ field->SetTextAngle( ANGLE_HORIZONTAL );
+ else if( textOrient == 'V' )
+ field->SetTextAngle( ANGLE_VERTICAL );
+ else
+ SCH_PARSE_ERROR( "invalid field text orientation parameter", aReader, line );
+
+ char textVisible = parseChar( aReader, line, &line );
+
+ if( textVisible == 'V' )
+ field->SetVisible( true );
+ else if ( textVisible == 'I' )
+ field->SetVisible( false );
+ else
+ SCH_PARSE_ERROR( "invalid field text visibility parameter", aReader, line );
+
+ // It may be technically correct to use the library version to determine if the field text
+ // attributes are present. If anyone knows if that is valid and what version that would be,
+ // please change this to test the library version rather than an EOL or the quoted string
+ // of the field name.
+ if( *line != 0 && *line != '"' )
+ {
+ char textHJustify = parseChar( aReader, line, &line );
+
+ if( textHJustify == 'C' )
+ field->SetHorizJustify( GR_TEXT_H_ALIGN_CENTER );
+ else if( textHJustify == 'L' )
+ field->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
+ else if( textHJustify == 'R' )
+ field->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT );
+ else
+ SCH_PARSE_ERROR( "invalid field text horizontal justification", aReader, line );
+
+ wxString attributes;
+
+ parseUnquotedString( attributes, aReader, line, &line );
+
+ size_t attrSize = attributes.size();
+
+ if( !(attrSize == 3 || attrSize == 1 ) )
+ SCH_PARSE_ERROR( "invalid field text attributes size", aReader, line );
+
+ switch( (wxChar) attributes[0] )
+ {
+ case 'C': field->SetVertJustify( GR_TEXT_V_ALIGN_CENTER ); break;
+ case 'B': field->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM ); break;
+ case 'T': field->SetVertJustify( GR_TEXT_V_ALIGN_TOP ); break;
+ default: SCH_PARSE_ERROR( "invalid field text vertical justification", aReader, line );
+ }
+
+ if( attrSize == 3 )
+ {
+ wxChar attr_1 = attributes[1];
+ wxChar attr_2 = attributes[2];
+
+ if( attr_1 == 'I' ) // Italic
+ field->SetItalic( true );
+ else if( attr_1 != 'N' ) // No italics is default, check for error.
+ SCH_PARSE_ERROR( "invalid field text italic parameter", aReader, line );
+
+ if ( attr_2 == 'B' ) // Bold
+ field->SetBold( true );
+ else if( attr_2 != 'N' ) // No bold is default, check for error.
+ SCH_PARSE_ERROR( "invalid field text bold parameter", aReader, line );
+ }
+ }
+
+ // Fields in RAM must always have names.
+ if( id >= 0 && id < MANDATORY_FIELDS )
+ {
+ // Fields in RAM must always have names, because we are trying to get
+ // less dependent on field ids and more dependent on names.
+ // Plus assumptions are made in the field editors.
+ field->m_name = TEMPLATE_FIELDNAME::GetDefaultFieldName( id );
+
+ // Ensure the VALUE field = the symbol name (can be not the case
+ // with malformed libraries: edited by hand, or converted from other tools)
+ if( id == VALUE_FIELD )
+ field->SetText( aSymbol->GetName() );
+ }
+ else
+ {
+ parseQuotedString( field->m_name, aReader, line, &line, true ); // Optional.
+ }
+}
+
+
+void SCH_LEGACY_PLUGIN_CACHE::loadDrawEntries( std::unique_ptr& aSymbol,
+ LINE_READER& aReader,
+ int aMajorVersion,
+ int aMinorVersion )
+{
+ const char* line = aReader.Line();
+
+ wxCHECK_RET( strCompare( "DRAW", line, &line ), "Invalid DRAW section" );
+
+ line = aReader.ReadLine();
+
+ while( line )
+ {
+ if( strCompare( "ENDDRAW", line, &line ) )
+ {
+ aSymbol->GetDrawItems().sort();
+ return;
+ }
+
+ switch( line[0] )
+ {
+ case 'A': // Arc
+ aSymbol->AddDrawItem( loadArc( aSymbol, aReader ), false );
+ break;
+
+ case 'C': // Circle
+ aSymbol->AddDrawItem( loadCircle( aSymbol, aReader ), false );
+ break;
+
+ case 'T': // Text
+ aSymbol->AddDrawItem( loadText( aSymbol, aReader, aMajorVersion,
+ aMinorVersion ), false );
+ break;
+
+ case 'S': // Square
+ aSymbol->AddDrawItem( loadRect( aSymbol, aReader ), false );
+ break;
+
+ case 'X': // Pin Description
+ aSymbol->AddDrawItem( loadPin( aSymbol, aReader ), false );
+ break;
+
+ case 'P': // Polyline
+ aSymbol->AddDrawItem( loadPolyLine( aSymbol, aReader ), false );
+ break;
+
+ case 'B': // Bezier Curves
+ aSymbol->AddDrawItem( loadBezier( aSymbol, aReader ), false );
+ break;
+
+ case '#': // Comment
+ case '\n': // Empty line
+ case '\r':
+ case 0:
+ break;
+
+ default:
+ SCH_PARSE_ERROR( "undefined DRAW entry", aReader, line );
+ }
+
+ line = aReader.ReadLine();
+ }
+
+ SCH_PARSE_ERROR( "File ended prematurely loading symbol draw element.", aReader, line );
+}
+
+
+FILL_T SCH_LEGACY_PLUGIN_CACHE::parseFillMode( LINE_READER& aReader, const char* aLine,
+ const char** aOutput )
+{
+ switch ( parseChar( aReader, aLine, aOutput ) )
+ {
+ case 'F': return FILL_T::FILLED_SHAPE;
+ case 'f': return FILL_T::FILLED_WITH_BG_BODYCOLOR;
+ case 'N': return FILL_T::NO_FILL;
+ default: SCH_PARSE_ERROR( "invalid fill type, expected f, F, or N", aReader, aLine );
+ }
+
+ // This will never be reached but quiets the compiler warnings
+ return FILL_T::NO_FILL;
+}
+
+
+LIB_SHAPE* SCH_LEGACY_PLUGIN_CACHE::loadArc( std::unique_ptr& aSymbol,
+ LINE_READER& aReader )
+{
+ const char* line = aReader.Line();
+
+ wxCHECK_MSG( strCompare( "A", line, &line ), nullptr, "Invalid arc definition" );
+
+ LIB_SHAPE* arc = new LIB_SHAPE( aSymbol.get(), SHAPE_T::ARC );
+
+ VECTOR2I center;
+
+ center.x = Mils2Iu( parseInt( aReader, line, &line ) );
+ center.y = Mils2Iu( parseInt( aReader, line, &line ) );
+
+ arc->SetPosition( center );
+
+ int radius = Mils2Iu( parseInt( aReader, line, &line ) );
+ EDA_ANGLE angle1( parseInt( aReader, line, &line ), TENTHS_OF_A_DEGREE_T );
+ EDA_ANGLE angle2( parseInt( aReader, line, &line ), TENTHS_OF_A_DEGREE_T );
+
+ angle1.Normalize();
+ angle2.Normalize();
+
+ arc->SetUnit( parseInt( aReader, line, &line ) );
+ arc->SetConvert( parseInt( aReader, line, &line ) );
+ arc->SetStroke( STROKE_PARAMS( Mils2Iu( parseInt( aReader, line, &line ) ),
+ PLOT_DASH_TYPE::SOLID ) );
+
+ // Old libraries (version <= 2.2) do not have always this FILL MODE param
+ // when fill mode is no fill (default mode).
+ if( *line != 0 )
+ arc->SetFillMode( parseFillMode( aReader, line, &line ) );
+
+ // Actual Coordinates of arc ends are read from file
+ if( *line != 0 )
+ {
+ VECTOR2I arcStart, arcEnd;
+
+ arcStart.x = Mils2Iu( parseInt( aReader, line, &line ) );
+ arcStart.y = Mils2Iu( parseInt( aReader, line, &line ) );
+ arcEnd.x = Mils2Iu( parseInt( aReader, line, &line ) );
+ arcEnd.y = Mils2Iu( parseInt( aReader, line, &line ) );
+
+ arc->SetStart( arcStart );
+ arc->SetEnd( arcEnd );
+ }
+ else
+ {
+ // Actual Coordinates of arc ends are not read from file
+ // (old library), calculate them
+ VECTOR2I arcStart( radius, 0 );
+ VECTOR2I arcEnd( radius, 0 );
+
+ RotatePoint( &arcStart.x, &arcStart.y, -angle1 );
+ arcStart += arc->GetCenter();
+ arc->SetStart( arcStart );
+ RotatePoint( &arcEnd.x, &arcEnd.y, -angle2 );
+ arcEnd += arc->GetCenter();
+ arc->SetEnd( arcEnd );
+ }
+
+ /**
+ * This accounts for an oddity in the old library format, where the symbol is overdefined.
+ * The previous draw (based on wxwidgets) used start point and end point and always drew
+ * counter-clockwise. The new GAL draw takes center, radius and start/end angles. All of
+ * these points were stored in the file, so we need to mimic the swapping of start/end
+ * points rather than using the stored angles in order to properly map edge cases.
+ */
+ if( !TRANSFORM().MapAngles( &angle1, &angle2 ) )
+ {
+ VECTOR2I temp = arc->GetStart();
+ arc->SetStart( arc->GetEnd() );
+ arc->SetEnd( temp );
+ }
+
+ return arc;
+}
+
+
+LIB_SHAPE* SCH_LEGACY_PLUGIN_CACHE::loadCircle( std::unique_ptr& aSymbol,
+ LINE_READER& aReader )
+{
+ const char* line = aReader.Line();
+
+ wxCHECK_MSG( strCompare( "C", line, &line ), nullptr, "Invalid circle definition" );
+
+ LIB_SHAPE* circle = new LIB_SHAPE( aSymbol.get(), SHAPE_T::CIRCLE );
+
+ VECTOR2I center;
+
+ center.x = Mils2Iu( parseInt( aReader, line, &line ) );
+ center.y = Mils2Iu( parseInt( aReader, line, &line ) );
+
+ int radius = Mils2Iu( parseInt( aReader, line, &line ) );
+
+ circle->SetStart( center );
+ circle->SetEnd( VECTOR2I( center.x + radius, center.y ) );
+ circle->SetUnit( parseInt( aReader, line, &line ) );
+ circle->SetConvert( parseInt( aReader, line, &line ) );
+ circle->SetStroke( STROKE_PARAMS( Mils2Iu( parseInt( aReader, line, &line ) ),
+ PLOT_DASH_TYPE::SOLID ) );
+
+ if( *line != 0 )
+ circle->SetFillMode( parseFillMode( aReader, line, &line ) );
+
+ return circle;
+}
+
+
+LIB_TEXT* SCH_LEGACY_PLUGIN_CACHE::loadText( std::unique_ptr& aSymbol,
+ LINE_READER& aReader,
+ int aMajorVersion,
+ int aMinorVersion )
+{
+ const char* line = aReader.Line();
+
+ wxCHECK_MSG( strCompare( "T", line, &line ), nullptr, "Invalid LIB_TEXT definition" );
+
+ LIB_TEXT* text = new LIB_TEXT( aSymbol.get() );
+ double angleInTenths = parseInt( aReader, line, &line );
+
+ text->SetTextAngle( EDA_ANGLE( angleInTenths, TENTHS_OF_A_DEGREE_T ) );
+
+ VECTOR2I center;
+
+ center.x = Mils2Iu( parseInt( aReader, line, &line ) );
+ center.y = Mils2Iu( parseInt( aReader, line, &line ) );
+ text->SetPosition( center );
+
+ wxSize size;
+
+ size.x = size.y = Mils2Iu( parseInt( aReader, line, &line ) );
+ text->SetTextSize( size );
+ text->SetVisible( !parseInt( aReader, line, &line ) );
+ text->SetUnit( parseInt( aReader, line, &line ) );
+ text->SetConvert( parseInt( aReader, line, &line ) );
+
+ wxString str;
+
+ // If quoted string loading fails, load as not quoted string.
+ if( *line == '"' )
+ {
+ parseQuotedString( str, aReader, line, &line );
+
+ str = ConvertToNewOverbarNotation( str );
+ }
+ else
+ {
+ parseUnquotedString( str, aReader, line, &line );
+
+ // In old libs, "spaces" are replaced by '~' in unquoted strings:
+ str.Replace( "~", " " );
+ }
+
+ if( !str.IsEmpty() )
+ {
+ // convert two apostrophes back to double quote
+ str.Replace( "''", "\"" );
+ }
+
+ text->SetText( str );
+
+ // Here things are murky and not well defined. At some point it appears the format
+ // was changed to add text properties. However rather than add the token to the end of
+ // the text definition, it was added after the string and no mention if the file
+ // verion was bumped or not so this code make break on very old symbol libraries.
+ //
+ // Update: apparently even in the latest version this can be different so added a test
+ // for end of line before checking for the text properties.
+ if( LIB_VERSION( aMajorVersion, aMinorVersion ) > 0
+ && LIB_VERSION( aMajorVersion, aMinorVersion ) > LIB_VERSION( 2, 0 ) && !is_eol( *line ) )
+ {
+ if( strCompare( "Italic", line, &line ) )
+ text->SetItalic( true );
+ else if( !strCompare( "Normal", line, &line ) )
+ SCH_PARSE_ERROR( "invalid text stype, expected 'Normal' or 'Italic'", aReader, line );
+
+ if( parseInt( aReader, line, &line ) > 0 )
+ text->SetBold( true );
+
+ // Some old libaries version > 2.0 do not have these options for text justification:
+ if( !is_eol( *line ) )
+ {
+ switch( parseChar( aReader, line, &line ) )
+ {
+ case 'L': text->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT ); break;
+ case 'C': text->SetHorizJustify( GR_TEXT_H_ALIGN_CENTER ); break;
+ case 'R': text->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT ); break;
+ default: SCH_PARSE_ERROR( "invalid horizontal text justication; expected L, C, or R",
+ aReader, line );
+ }
+
+ switch( parseChar( aReader, line, &line ) )
+ {
+ case 'T': text->SetVertJustify( GR_TEXT_V_ALIGN_TOP ); break;
+ case 'C': text->SetVertJustify( GR_TEXT_V_ALIGN_CENTER ); break;
+ case 'B': text->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM ); break;
+ default: SCH_PARSE_ERROR( "invalid vertical text justication; expected T, C, or B",
+ aReader, line );
+ }
+ }
+ }
+
+ return text;
+}
+
+
+LIB_SHAPE* SCH_LEGACY_PLUGIN_CACHE::loadRect( std::unique_ptr& aSymbol,
+ LINE_READER& aReader )
+{
+ const char* line = aReader.Line();
+
+ wxCHECK_MSG( strCompare( "S", line, &line ), nullptr, "Invalid rectangle definition" );
+
+ LIB_SHAPE* rectangle = new LIB_SHAPE( aSymbol.get(), SHAPE_T::RECT );
+
+ VECTOR2I pos;
+
+ pos.x = Mils2Iu( parseInt( aReader, line, &line ) );
+ pos.y = Mils2Iu( parseInt( aReader, line, &line ) );
+ rectangle->SetPosition( pos );
+
+ VECTOR2I end;
+
+ end.x = Mils2Iu( parseInt( aReader, line, &line ) );
+ end.y = Mils2Iu( parseInt( aReader, line, &line ) );
+ rectangle->SetEnd( end );
+
+ rectangle->SetUnit( parseInt( aReader, line, &line ) );
+ rectangle->SetConvert( parseInt( aReader, line, &line ) );
+ rectangle->SetStroke( STROKE_PARAMS( Mils2Iu( parseInt( aReader, line, &line ) ),
+ PLOT_DASH_TYPE::SOLID ) );
+
+ if( *line != 0 )
+ rectangle->SetFillMode( parseFillMode( aReader, line, &line ) );
+
+ return rectangle;
+}
+
+
+LIB_PIN* SCH_LEGACY_PLUGIN_CACHE::loadPin( std::unique_ptr& aSymbol,
+ LINE_READER& aReader )
+{
+ const char* line = aReader.Line();
+
+ wxCHECK_MSG( strCompare( "X", line, &line ), nullptr, "Invalid LIB_PIN definition" );
+
+ wxString name;
+ wxString number;
+
+ size_t pos = 2; // "X" plus ' ' space character.
+ wxString tmp;
+ wxString utf8Line = wxString::FromUTF8( line );
+ wxStringTokenizer tokens( utf8Line, " \r\n\t" );
+
+ if( tokens.CountTokens() < 11 )
+ SCH_PARSE_ERROR( "invalid pin definition", aReader, line );
+
+ tmp = tokens.GetNextToken();
+ name = tmp;
+ pos += tmp.size() + 1;
+
+ tmp = tokens.GetNextToken();
+ number = tmp ;
+ pos += tmp.size() + 1;
+
+ long num;
+ VECTOR2I position;
+
+ tmp = tokens.GetNextToken();
+
+ if( !tmp.ToLong( &num ) )
+ THROW_PARSE_ERROR( "invalid pin X coordinate", aReader.GetSource(), aReader.Line(),
+ aReader.LineNumber(), pos );
+
+ pos += tmp.size() + 1;
+ position.x = Mils2Iu( (int) num );
+
+ tmp = tokens.GetNextToken();
+
+ if( !tmp.ToLong( &num ) )
+ THROW_PARSE_ERROR( "invalid pin Y coordinate", aReader.GetSource(), aReader.Line(),
+ aReader.LineNumber(), pos );
+
+ pos += tmp.size() + 1;
+ position.y = Mils2Iu( (int) num );
+
+ tmp = tokens.GetNextToken();
+
+ if( !tmp.ToLong( &num ) )
+ THROW_PARSE_ERROR( "invalid pin length", aReader.GetSource(), aReader.Line(),
+ aReader.LineNumber(), pos );
+
+ pos += tmp.size() + 1;
+ int length = Mils2Iu( (int) num );
+
+
+ tmp = tokens.GetNextToken();
+
+ if( tmp.size() > 1 )
+ THROW_PARSE_ERROR( "invalid pin orientation", aReader.GetSource(), aReader.Line(),
+ aReader.LineNumber(), pos );
+
+ pos += tmp.size() + 1;
+ int orientation = tmp[0];
+
+ tmp = tokens.GetNextToken();
+
+ if( !tmp.ToLong( &num ) )
+ THROW_PARSE_ERROR( "invalid pin number text size", aReader.GetSource(), aReader.Line(),
+ aReader.LineNumber(), pos );
+
+ pos += tmp.size() + 1;
+ int numberTextSize = Mils2Iu( (int) num );
+
+ tmp = tokens.GetNextToken();
+
+ if( !tmp.ToLong( &num ) )
+ THROW_PARSE_ERROR( "invalid pin name text size", aReader.GetSource(), aReader.Line(),
+ aReader.LineNumber(), pos );
+
+ pos += tmp.size() + 1;
+ int nameTextSize = Mils2Iu( (int) num );
+
+ tmp = tokens.GetNextToken();
+
+ if( !tmp.ToLong( &num ) )
+ THROW_PARSE_ERROR( "invalid pin unit", aReader.GetSource(), aReader.Line(),
+ aReader.LineNumber(), pos );
+
+ pos += tmp.size() + 1;
+ int unit = (int) num;
+
+ tmp = tokens.GetNextToken();
+
+ if( !tmp.ToLong( &num ) )
+ THROW_PARSE_ERROR( "invalid pin alternate body type", aReader.GetSource(), aReader.Line(),
+ aReader.LineNumber(), pos );
+
+ pos += tmp.size() + 1;
+ int convert = (int) num;
+
+ tmp = tokens.GetNextToken();
+
+ if( tmp.size() != 1 )
+ THROW_PARSE_ERROR( "invalid pin type", aReader.GetSource(), aReader.Line(),
+ aReader.LineNumber(), pos );
+
+ pos += tmp.size() + 1;
+ char type = tmp[0];
+ ELECTRICAL_PINTYPE pinType;
+
+ switch( type )
+ {
+ case 'I': pinType = ELECTRICAL_PINTYPE::PT_INPUT; break;
+ case 'O': pinType = ELECTRICAL_PINTYPE::PT_OUTPUT; break;
+ case 'B': pinType = ELECTRICAL_PINTYPE::PT_BIDI; break;
+ case 'T': pinType = ELECTRICAL_PINTYPE::PT_TRISTATE; break;
+ case 'P': pinType = ELECTRICAL_PINTYPE::PT_PASSIVE; break;
+ case 'U': pinType = ELECTRICAL_PINTYPE::PT_UNSPECIFIED; break;
+ case 'W': pinType = ELECTRICAL_PINTYPE::PT_POWER_IN; break;
+ case 'w': pinType = ELECTRICAL_PINTYPE::PT_POWER_OUT; break;
+ case 'C': pinType = ELECTRICAL_PINTYPE::PT_OPENCOLLECTOR; break;
+ case 'E': pinType = ELECTRICAL_PINTYPE::PT_OPENEMITTER; break;
+ case 'N': pinType = ELECTRICAL_PINTYPE::PT_NC; break;
+ default:
+ THROW_PARSE_ERROR( "unknown pin type", aReader.GetSource(), aReader.Line(),
+ aReader.LineNumber(), pos );
+ }
+
+
+ LIB_PIN* pin = new LIB_PIN( aSymbol.get(),
+ ConvertToNewOverbarNotation( name ),
+ ConvertToNewOverbarNotation( number ),
+ orientation,
+ pinType,
+ length,
+ nameTextSize,
+ numberTextSize,
+ convert,
+ position,
+ unit );
+
+ // Optional
+ if( tokens.HasMoreTokens() ) /* Special Symbol defined */
+ {
+ tmp = tokens.GetNextToken();
+
+ enum
+ {
+ INVERTED = 1 << 0,
+ CLOCK = 1 << 1,
+ LOWLEVEL_IN = 1 << 2,
+ LOWLEVEL_OUT = 1 << 3,
+ FALLING_EDGE = 1 << 4,
+ NONLOGIC = 1 << 5
+ };
+
+ int flags = 0;
+
+ for( int j = tmp.size(); j > 0; )
+ {
+ switch( tmp[--j].GetValue() )
+ {
+ case '~': break;
+ case 'N': pin->SetVisible( false ); break;
+ case 'I': flags |= INVERTED; break;
+ case 'C': flags |= CLOCK; break;
+ case 'L': flags |= LOWLEVEL_IN; break;
+ case 'V': flags |= LOWLEVEL_OUT; break;
+ case 'F': flags |= FALLING_EDGE; break;
+ case 'X': flags |= NONLOGIC; break;
+ default: THROW_PARSE_ERROR( "invalid pin attribut", aReader.GetSource(),
+ aReader.Line(), aReader.LineNumber(), pos );
+ }
+
+ pos += 1;
+ }
+
+ switch( flags )
+ {
+ case 0: pin->SetShape( GRAPHIC_PINSHAPE::LINE ); break;
+ case INVERTED: pin->SetShape( GRAPHIC_PINSHAPE::INVERTED ); break;
+ case CLOCK: pin->SetShape( GRAPHIC_PINSHAPE::CLOCK ); break;
+ case INVERTED | CLOCK: pin->SetShape( GRAPHIC_PINSHAPE::INVERTED_CLOCK ); break;
+ case LOWLEVEL_IN: pin->SetShape( GRAPHIC_PINSHAPE::INPUT_LOW ); break;
+ case LOWLEVEL_IN | CLOCK: pin->SetShape( GRAPHIC_PINSHAPE::CLOCK_LOW ); break;
+ case LOWLEVEL_OUT: pin->SetShape( GRAPHIC_PINSHAPE::OUTPUT_LOW ); break;
+ case FALLING_EDGE: pin->SetShape( GRAPHIC_PINSHAPE::FALLING_EDGE_CLOCK ); break;
+ case NONLOGIC: pin->SetShape( GRAPHIC_PINSHAPE::NONLOGIC ); break;
+ default:
+ SCH_PARSE_ERROR( "pin attributes do not define a valid pin shape", aReader, line );
+ }
+ }
+
+ return pin;
+}
+
+
+LIB_SHAPE* SCH_LEGACY_PLUGIN_CACHE::loadPolyLine( std::unique_ptr& aSymbol,
+ LINE_READER& aReader )
+{
+ const char* line = aReader.Line();
+
+ wxCHECK_MSG( strCompare( "P", line, &line ), nullptr, "Invalid poly definition" );
+
+ LIB_SHAPE* polyLine = new LIB_SHAPE( aSymbol.get(), SHAPE_T::POLY );
+
+ int points = parseInt( aReader, line, &line );
+ polyLine->SetUnit( parseInt( aReader, line, &line ) );
+ polyLine->SetConvert( parseInt( aReader, line, &line ) );
+ polyLine->SetStroke( STROKE_PARAMS( Mils2Iu( parseInt( aReader, line, &line ) ),
+ PLOT_DASH_TYPE::SOLID ) );
+
+ VECTOR2I pt;
+
+ for( int i = 0; i < points; i++ )
+ {
+ pt.x = Mils2Iu( parseInt( aReader, line, &line ) );
+ pt.y = Mils2Iu( parseInt( aReader, line, &line ) );
+ polyLine->AddPoint( pt );
+ }
+
+ if( *line != 0 )
+ polyLine->SetFillMode( parseFillMode( aReader, line, &line ) );
+
+ return polyLine;
+}
+
+
+LIB_SHAPE* SCH_LEGACY_PLUGIN_CACHE::loadBezier( std::unique_ptr& aSymbol,
+ LINE_READER& aReader )
+{
+ const char* line = aReader.Line();
+
+ wxCHECK_MSG( strCompare( "B", line, &line ), nullptr, "Invalid Bezier definition" );
+
+ int points = parseInt( aReader, line, &line );
+
+ wxCHECK_MSG( points == 4, NULL, "Invalid Bezier curve definition" );
+
+ LIB_SHAPE* bezier = new LIB_SHAPE( aSymbol.get(), SHAPE_T::BEZIER );
+
+ bezier->SetUnit( parseInt( aReader, line, &line ) );
+ bezier->SetConvert( parseInt( aReader, line, &line ) );
+ bezier->SetStroke( STROKE_PARAMS( Mils2Iu( parseInt( aReader, line, &line ) ),
+ PLOT_DASH_TYPE::SOLID ) );
+
+ bezier->SetStart( VECTOR2I( Mils2Iu( parseInt( aReader, line, &line ) ),
+ Mils2Iu( parseInt( aReader, line, &line ) ) ) );
+
+ bezier->SetBezierC1( VECTOR2I( Mils2Iu( parseInt( aReader, line, &line ) ),
+ Mils2Iu( parseInt( aReader, line, &line ) ) ) );
+
+ bezier->SetBezierC2( VECTOR2I( Mils2Iu( parseInt( aReader, line, &line ) ),
+ Mils2Iu( parseInt( aReader, line, &line ) ) ) );
+
+ bezier->SetEnd( VECTOR2I( Mils2Iu( parseInt( aReader, line, &line ) ),
+ Mils2Iu( parseInt( aReader, line, &line ) ) ) );
+
+ bezier->RebuildBezierToSegmentsPointsList( bezier->GetWidth() );
+
+ if( *line != 0 )
+ bezier->SetFillMode( parseFillMode( aReader, line, &line ) );
+
+ return bezier;
+}
+
+
+void SCH_LEGACY_PLUGIN_CACHE::loadFootprintFilters( std::unique_ptr& aSymbol,
+ LINE_READER& aReader )
+{
+ const char* line = aReader.Line();
+
+ wxCHECK_RET( strCompare( "$FPLIST", line, &line ), "Invalid footprint filter list" );
+
+ line = aReader.ReadLine();
+
+ wxArrayString footprintFilters;
+
+ while( line )
+ {
+ if( strCompare( "$ENDFPLIST", line, &line ) )
+ {
+ aSymbol->SetFPFilters( footprintFilters );
+ return;
+ }
+
+ wxString footprint;
+
+ parseUnquotedString( footprint, aReader, line, &line );
+ footprintFilters.Add( footprint );
+ line = aReader.ReadLine();
+ }
+
+ SCH_PARSE_ERROR( "File ended prematurely while loading footprint filters.", aReader, line );
+}
+
+
+void SCH_LEGACY_PLUGIN_CACHE::Save( const std::optional& aOpt )
+{
+ wxCHECK( aOpt, /* void */ );
+
+ bool doSaveDocFile = *aOpt;
+
+ if( !m_isModified )
+ return;
+
+ // Write through symlinks, don't replace them
+ wxFileName fn = GetRealFile();
+
+ auto formatter = std::make_unique( fn.GetFullPath() );
+ formatter->Print( 0, "%s %d.%d\n", LIBFILE_IDENT, LIB_VERSION_MAJOR, LIB_VERSION_MINOR );
+ formatter->Print( 0, "#encoding utf-8\n");
+
+ for( LIB_SYMBOL_MAP::iterator it = m_symbols.begin(); it != m_symbols.end(); it++ )
+ {
+ if( !it->second->IsRoot() )
+ continue;
+
+ SaveSymbol( it->second, *formatter.get(), &m_symbols );
+ }
+
+ formatter->Print( 0, "#\n#End Library\n" );
+ formatter.reset();
+
+ m_fileModTime = fn.GetModificationTime();
+ m_isModified = false;
+
+ if( doSaveDocFile )
+ saveDocFile();
+}
+
+
+void SCH_LEGACY_PLUGIN_CACHE::SaveSymbol( LIB_SYMBOL* aSymbol, OUTPUTFORMATTER& aFormatter,
+ LIB_SYMBOL_MAP* aMap )
+{
+ /*
+ * NB:
+ * Some of the rescue code still uses the legacy format as an intermediary, so we have
+ * to keep this code.
+ */
+
+ wxCHECK_RET( aSymbol && aSymbol->IsRoot(), "Invalid LIB_SYMBOL pointer." );
+
+ // LIB_ALIAS objects are deprecated but we still need to gather up the derived symbols
+ // and save their names for the old file format.
+ wxArrayString aliasNames;
+
+ if( aMap )
+ {
+ for( auto entry : *aMap )
+ {
+ LIB_SYMBOL* symbol = entry.second;
+
+ if( symbol->IsAlias() && symbol->GetParent().lock() == aSymbol->SharedPtr() )
+ aliasNames.Add( symbol->GetName() );
+ }
+ }
+
+ LIB_FIELD& value = aSymbol->GetValueField();
+
+ // First line: it s a comment (symbol name for readers)
+ aFormatter.Print( 0, "#\n# %s\n#\n", TO_UTF8( value.GetText() ) );
+
+ // Save data
+ aFormatter.Print( 0, "DEF" );
+ aFormatter.Print( 0, " %s", TO_UTF8( value.GetText() ) );
+
+ LIB_FIELD& reference = aSymbol->GetReferenceField();
+
+ if( !reference.GetText().IsEmpty() )
+ aFormatter.Print( 0, " %s", TO_UTF8( reference.GetText() ) );
+ else
+ aFormatter.Print( 0, " ~" );
+
+ aFormatter.Print( 0, " %d %d %c %c %d %c %c\n",
+ 0, Iu2Mils( aSymbol->GetPinNameOffset() ),
+ aSymbol->ShowPinNumbers() ? 'Y' : 'N',
+ aSymbol->ShowPinNames() ? 'Y' : 'N',
+ aSymbol->GetUnitCount(), aSymbol->UnitsLocked() ? 'L' : 'F',
+ aSymbol->IsPower() ? 'P' : 'N' );
+
+ timestamp_t dateModified = aSymbol->GetLastModDate();
+
+ if( dateModified != 0 )
+ {
+ int sec = dateModified & 63;
+ int min = ( dateModified >> 6 ) & 63;
+ int hour = ( dateModified >> 12 ) & 31;
+ int day = ( dateModified >> 17 ) & 31;
+ int mon = ( dateModified >> 22 ) & 15;
+ int year = ( dateModified >> 26 ) + 1990;
+
+ aFormatter.Print( 0, "Ti %d/%d/%d %d:%d:%d\n", year, mon, day, hour, min, sec );
+ }
+
+ std::vector fields;
+ aSymbol->GetFields( fields );
+
+ // Mandatory fields:
+ // may have their own save policy so there is a separate loop for them.
+ // Empty fields are saved, because the user may have set visibility,
+ // size and orientation
+ for( int i = 0; i < MANDATORY_FIELDS; ++i )
+ saveField( fields[i], aFormatter );
+
+ // User defined fields:
+ // may have their own save policy so there is a separate loop for them.
+ int fieldId = MANDATORY_FIELDS; // really wish this would go away.
+
+ for( unsigned i = MANDATORY_FIELDS; i < fields.size(); ++i )
+ {
+ // There is no need to save empty fields, i.e. no reason to preserve field
+ // names now that fields names come in dynamically through the template
+ // fieldnames.
+ if( !fields[i]->GetText().IsEmpty() )
+ {
+ fields[i]->SetId( fieldId++ );
+ saveField( fields[i], aFormatter );
+ }
+ }
+
+ // Save the alias list: a line starting by "ALIAS".
+ if( !aliasNames.IsEmpty() )
+ {
+ aFormatter.Print( 0, "ALIAS" );
+
+ for( unsigned i = 0; i < aliasNames.GetCount(); i++ )
+ aFormatter.Print( 0, " %s", TO_UTF8( aliasNames[i] ) );
+
+ aFormatter.Print( 0, "\n" );
+ }
+
+ wxArrayString footprints = aSymbol->GetFPFilters();
+
+ // Write the footprint filter list
+ if( footprints.GetCount() != 0 )
+ {
+ aFormatter.Print( 0, "$FPLIST\n" );
+
+ for( unsigned i = 0; i < footprints.GetCount(); i++ )
+ aFormatter.Print( 0, " %s\n", TO_UTF8( footprints[i] ) );
+
+ aFormatter.Print( 0, "$ENDFPLIST\n" );
+ }
+
+ // Save graphics items (including pins)
+ if( !aSymbol->GetDrawItems().empty() )
+ {
+ // Sort the draw items in order to editing a file editing by hand.
+ aSymbol->GetDrawItems().sort();
+
+ aFormatter.Print( 0, "DRAW\n" );
+
+ for( LIB_ITEM& item : aSymbol->GetDrawItems() )
+ {
+ switch( item.Type() )
+ {
+ default:
+ case LIB_FIELD_T: /* Fields have already been saved above. */ break;
+ case LIB_PIN_T: savePin( (LIB_PIN* ) &item, aFormatter ); break;
+ case LIB_TEXT_T: saveText( ( LIB_TEXT* ) &item, aFormatter ); break;
+ case LIB_SHAPE_T:
+ {
+ LIB_SHAPE& shape = static_cast( item );
+
+ switch( shape.GetShape() )
+ {
+ case SHAPE_T::ARC: saveArc( &shape, aFormatter ); break;
+ case SHAPE_T::BEZIER: saveBezier( &shape, aFormatter ); break;
+ case SHAPE_T::CIRCLE: saveCircle( &shape, aFormatter ); break;
+ case SHAPE_T::POLY: savePolyLine( &shape, aFormatter ); break;
+ case SHAPE_T::RECT: saveRectangle( &shape, aFormatter ); break;
+ default: break;
+ }
+ }
+ }
+ }
+
+ aFormatter.Print( 0, "ENDDRAW\n" );
+ }
+
+ aFormatter.Print( 0, "ENDDEF\n" );
+}
+
+
+void SCH_LEGACY_PLUGIN_CACHE::saveArc( LIB_SHAPE* aArc, OUTPUTFORMATTER& aFormatter )
+{
+ wxCHECK_RET( aArc && aArc->GetShape() == SHAPE_T::ARC, "Invalid ARC object." );
+
+ EDA_ANGLE startAngle, endAngle;
+
+ aArc->CalcArcAngles( startAngle, endAngle );
+ startAngle.Normalize180();
+ endAngle.Normalize180();
+
+ aFormatter.Print( 0, "A %d %d %d %d %d %d %d %d %c %d %d %d %d\n",
+ Iu2Mils( aArc->GetPosition().x ),
+ Iu2Mils( aArc->GetPosition().y ),
+ Iu2Mils( aArc->GetRadius() ),
+ startAngle.AsTenthsOfADegree(),
+ endAngle.AsTenthsOfADegree(),
+ aArc->GetUnit(),
+ aArc->GetConvert(),
+ Iu2Mils( aArc->GetWidth() ),
+ fill_tab[ static_cast( aArc->GetFillMode() ) - 1 ],
+ Iu2Mils( aArc->GetStart().x ),
+ Iu2Mils( aArc->GetStart().y ),
+ Iu2Mils( aArc->GetEnd().x ),
+ Iu2Mils( aArc->GetEnd().y ) );
+}
+
+
+void SCH_LEGACY_PLUGIN_CACHE::saveBezier( LIB_SHAPE* aBezier, OUTPUTFORMATTER& aFormatter )
+{
+ wxCHECK_RET( aBezier && aBezier->GetShape() == SHAPE_T::BEZIER, "Invalid BEZIER object." );
+
+ aFormatter.Print( 0, "B %u %d %d %d",
+ (unsigned)aBezier->GetBezierPoints().size(),
+ aBezier->GetUnit(),
+ aBezier->GetConvert(),
+ Iu2Mils( aBezier->GetWidth() ) );
+
+ for( const VECTOR2I& pt : aBezier->GetBezierPoints() )
+ aFormatter.Print( 0, " %d %d", Iu2Mils( pt.x ), Iu2Mils( pt.y ) );
+
+ aFormatter.Print( 0, " %c\n", fill_tab[ static_cast( aBezier->GetFillMode() ) - 1 ] );
+}
+
+
+void SCH_LEGACY_PLUGIN_CACHE::saveCircle( LIB_SHAPE* aCircle, OUTPUTFORMATTER& aFormatter )
+{
+ wxCHECK_RET( aCircle && aCircle->GetShape() == SHAPE_T::CIRCLE, "Invalid CIRCLE object." );
+
+ aFormatter.Print( 0, "C %d %d %d %d %d %d %c\n",
+ Iu2Mils( aCircle->GetPosition().x ),
+ Iu2Mils( aCircle->GetPosition().y ),
+ Iu2Mils( aCircle->GetRadius() ),
+ aCircle->GetUnit(),
+ aCircle->GetConvert(),
+ Iu2Mils( aCircle->GetWidth() ),
+ fill_tab[ static_cast( aCircle->GetFillMode() ) - 1 ] );
+}
+
+
+void SCH_LEGACY_PLUGIN_CACHE::saveField( const LIB_FIELD* aField, OUTPUTFORMATTER& aFormatter )
+{
+ wxCHECK_RET( aField && aField->Type() == LIB_FIELD_T, "Invalid LIB_FIELD object." );
+
+ int hjustify, vjustify;
+ int id = aField->GetId();
+ wxString text = aField->GetText();
+
+ hjustify = 'C';
+
+ if( aField->GetHorizJustify() == GR_TEXT_H_ALIGN_LEFT )
+ hjustify = 'L';
+ else if( aField->GetHorizJustify() == GR_TEXT_H_ALIGN_RIGHT )
+ hjustify = 'R';
+
+ vjustify = 'C';
+
+ if( aField->GetVertJustify() == GR_TEXT_V_ALIGN_BOTTOM )
+ vjustify = 'B';
+ else if( aField->GetVertJustify() == GR_TEXT_V_ALIGN_TOP )
+ vjustify = 'T';
+
+ aFormatter.Print( 0, "F%d %s %d %d %d %c %c %c %c%c%c",
+ id,
+ EscapedUTF8( text ).c_str(), // wraps in quotes
+ Iu2Mils( aField->GetTextPos().x ),
+ Iu2Mils( aField->GetTextPos().y ),
+ Iu2Mils( aField->GetTextWidth() ),
+ aField->GetTextAngle().IsHorizontal() ? 'H' : 'V',
+ aField->IsVisible() ? 'V' : 'I',
+ hjustify, vjustify,
+ aField->IsItalic() ? 'I' : 'N',
+ aField->IsBold() ? 'B' : 'N' );
+
+ /* Save field name, if necessary
+ * Field name is saved only if it is not the default name.
+ * Just because default name depends on the language and can change from
+ * a country to another
+ */
+ wxString defName = TEMPLATE_FIELDNAME::GetDefaultFieldName( id );
+
+ if( id >= MANDATORY_FIELDS && !aField->m_name.IsEmpty() && aField->m_name != defName )
+ aFormatter.Print( 0, " %s", EscapedUTF8( aField->m_name ).c_str() );
+
+ aFormatter.Print( 0, "\n" );
+}
+
+
+void SCH_LEGACY_PLUGIN_CACHE::savePin( const LIB_PIN* aPin, OUTPUTFORMATTER& aFormatter )
+{
+ wxCHECK_RET( aPin && aPin->Type() == LIB_PIN_T, "Invalid LIB_PIN object." );
+
+ int Etype;
+
+ switch( aPin->GetType() )
+ {
+ default:
+ case ELECTRICAL_PINTYPE::PT_INPUT: Etype = 'I'; break;
+ case ELECTRICAL_PINTYPE::PT_OUTPUT: Etype = 'O'; break;
+ case ELECTRICAL_PINTYPE::PT_BIDI: Etype = 'B'; break;
+ case ELECTRICAL_PINTYPE::PT_TRISTATE: Etype = 'T'; break;
+ case ELECTRICAL_PINTYPE::PT_PASSIVE: Etype = 'P'; break;
+ case ELECTRICAL_PINTYPE::PT_UNSPECIFIED: Etype = 'U'; break;
+ case ELECTRICAL_PINTYPE::PT_POWER_IN: Etype = 'W'; break;
+ case ELECTRICAL_PINTYPE::PT_POWER_OUT: Etype = 'w'; break;
+ case ELECTRICAL_PINTYPE::PT_OPENCOLLECTOR: Etype = 'C'; break;
+ case ELECTRICAL_PINTYPE::PT_OPENEMITTER: Etype = 'E'; break;
+ case ELECTRICAL_PINTYPE::PT_NC: Etype = 'N'; break;
+ }
+
+ if( !aPin->GetName().IsEmpty() )
+ aFormatter.Print( 0, "X %s", TO_UTF8( aPin->GetName() ) );
+ else
+ aFormatter.Print( 0, "X ~" );
+
+ aFormatter.Print( 0, " %s %d %d %d %c %d %d %d %d %c",
+ aPin->GetNumber().IsEmpty() ? "~" : TO_UTF8( aPin->GetNumber() ),
+ Iu2Mils( aPin->GetPosition().x ),
+ Iu2Mils( aPin->GetPosition().y ),
+ Iu2Mils( (int) aPin->GetLength() ),
+ (int) aPin->GetOrientation(),
+ Iu2Mils( aPin->GetNumberTextSize() ),
+ Iu2Mils( aPin->GetNameTextSize() ),
+ aPin->GetUnit(),
+ aPin->GetConvert(),
+ Etype );
+
+ if( aPin->GetShape() != GRAPHIC_PINSHAPE::LINE || !aPin->IsVisible() )
+ aFormatter.Print( 0, " " );
+
+ if( !aPin->IsVisible() )
+ aFormatter.Print( 0, "N" );
+
+ switch( aPin->GetShape() )
+ {
+ case GRAPHIC_PINSHAPE::LINE: break;
+ case GRAPHIC_PINSHAPE::INVERTED: aFormatter.Print( 0, "I" ); break;
+ case GRAPHIC_PINSHAPE::CLOCK: aFormatter.Print( 0, "C" ); break;
+ case GRAPHIC_PINSHAPE::INVERTED_CLOCK: aFormatter.Print( 0, "IC" ); break;
+ case GRAPHIC_PINSHAPE::INPUT_LOW: aFormatter.Print( 0, "L" ); break;
+ case GRAPHIC_PINSHAPE::CLOCK_LOW: aFormatter.Print( 0, "CL" ); break;
+ case GRAPHIC_PINSHAPE::OUTPUT_LOW: aFormatter.Print( 0, "V" ); break;
+ case GRAPHIC_PINSHAPE::FALLING_EDGE_CLOCK: aFormatter.Print( 0, "F" ); break;
+ case GRAPHIC_PINSHAPE::NONLOGIC: aFormatter.Print( 0, "X" ); break;
+ default: wxFAIL_MSG( "Invalid pin shape" );
+ }
+
+ aFormatter.Print( 0, "\n" );
+
+ const_cast( aPin )->ClearFlags( IS_CHANGED );
+}
+
+
+void SCH_LEGACY_PLUGIN_CACHE::savePolyLine( LIB_SHAPE* aPolyLine, OUTPUTFORMATTER& aFormatter )
+{
+ wxCHECK_RET( aPolyLine && aPolyLine->GetShape() == SHAPE_T::POLY, "Invalid POLY object." );
+
+ aFormatter.Print( 0, "P %d %d %d %d",
+ (int) aPolyLine->GetPolyShape().Outline( 0 ).GetPointCount(),
+ aPolyLine->GetUnit(),
+ aPolyLine->GetConvert(),
+ Iu2Mils( aPolyLine->GetWidth() ) );
+
+ for( const VECTOR2I& pt : aPolyLine->GetPolyShape().Outline( 0 ).CPoints() )
+ aFormatter.Print( 0, " %d %d", Iu2Mils( pt.x ), Iu2Mils( pt.y ) );
+
+ aFormatter.Print( 0, " %c\n", fill_tab[ static_cast( aPolyLine->GetFillMode() ) - 1 ] );
+}
+
+
+void SCH_LEGACY_PLUGIN_CACHE::saveRectangle( LIB_SHAPE* aRectangle, OUTPUTFORMATTER& aFormatter )
+{
+ wxCHECK_RET( aRectangle && aRectangle->GetShape() == SHAPE_T::RECT, "Invalid RECT object." );
+
+ aFormatter.Print( 0, "S %d %d %d %d %d %d %d %c\n",
+ Iu2Mils( aRectangle->GetPosition().x ),
+ Iu2Mils( aRectangle->GetPosition().y ),
+ Iu2Mils( aRectangle->GetEnd().x ),
+ Iu2Mils( aRectangle->GetEnd().y ),
+ aRectangle->GetUnit(),
+ aRectangle->GetConvert(),
+ Iu2Mils( aRectangle->GetWidth() ),
+ fill_tab[ static_cast( aRectangle->GetFillMode() ) - 1 ] );
+}
+
+
+void SCH_LEGACY_PLUGIN_CACHE::saveText( const LIB_TEXT* aText, OUTPUTFORMATTER& aFormatter )
+{
+ wxCHECK_RET( aText && aText->Type() == LIB_TEXT_T, "Invalid LIB_TEXT object." );
+
+ wxString text = aText->GetText();
+
+ if( text.Contains( wxT( " " ) ) || text.Contains( wxT( "~" ) ) || text.Contains( wxT( "\"" ) ) )
+ {
+ // convert double quote to similar-looking two apostrophes
+ text.Replace( wxT( "\"" ), wxT( "''" ) );
+ text = wxT( "\"" ) + text + wxT( "\"" );
+ }
+
+ aFormatter.Print( 0, "T %g %d %d %d %d %d %d %s",
+ (double) aText->GetTextAngle().AsTenthsOfADegree(),
+ Iu2Mils( aText->GetTextPos().x ),
+ Iu2Mils( aText->GetTextPos().y ),
+ Iu2Mils( aText->GetTextWidth() ),
+ !aText->IsVisible(),
+ aText->GetUnit(),
+ aText->GetConvert(),
+ TO_UTF8( text ) );
+
+ aFormatter.Print( 0, " %s %d", aText->IsItalic() ? "Italic" : "Normal", aText->IsBold() );
+
+ char hjustify = 'C';
+
+ if( aText->GetHorizJustify() == GR_TEXT_H_ALIGN_LEFT )
+ hjustify = 'L';
+ else if( aText->GetHorizJustify() == GR_TEXT_H_ALIGN_RIGHT )
+ hjustify = 'R';
+
+ char vjustify = 'C';
+
+ if( aText->GetVertJustify() == GR_TEXT_V_ALIGN_BOTTOM )
+ vjustify = 'B';
+ else if( aText->GetVertJustify() == GR_TEXT_V_ALIGN_TOP )
+ vjustify = 'T';
+
+ aFormatter.Print( 0, " %c %c\n", hjustify, vjustify );
+}
+
+
+void SCH_LEGACY_PLUGIN_CACHE::saveDocFile()
+{
+ /*
+ * NB:
+ * Some of the rescue code still uses the legacy format as an intermediary, so we have
+ * to keep this code.
+ */
+
+ wxFileName fileName = m_libFileName;
+
+ fileName.SetExt( LegacySymbolDocumentFileExtension );
+ FILE_OUTPUTFORMATTER formatter( fileName.GetFullPath() );
+
+ formatter.Print( 0, "%s\n", DOCFILE_IDENT );
+
+ for( LIB_SYMBOL_MAP::iterator it = m_symbols.begin(); it != m_symbols.end(); ++it )
+ {
+ wxString description = it->second->GetDescription();
+ wxString keyWords = it->second->GetKeyWords();
+ wxString docFileName = it->second->GetDatasheetField().GetText();
+
+ if( description.IsEmpty() && keyWords.IsEmpty() && docFileName.IsEmpty() )
+ continue;
+
+ formatter.Print( 0, "#\n$CMP %s\n", TO_UTF8( it->second->GetName() ) );
+
+ if( !description.IsEmpty() )
+ formatter.Print( 0, "D %s\n", TO_UTF8( description ) );
+
+ if( !keyWords.IsEmpty() )
+ formatter.Print( 0, "K %s\n", TO_UTF8( keyWords ) );
+
+ if( !docFileName.IsEmpty() )
+ formatter.Print( 0, "F %s\n", TO_UTF8( docFileName ) );
+
+ formatter.Print( 0, "$ENDCMP\n" );
+ }
+
+ formatter.Print( 0, "#\n#End Doc Library\n" );
+}
+
+
+void SCH_LEGACY_PLUGIN_CACHE::DeleteSymbol( const wxString& aSymbolName )
+{
+ LIB_SYMBOL_MAP::iterator it = m_symbols.find( aSymbolName );
+
+ if( it == m_symbols.end() )
+ THROW_IO_ERROR( wxString::Format( _( "library %s does not contain a symbol named %s" ),
+ m_libFileName.GetFullName(), aSymbolName ) );
+
+ LIB_SYMBOL* symbol = it->second;
+
+ if( symbol->IsRoot() )
+ {
+ LIB_SYMBOL* rootSymbol = symbol;
+
+ // Remove the root symbol and all its children.
+ m_symbols.erase( it );
+
+ LIB_SYMBOL_MAP::iterator it1 = m_symbols.begin();
+
+ while( it1 != m_symbols.end() )
+ {
+ if( it1->second->IsAlias()
+ && it1->second->GetParent().lock() == rootSymbol->SharedPtr() )
+ {
+ delete it1->second;
+ it1 = m_symbols.erase( it1 );
+ }
+ else
+ {
+ it1++;
+ }
+ }
+
+ delete rootSymbol;
+ }
+ else
+ {
+ // Just remove the alias.
+ m_symbols.erase( it );
+ delete symbol;
+ }
+
+ SCH_LEGACY_PLUGIN_CACHE::IncrementModifyHash();
+ m_isModified = true;
+}
diff --git a/eeschema/sch_plugins/legacy/sch_legacy_lib_plugin_cache.h b/eeschema/sch_plugins/legacy/sch_legacy_lib_plugin_cache.h
new file mode 100644
index 0000000000..6121096d78
--- /dev/null
+++ b/eeschema/sch_plugins/legacy/sch_legacy_lib_plugin_cache.h
@@ -0,0 +1,102 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2022 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * @author Wayne Stambaugh
+ *
+ * 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, see .
+ */
+
+#ifndef _SCH_LEGACY_LIB_PLUGIN_CACHE_
+#define _SCH_LEGACY_LIB_PLUGIN_CACHE_
+
+#include
+
+#include // FILL_T
+
+#include "../sch_lib_plugin_cache.h"
+
+class FILE_LINE_READER;
+class LIB_FIELD;
+class LIB_PIN;
+class LIB_SHAPE;
+class LIB_TEXT;
+class LINE_READER;
+class SCH_LEGACY_PLUGIN;
+
+
+/**
+ * A cache assistant for KiCad legacy symbol libraries.
+ */
+class SCH_LEGACY_PLUGIN_CACHE : public SCH_LIB_PLUGIN_CACHE
+{
+public:
+ SCH_LEGACY_PLUGIN_CACHE( const wxString& aLibraryPath );
+ virtual ~SCH_LEGACY_PLUGIN_CACHE() {}
+
+ // Most all functions in this class throw IO_ERROR exceptions. There are no
+ // error codes nor user interface calls from here, nor in any SCH_PLUGIN objects.
+ // Catch these exceptions higher up please.
+
+ /// Save the entire library to file m_libFileName;
+ void Save( const std::optional& aOpt ) override;
+
+ void Load() override;
+
+ void DeleteSymbol( const wxString& aName ) override;
+
+ static LIB_SYMBOL* LoadPart( LINE_READER& aReader, int aMajorVersion, int aMinorVersion,
+ LIB_SYMBOL_MAP* aMap = nullptr );
+ static void SaveSymbol( LIB_SYMBOL* aSymbol, OUTPUTFORMATTER& aFormatter,
+ LIB_SYMBOL_MAP* aMap = nullptr );
+
+private:
+ friend SCH_LEGACY_PLUGIN;
+
+ void loadHeader( FILE_LINE_READER& aReader );
+ static void loadAliases( std::unique_ptr& aSymbol, LINE_READER& aReader,
+ LIB_SYMBOL_MAP* aMap = nullptr );
+ static void loadField( std::unique_ptr& aSymbol, LINE_READER& aReader );
+ static void loadDrawEntries( std::unique_ptr& aSymbol, LINE_READER& aReader,
+ int aMajorVersion, int aMinorVersion );
+ static void loadFootprintFilters( std::unique_ptr& aSymbol,
+ LINE_READER& aReader );
+ void loadDocs();
+ static LIB_SHAPE* loadArc( std::unique_ptr& aSymbol, LINE_READER& aReader );
+ static LIB_SHAPE* loadCircle( std::unique_ptr& aSymbol, LINE_READER& aReader );
+ static LIB_TEXT* loadText( std::unique_ptr& aSymbol, LINE_READER& aReader,
+ int aMajorVersion, int aMinorVersion );
+ static LIB_SHAPE* loadRect( std::unique_ptr& aSymbol, LINE_READER& aReader );
+ static LIB_PIN* loadPin( std::unique_ptr& aSymbol, LINE_READER& aReader );
+ static LIB_SHAPE* loadPolyLine( std::unique_ptr& aSymbol, LINE_READER& aReader );
+ static LIB_SHAPE* loadBezier( std::unique_ptr& aSymbol, LINE_READER& aReader );
+
+ static FILL_T parseFillMode( LINE_READER& aReader, const char* aLine, const char** aOutput );
+
+ void saveDocFile();
+ static void saveArc( LIB_SHAPE* aArc, OUTPUTFORMATTER& aFormatter );
+ static void saveBezier( LIB_SHAPE* aBezier, OUTPUTFORMATTER& aFormatter );
+ static void saveCircle( LIB_SHAPE* aCircle, OUTPUTFORMATTER& aFormatter );
+ static void saveField( const LIB_FIELD* aField, OUTPUTFORMATTER& aFormatter );
+ static void savePin( const LIB_PIN* aPin, OUTPUTFORMATTER& aFormatter );
+ static void savePolyLine( LIB_SHAPE* aPolyLine, OUTPUTFORMATTER& aFormatter );
+ static void saveRectangle( LIB_SHAPE* aRectangle, OUTPUTFORMATTER& aFormatter );
+ static void saveText( const LIB_TEXT* aText, OUTPUTFORMATTER& aFormatter );
+
+ int m_versionMajor;
+ int m_versionMinor;
+};
+
+#endif // _SCH_LEGACY_LIB_PLUGIN_CACHE_
diff --git a/eeschema/sch_plugins/legacy/sch_legacy_plugin.cpp b/eeschema/sch_plugins/legacy/sch_legacy_plugin.cpp
index 27362bd29a..b605959ca6 100644
--- a/eeschema/sch_plugins/legacy/sch_legacy_plugin.cpp
+++ b/eeschema/sch_plugins/legacy/sch_legacy_plugin.cpp
@@ -53,42 +53,17 @@
#include
#include
#include
+#include
#include
#include
#include
#include
#include
-#include
-#include
-#include
-#include
#include // for MAX_UNIT_COUNT_PER_PACKAGE definition
#include
#include
-#define Mils2Iu( x ) Mils2iu( x )
-
-#define LIB_VERSION_MAJOR 2 ///< Legacy symbol library major version.
-#define LIB_VERSION_MINOR 4 ///< Legacy symbol library minor version.
-
-#define LIB_VERSION( major, minor ) ( major * 100 + minor )
-
-/** Legacy symbol library (.lib) file header. */
-#define LIBFILE_IDENT "EESchema-LIBRARY Version"
-
-/** Legacy symbol library document (.dcm) file header. */
-#define DOCFILE_IDENT "EESchema-DOCLIB Version 2.0"
-
-/**
- * Library versions 2.4 and lower use the old separate library (.lib) and
- * document (.dcm) files. Symbol libraries after 2.4 merged the library
- * and document files into a single library file. This macro checks if the
- * library version supports the old format.
- */
-#define USE_OLD_DOC_FILE_FORMAT( major, minor ) \
- ( LIB_VERSION( major, minor ) <= LIB_VERSION( 2, 4 ) )
-
// Tokens to read/save graphic lines style
#define T_STYLE "style"
#define T_COLOR "rgb" // cannot be modified (used by wxWidgets)
@@ -96,112 +71,6 @@
#define T_WIDTH "width"
-/**
- * A cache assistant for the symbol library portion of the #SCH_PLUGIN API, and only for the
- * #SCH_LEGACY_PLUGIN, so therefore is private to this implementation file, i.e. not placed
- * into a header.
- */
-class SCH_LEGACY_PLUGIN_CACHE
-{
- static int s_modHash; // Keep track of the modification status of the library.
- wxString m_fileName; // Absolute path and file name.
- wxFileName m_libFileName; // Absolute path and file name is required here.
- wxDateTime m_fileModTime;
- LIB_SYMBOL_MAP m_symbols; // Map of names of #LIB_SYMBOL pointers.
- bool m_isWritable;
- bool m_isModified;
- int m_versionMajor;
- int m_versionMinor;
- SCH_LIB_TYPE m_libType; // Is this cache a symbol or symbol library.
-
- void loadHeader( FILE_LINE_READER& aReader );
- static void loadAliases( std::unique_ptr& aSymbol, LINE_READER& aReader,
- LIB_SYMBOL_MAP* aMap = nullptr );
- static void loadField( std::unique_ptr& aSymbol, LINE_READER& aReader );
- static void loadDrawEntries( std::unique_ptr& aSymbol, LINE_READER& aReader,
- int aMajorVersion, int aMinorVersion );
- static void loadFootprintFilters( std::unique_ptr& aSymbol,
- LINE_READER& aReader );
- void loadDocs();
- static LIB_SHAPE* loadArc( std::unique_ptr& aSymbol, LINE_READER& aReader );
- static LIB_SHAPE* loadCircle( std::unique_ptr& aSymbol, LINE_READER& aReader );
- static LIB_TEXT* loadText( std::unique_ptr& aSymbol, LINE_READER& aReader,
- int aMajorVersion, int aMinorVersion );
- static LIB_SHAPE* loadRect( std::unique_ptr& aSymbol, LINE_READER& aReader );
- static LIB_PIN* loadPin( std::unique_ptr& aSymbol, LINE_READER& aReader );
- static LIB_SHAPE* loadPolyLine( std::unique_ptr& aSymbol, LINE_READER& aReader );
- static LIB_SHAPE* loadBezier( std::unique_ptr& aSymbol, LINE_READER& aReader );
-
- static FILL_T parseFillMode( LINE_READER& aReader, const char* aLine, const char** aOutput );
- LIB_SYMBOL* removeSymbol( LIB_SYMBOL* aAlias );
-
- void saveDocFile();
- static void saveArc( LIB_SHAPE* aArc, OUTPUTFORMATTER& aFormatter );
- static void saveBezier( LIB_SHAPE* aBezier, OUTPUTFORMATTER& aFormatter );
- static void saveCircle( LIB_SHAPE* aCircle, OUTPUTFORMATTER& aFormatter );
- static void saveField( const LIB_FIELD* aField, OUTPUTFORMATTER& aFormatter );
- static void savePin( const LIB_PIN* aPin, OUTPUTFORMATTER& aFormatter );
- static void savePolyLine( LIB_SHAPE* aPolyLine, OUTPUTFORMATTER& aFormatter );
- static void saveRectangle( LIB_SHAPE* aRectangle, OUTPUTFORMATTER& aFormatter );
- static void saveText( const LIB_TEXT* aText, OUTPUTFORMATTER& aFormatter );
-
- friend SCH_LEGACY_PLUGIN;
-
- static std::mutex s_modHashMutex;
-
-public:
- SCH_LEGACY_PLUGIN_CACHE( const wxString& aLibraryPath );
- ~SCH_LEGACY_PLUGIN_CACHE();
-
- static void IncrementModifyHash()
- {
- std::lock_guard mut( SCH_LEGACY_PLUGIN_CACHE::s_modHashMutex );
- SCH_LEGACY_PLUGIN_CACHE::s_modHash++;
- }
-
- static int GetModifyHash()
- {
- std::lock_guard mut( SCH_LEGACY_PLUGIN_CACHE::s_modHashMutex );
- return SCH_LEGACY_PLUGIN_CACHE::s_modHash;
- }
-
- // Most all functions in this class throw IO_ERROR exceptions. There are no
- // error codes nor user interface calls from here, nor in any SCH_PLUGIN objects.
- // Catch these exceptions higher up please.
-
- /// Save the entire library to file m_libFileName;
- void Save( bool aSaveDocFile = true );
-
- void Load();
-
- void AddSymbol( const LIB_SYMBOL* aSymbol );
-
- void DeleteSymbol( const wxString& aName );
-
- // If m_libFileName is a symlink follow it to the real source file
- wxFileName GetRealFile() const;
-
- wxDateTime GetLibModificationTime();
-
- bool IsFile( const wxString& aFullPathAndFileName ) const;
-
- bool IsFileChanged() const;
-
- void SetModified( bool aModified = true ) { m_isModified = aModified; }
-
- wxString GetLogicalName() const { return m_libFileName.GetName(); }
-
- void SetFileName( const wxString& aFileName ) { m_libFileName = aFileName; }
-
- wxString GetFileName() const { return m_libFileName.GetFullPath(); }
-
- static LIB_SYMBOL* LoadPart( LINE_READER& aReader, int aMajorVersion, int aMinorVersion,
- LIB_SYMBOL_MAP* aMap = nullptr );
- static void SaveSymbol( LIB_SYMBOL* aSymbol, OUTPUTFORMATTER& aFormatter,
- LIB_SYMBOL_MAP* aMap = nullptr );
-};
-
-
SCH_LEGACY_PLUGIN::SCH_LEGACY_PLUGIN() :
m_progressReporter( nullptr ),
m_lineReader( nullptr ),
@@ -2086,1882 +1955,6 @@ void SCH_LEGACY_PLUGIN::saveBusAlias( std::shared_ptr aAlias )
}
-int SCH_LEGACY_PLUGIN_CACHE::s_modHash = 1; // starts at 1 and goes up
-std::mutex SCH_LEGACY_PLUGIN_CACHE::s_modHashMutex;
-
-
-SCH_LEGACY_PLUGIN_CACHE::SCH_LEGACY_PLUGIN_CACHE( const wxString& aFullPathAndFileName ) :
- m_fileName( aFullPathAndFileName ),
- m_libFileName( aFullPathAndFileName ),
- m_isWritable( true ),
- m_isModified( false )
-{
- m_versionMajor = -1;
- m_versionMinor = -1;
- m_libType = SCH_LIB_TYPE::LT_EESCHEMA;
-}
-
-
-SCH_LEGACY_PLUGIN_CACHE::~SCH_LEGACY_PLUGIN_CACHE()
-{
- // When the cache is destroyed, all of the alias objects on the heap should be deleted.
- for( auto& symbol : m_symbols )
- delete symbol.second;
-
- m_symbols.clear();
-}
-
-
-// If m_libFileName is a symlink follow it to the real source file
-wxFileName SCH_LEGACY_PLUGIN_CACHE::GetRealFile() const
-{
- wxFileName fn( m_libFileName );
- WX_FILENAME::ResolvePossibleSymlinks( fn );
- return fn;
-}
-
-
-wxDateTime SCH_LEGACY_PLUGIN_CACHE::GetLibModificationTime()
-{
- wxFileName fn = GetRealFile();
-
- // update the writable flag while we have a wxFileName, in a network this
- // is possibly quite dynamic anyway.
- m_isWritable = fn.IsFileWritable();
-
- return fn.GetModificationTime();
-}
-
-
-bool SCH_LEGACY_PLUGIN_CACHE::IsFile( const wxString& aFullPathAndFileName ) const
-{
- return m_fileName == aFullPathAndFileName;
-}
-
-
-bool SCH_LEGACY_PLUGIN_CACHE::IsFileChanged() const
-{
- wxFileName fn = GetRealFile();
-
- if( m_fileModTime.IsValid() && fn.IsOk() && fn.FileExists() )
- return fn.GetModificationTime() != m_fileModTime;
-
- return false;
-}
-
-
-LIB_SYMBOL* SCH_LEGACY_PLUGIN_CACHE::removeSymbol( LIB_SYMBOL* aSymbol )
-{
- wxCHECK_MSG( aSymbol != nullptr, nullptr, "NULL pointer cannot be removed from library." );
-
- LIB_SYMBOL* firstChild = nullptr;
- LIB_SYMBOL_MAP::iterator it = m_symbols.find( aSymbol->GetName() );
-
- if( it == m_symbols.end() )
- return nullptr;
-
- // If the entry pointer doesn't match the name it is mapped to in the library, we
- // have done something terribly wrong.
- wxCHECK_MSG( *it->second == aSymbol, nullptr,
- "Pointer mismatch while attempting to remove alias entry <" + aSymbol->GetName() +
- "> from library cache <" + m_libFileName.GetName() + ">." );
-
- // If the symbol is a root symbol used by other symbols find the first alias that uses
- // the root symbol and make it the new root.
- if( aSymbol->IsRoot() )
- {
- for( auto entry : m_symbols )
- {
- if( entry.second->IsAlias()
- && entry.second->GetParent().lock() == aSymbol->SharedPtr() )
- {
- firstChild = entry.second;
- break;
- }
- }
-
- if( firstChild )
- {
- for( LIB_ITEM& drawItem : aSymbol->GetDrawItems() )
- {
- if( drawItem.Type() == LIB_FIELD_T )
- {
- LIB_FIELD& field = static_cast( drawItem );
-
- if( firstChild->FindField( field.GetCanonicalName() ) )
- continue;
- }
-
- LIB_ITEM* newItem = (LIB_ITEM*) drawItem.Clone();
- drawItem.SetParent( firstChild );
- firstChild->AddDrawItem( newItem );
- }
-
- // Reparent the remaining aliases.
- for( auto entry : m_symbols )
- {
- if( entry.second->IsAlias()
- && entry.second->GetParent().lock() == aSymbol->SharedPtr() )
- entry.second->SetParent( firstChild );
- }
- }
- }
-
- m_symbols.erase( it );
- delete aSymbol;
- m_isModified = true;
- SCH_LEGACY_PLUGIN_CACHE::IncrementModifyHash();
- return firstChild;
-}
-
-
-void SCH_LEGACY_PLUGIN_CACHE::AddSymbol( const LIB_SYMBOL* aSymbol )
-{
- // aSymbol is cloned in SYMBOL_LIB::AddSymbol(). The cache takes ownership of aSymbol.
- wxString name = aSymbol->GetName();
- LIB_SYMBOL_MAP::iterator it = m_symbols.find( name );
-
- if( it != m_symbols.end() )
- {
- removeSymbol( it->second );
- }
-
- m_symbols[ name ] = const_cast< LIB_SYMBOL* >( aSymbol );
- m_isModified = true;
- SCH_LEGACY_PLUGIN_CACHE::IncrementModifyHash();
-}
-
-
-void SCH_LEGACY_PLUGIN_CACHE::Load()
-{
- if( !m_libFileName.FileExists() )
- {
- THROW_IO_ERROR( wxString::Format( _( "Library file '%s' not found." ),
- m_libFileName.GetFullPath() ) );
- }
-
- wxCHECK_RET( m_libFileName.IsAbsolute(),
- wxString::Format( "Cannot use relative file paths in legacy plugin to "
- "open library '%s'.", m_libFileName.GetFullPath() ) );
-
- wxLogTrace( traceSchLegacyPlugin, "Loading legacy symbol file '%s'",
- m_libFileName.GetFullPath() );
-
- FILE_LINE_READER reader( m_libFileName.GetFullPath() );
-
- if( !reader.ReadLine() )
- THROW_IO_ERROR( _( "Unexpected end of file." ) );
-
- const char* line = reader.Line();
-
- if( !strCompare( "EESchema-LIBRARY Version", line, &line ) )
- {
- // Old .sym files (which are libraries with only one symbol, used to store and reuse shapes)
- // EESchema-LIB Version x.x SYMBOL. They are valid files.
- if( !strCompare( "EESchema-LIB Version", line, &line ) )
- SCH_PARSE_ERROR( "file is not a valid symbol or symbol library file", reader, line );
- }
-
- m_versionMajor = parseInt( reader, line, &line );
-
- if( *line != '.' )
- SCH_PARSE_ERROR( "invalid file version formatting in header", reader, line );
-
- line++;
-
- m_versionMinor = parseInt( reader, line, &line );
-
- if( m_versionMajor < 1 || m_versionMinor < 0 || m_versionMinor > 99 )
- SCH_PARSE_ERROR( "invalid file version in header", reader, line );
-
- // Check if this is a symbol library which is the same as a symbol library but without
- // any alias, documentation, footprint filters, etc.
- if( strCompare( "SYMBOL", line, &line ) )
- {
- // Symbol files add date and time stamp info to the header.
- m_libType = SCH_LIB_TYPE::LT_SYMBOL;
-
- /// @todo Probably should check for a valid date and time stamp even though it's not used.
- }
- else
- {
- m_libType = SCH_LIB_TYPE::LT_EESCHEMA;
- }
-
- while( reader.ReadLine() )
- {
- line = reader.Line();
-
- if( *line == '#' || isspace( *line ) ) // Skip comments and blank lines.
- continue;
-
- // Headers where only supported in older library file formats.
- if( m_libType == SCH_LIB_TYPE::LT_EESCHEMA && strCompare( "$HEADER", line ) )
- loadHeader( reader );
-
- if( strCompare( "DEF", line ) )
- {
- // Read one DEF/ENDDEF symbol entry from library:
- LIB_SYMBOL* symbol = LoadPart( reader, m_versionMajor, m_versionMinor, &m_symbols );
-
- m_symbols[ symbol->GetName() ] = symbol;
- }
- }
-
- SCH_LEGACY_PLUGIN_CACHE::IncrementModifyHash();
-
- // Remember the file modification time of library file when the
- // cache snapshot was made, so that in a networked environment we will
- // reload the cache as needed.
- m_fileModTime = GetLibModificationTime();
-
- if( USE_OLD_DOC_FILE_FORMAT( m_versionMajor, m_versionMinor ) )
- loadDocs();
-}
-
-
-void SCH_LEGACY_PLUGIN_CACHE::loadDocs()
-{
- const char* line;
- wxString text;
- wxString aliasName;
- wxFileName fn = m_libFileName;
- LIB_SYMBOL* symbol = nullptr;;
-
- fn.SetExt( LegacySymbolDocumentFileExtension );
-
- // Not all libraries will have a document file.
- if( !fn.FileExists() )
- return;
-
- if( !fn.IsFileReadable() )
- {
- THROW_IO_ERROR( wxString::Format( _( "Insufficient permissions to read library '%s'." ),
- fn.GetFullPath() ) );
- }
-
- FILE_LINE_READER reader( fn.GetFullPath() );
-
- line = reader.ReadLine();
-
- if( !line )
- THROW_IO_ERROR( _( "symbol document library file is empty" ) );
-
- if( !strCompare( DOCFILE_IDENT, line, &line ) )
- SCH_PARSE_ERROR( "invalid document library file version formatting in header",
- reader, line );
-
- while( reader.ReadLine() )
- {
- line = reader.Line();
-
- if( *line == '#' ) // Comment line.
- continue;
-
- if( !strCompare( "$CMP", line, &line ) != 0 )
- SCH_PARSE_ERROR( "$CMP command expected", reader, line );
-
- aliasName = wxString::FromUTF8( line );
- aliasName.Trim();
-
- LIB_SYMBOL_MAP::iterator it = m_symbols.find( aliasName );
-
- if( it == m_symbols.end() )
- wxLogWarning( "Symbol '%s' not found in library:\n\n"
- "'%s'\n\nat line %d offset %d", aliasName, fn.GetFullPath(),
- reader.LineNumber(), (int) (line - reader.Line() ) );
- else
- symbol = it->second;
-
- // Read the current alias associated doc.
- // if the alias does not exist, just skip the description
- // (Can happen if a .dcm is not synchronized with the corresponding .lib file)
- while( reader.ReadLine() )
- {
- line = reader.Line();
-
- if( !line )
- SCH_PARSE_ERROR( "unexpected end of file", reader, line );
-
- if( strCompare( "$ENDCMP", line, &line ) )
- break;
-
- text = FROM_UTF8( line + 2 );
- // Remove spaces at eol, and eol chars:
- text = text.Trim();
-
- switch( line[0] )
- {
- case 'D':
- if( symbol )
- symbol->SetDescription( text );
- break;
-
- case 'K':
- if( symbol )
- symbol->SetKeyWords( text );
- break;
-
- case 'F':
- if( symbol )
- symbol->GetFieldById( DATASHEET_FIELD )->SetText( text );
- break;
-
- case 0:
- case '\n':
- case '\r':
- case '#':
- // Empty line or commment
- break;
-
- default:
- SCH_PARSE_ERROR( "expected token in symbol definition", reader, line );
- }
- }
- }
-}
-
-
-void SCH_LEGACY_PLUGIN_CACHE::loadHeader( FILE_LINE_READER& aReader )
-{
- const char* line = aReader.Line();
-
- wxASSERT( strCompare( "$HEADER", line, &line ) );
-
- while( aReader.ReadLine() )
- {
- line = (char*) aReader;
-
- // The time stamp saved in old library files is not used or saved in the latest
- // library file version.
- if( strCompare( "TimeStamp", line, &line ) )
- continue;
- else if( strCompare( "$ENDHEADER", line, &line ) )
- return;
- }
-
- SCH_PARSE_ERROR( "$ENDHEADER not found", aReader, line );
-}
-
-
-LIB_SYMBOL* SCH_LEGACY_PLUGIN_CACHE::LoadPart( LINE_READER& aReader, int aMajorVersion,
- int aMinorVersion, LIB_SYMBOL_MAP* aMap )
-{
- const char* line = aReader.Line();
-
- while( *line == '#' )
- aReader.ReadLine();
-
- if( !strCompare( "DEF", line, &line ) )
- SCH_PARSE_ERROR( "invalid symbol definition", aReader, line );
-
- long num;
- size_t pos = 4; // "DEF" plus the first space.
- wxString utf8Line = wxString::FromUTF8( line );
- wxStringTokenizer tokens( utf8Line, " \r\n\t" );
-
- if( tokens.CountTokens() < 8 )
- SCH_PARSE_ERROR( "invalid symbol definition", aReader, line );
-
- // Read DEF line:
- std::unique_ptr symbol = std::make_unique( wxEmptyString );
-
- wxString name, prefix, tmp;
-
- name = tokens.GetNextToken();
- name = EscapeString( name, CTX_LIBID );
- pos += name.size() + 1;
-
- prefix = tokens.GetNextToken();
- pos += prefix.size() + 1;
-
- tmp = tokens.GetNextToken();
- pos += tmp.size() + 1; // NumOfPins, unused.
-
- tmp = tokens.GetNextToken(); // Pin name offset.
-
- if( !tmp.ToLong( &num ) )
- THROW_PARSE_ERROR( "invalid pin offset", aReader.GetSource(), aReader.Line(),
- aReader.LineNumber(), pos );
-
- pos += tmp.size() + 1;
- symbol->SetPinNameOffset( Mils2Iu( (int)num ) );
-
- tmp = tokens.GetNextToken(); // Show pin numbers.
-
- if( !( tmp == "Y" || tmp == "N") )
- THROW_PARSE_ERROR( "expected Y or N", aReader.GetSource(), aReader.Line(),
- aReader.LineNumber(), pos );
-
- pos += tmp.size() + 1;
- symbol->SetShowPinNumbers( ( tmp == "N" ) ? false : true );
-
- tmp = tokens.GetNextToken(); // Show pin names.
-
- if( !( tmp == "Y" || tmp == "N") )
- THROW_PARSE_ERROR( "expected Y or N", aReader.GetSource(), aReader.Line(),
- aReader.LineNumber(), pos );
-
- pos += tmp.size() + 1;
- symbol->SetShowPinNames( ( tmp == "N" ) ? false : true );
-
- tmp = tokens.GetNextToken(); // Number of units.
-
- if( !tmp.ToLong( &num ) )
- THROW_PARSE_ERROR( "invalid unit count", aReader.GetSource(), aReader.Line(),
- aReader.LineNumber(), pos );
-
- pos += tmp.size() + 1;
- symbol->SetUnitCount( (int)num );
-
- // Ensure m_unitCount is >= 1. Could be read as 0 in old libraries.
- if( symbol->GetUnitCount() < 1 )
- symbol->SetUnitCount( 1 );
-
- // Copy symbol name and prefix.
-
- // The root alias is added to the alias list by SetName() which is called by SetText().
- if( name.IsEmpty() )
- {
- symbol->SetName( "~" );
- }
- else if( name[0] != '~' )
- {
- symbol->SetName( name );
- }
- else
- {
- symbol->SetName( name.Right( name.Length() - 1 ) );
- symbol->GetValueField().SetVisible( false );
- }
-
- // Don't set the library alias, this is determined by the symbol library table.
- symbol->SetLibId( LIB_ID( wxEmptyString, symbol->GetName() ) );
-
- LIB_FIELD& reference = symbol->GetReferenceField();
-
- if( prefix == "~" )
- {
- reference.Empty();
- reference.SetVisible( false );
- }
- else
- {
- reference.SetText( prefix );
- }
-
- // In version 2.2 and earlier, this parameter was a '0' which was just a place holder.
- // The was no concept of interchangeable multiple unit symbols.
- if( LIB_VERSION( aMajorVersion, aMinorVersion ) > 0
- && LIB_VERSION( aMajorVersion, aMinorVersion ) <= LIB_VERSION( 2, 2 ) )
- {
- // Nothing needs to be set since the default setting for symbols with multiple
- // units were never interchangeable. Just parse the 0 an move on.
- tmp = tokens.GetNextToken();
- pos += tmp.size() + 1;
- }
- else
- {
- tmp = tokens.GetNextToken();
-
- if( tmp == "L" )
- symbol->LockUnits( true );
- else if( tmp == "F" || tmp == "0" )
- symbol->LockUnits( false );
- else
- THROW_PARSE_ERROR( "expected L, F, or 0", aReader.GetSource(), aReader.Line(),
- aReader.LineNumber(), pos );
-
- pos += tmp.size() + 1;
- }
-
- // There is the optional power symbol flag.
- if( tokens.HasMoreTokens() )
- {
- tmp = tokens.GetNextToken();
-
- if( tmp == "P" )
- symbol->SetPower();
- else if( tmp == "N" )
- symbol->SetNormal();
- else
- THROW_PARSE_ERROR( "expected P or N", aReader.GetSource(), aReader.Line(),
- aReader.LineNumber(), pos );
- }
-
- line = aReader.ReadLine();
-
- // Read lines until "ENDDEF" is found.
- while( line )
- {
- if( *line == '#' ) // Comment
- ;
- else if( strCompare( "Ti", line, &line ) ) // Modification date is ignored.
- continue;
- else if( strCompare( "ALIAS", line, &line ) ) // Aliases
- loadAliases( symbol, aReader, aMap );
- else if( *line == 'F' ) // Fields
- loadField( symbol, aReader );
- else if( strCompare( "DRAW", line, &line ) ) // Drawing objects.
- loadDrawEntries( symbol, aReader, aMajorVersion, aMinorVersion );
- else if( strCompare( "$FPLIST", line, &line ) ) // Footprint filter list
- loadFootprintFilters( symbol, aReader );
- else if( strCompare( "ENDDEF", line, &line ) ) // End of symbol description
- {
- return symbol.release();
- }
-
- line = aReader.ReadLine();
- }
-
- SCH_PARSE_ERROR( "missing ENDDEF", aReader, line );
-}
-
-
-void SCH_LEGACY_PLUGIN_CACHE::loadAliases( std::unique_ptr& aSymbol,
- LINE_READER& aReader,
- LIB_SYMBOL_MAP* aMap )
-{
- wxString newAliasName;
- const char* line = aReader.Line();
-
- wxCHECK_RET( strCompare( "ALIAS", line, &line ), "Invalid ALIAS section" );
-
- wxString utf8Line = wxString::FromUTF8( line );
- wxStringTokenizer tokens( utf8Line, " \r\n\t" );
-
- // Parse the ALIAS list.
- while( tokens.HasMoreTokens() )
- {
- newAliasName = tokens.GetNextToken();
-
- if( aMap )
- {
- LIB_SYMBOL* newSymbol = new LIB_SYMBOL( newAliasName );
-
- // Inherit the parent mandatory field attributes.
- for( int id = 0; id < MANDATORY_FIELDS; ++id )
- {
- LIB_FIELD* field = newSymbol->GetFieldById( id );
-
- // the MANDATORY_FIELDS are exactly that in RAM.
- wxASSERT( field );
-
- LIB_FIELD* parentField = aSymbol->GetFieldById( id );
-
- wxASSERT( parentField );
-
- *field = *parentField;
-
- if( id == VALUE_FIELD )
- field->SetText( newAliasName );
-
- field->SetParent( newSymbol );
- }
-
- newSymbol->SetParent( aSymbol.get() );
-
- // This will prevent duplicate aliases.
- (*aMap)[ newSymbol->GetName() ] = newSymbol;
- }
- }
-}
-
-
-void SCH_LEGACY_PLUGIN_CACHE::loadField( std::unique_ptr& aSymbol,
- LINE_READER& aReader )
-{
- const char* line = aReader.Line();
-
- wxCHECK_RET( *line == 'F', "Invalid field line" );
-
- wxString text;
- int id;
-
- if( sscanf( line + 1, "%d", &id ) != 1 || id < 0 )
- SCH_PARSE_ERROR( "invalid field ID", aReader, line + 1 );
-
- LIB_FIELD* field;
-
- if( id >= 0 && id < MANDATORY_FIELDS )
- {
- field = aSymbol->GetFieldById( id );
-
- // this will fire only if somebody broke a constructor or editor.
- // MANDATORY_FIELDS are always present in ram resident symbols, no
- // exceptions, and they always have their names set, even fixed fields.
- wxASSERT( field );
- }
- else
- {
- field = new LIB_FIELD( aSymbol.get(), id );
- aSymbol->AddDrawItem( field, false );
- }
-
- // Skip to the first double quote.
- while( *line != '"' && *line != 0 )
- line++;
-
- if( *line == 0 )
- SCH_PARSE_ERROR( _( "unexpected end of line" ), aReader, line );
-
- parseQuotedString( text, aReader, line, &line, true );
-
- // The value field needs to be "special" escaped. The other fields are
- // escaped normally and don't need special handling
- if( id == VALUE_FIELD )
- text = EscapeString( text, CTX_QUOTED_STR );
-
- // Doctor the *.lib file field which has a "~" in blank fields. New saves will
- // not save like this.
- if( text.size() == 1 && text[0] == '~' )
- field->SetText( wxEmptyString );
- else
- field->SetText( ConvertToNewOverbarNotation( text ) );
-
- VECTOR2I pos;
-
- pos.x = Mils2Iu( parseInt( aReader, line, &line ) );
- pos.y = Mils2Iu( parseInt( aReader, line, &line ) );
- field->SetPosition( pos );
-
- wxSize textSize;
-
- textSize.x = textSize.y = Mils2Iu( parseInt( aReader, line, &line ) );
- field->SetTextSize( textSize );
-
- char textOrient = parseChar( aReader, line, &line );
-
- if( textOrient == 'H' )
- field->SetTextAngle( ANGLE_HORIZONTAL );
- else if( textOrient == 'V' )
- field->SetTextAngle( ANGLE_VERTICAL );
- else
- SCH_PARSE_ERROR( "invalid field text orientation parameter", aReader, line );
-
- char textVisible = parseChar( aReader, line, &line );
-
- if( textVisible == 'V' )
- field->SetVisible( true );
- else if ( textVisible == 'I' )
- field->SetVisible( false );
- else
- SCH_PARSE_ERROR( "invalid field text visibility parameter", aReader, line );
-
- // It may be technically correct to use the library version to determine if the field text
- // attributes are present. If anyone knows if that is valid and what version that would be,
- // please change this to test the library version rather than an EOL or the quoted string
- // of the field name.
- if( *line != 0 && *line != '"' )
- {
- char textHJustify = parseChar( aReader, line, &line );
-
- if( textHJustify == 'C' )
- field->SetHorizJustify( GR_TEXT_H_ALIGN_CENTER );
- else if( textHJustify == 'L' )
- field->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
- else if( textHJustify == 'R' )
- field->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT );
- else
- SCH_PARSE_ERROR( "invalid field text horizontal justification", aReader, line );
-
- wxString attributes;
-
- parseUnquotedString( attributes, aReader, line, &line );
-
- size_t attrSize = attributes.size();
-
- if( !(attrSize == 3 || attrSize == 1 ) )
- SCH_PARSE_ERROR( "invalid field text attributes size", aReader, line );
-
- switch( (wxChar) attributes[0] )
- {
- case 'C': field->SetVertJustify( GR_TEXT_V_ALIGN_CENTER ); break;
- case 'B': field->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM ); break;
- case 'T': field->SetVertJustify( GR_TEXT_V_ALIGN_TOP ); break;
- default: SCH_PARSE_ERROR( "invalid field text vertical justification", aReader, line );
- }
-
- if( attrSize == 3 )
- {
- wxChar attr_1 = attributes[1];
- wxChar attr_2 = attributes[2];
-
- if( attr_1 == 'I' ) // Italic
- field->SetItalic( true );
- else if( attr_1 != 'N' ) // No italics is default, check for error.
- SCH_PARSE_ERROR( "invalid field text italic parameter", aReader, line );
-
- if ( attr_2 == 'B' ) // Bold
- field->SetBold( true );
- else if( attr_2 != 'N' ) // No bold is default, check for error.
- SCH_PARSE_ERROR( "invalid field text bold parameter", aReader, line );
- }
- }
-
- // Fields in RAM must always have names.
- if( id >= 0 && id < MANDATORY_FIELDS )
- {
- // Fields in RAM must always have names, because we are trying to get
- // less dependent on field ids and more dependent on names.
- // Plus assumptions are made in the field editors.
- field->m_name = TEMPLATE_FIELDNAME::GetDefaultFieldName( id );
-
- // Ensure the VALUE field = the symbol name (can be not the case
- // with malformed libraries: edited by hand, or converted from other tools)
- if( id == VALUE_FIELD )
- field->SetText( aSymbol->GetName() );
- }
- else
- {
- parseQuotedString( field->m_name, aReader, line, &line, true ); // Optional.
- }
-}
-
-
-void SCH_LEGACY_PLUGIN_CACHE::loadDrawEntries( std::unique_ptr& aSymbol,
- LINE_READER& aReader,
- int aMajorVersion,
- int aMinorVersion )
-{
- const char* line = aReader.Line();
-
- wxCHECK_RET( strCompare( "DRAW", line, &line ), "Invalid DRAW section" );
-
- line = aReader.ReadLine();
-
- while( line )
- {
- if( strCompare( "ENDDRAW", line, &line ) )
- {
- aSymbol->GetDrawItems().sort();
- return;
- }
-
- switch( line[0] )
- {
- case 'A': // Arc
- aSymbol->AddDrawItem( loadArc( aSymbol, aReader ), false );
- break;
-
- case 'C': // Circle
- aSymbol->AddDrawItem( loadCircle( aSymbol, aReader ), false );
- break;
-
- case 'T': // Text
- aSymbol->AddDrawItem( loadText( aSymbol, aReader, aMajorVersion,
- aMinorVersion ), false );
- break;
-
- case 'S': // Square
- aSymbol->AddDrawItem( loadRect( aSymbol, aReader ), false );
- break;
-
- case 'X': // Pin Description
- aSymbol->AddDrawItem( loadPin( aSymbol, aReader ), false );
- break;
-
- case 'P': // Polyline
- aSymbol->AddDrawItem( loadPolyLine( aSymbol, aReader ), false );
- break;
-
- case 'B': // Bezier Curves
- aSymbol->AddDrawItem( loadBezier( aSymbol, aReader ), false );
- break;
-
- case '#': // Comment
- case '\n': // Empty line
- case '\r':
- case 0:
- break;
-
- default:
- SCH_PARSE_ERROR( "undefined DRAW entry", aReader, line );
- }
-
- line = aReader.ReadLine();
- }
-
- SCH_PARSE_ERROR( "File ended prematurely loading symbol draw element.", aReader, line );
-}
-
-
-FILL_T SCH_LEGACY_PLUGIN_CACHE::parseFillMode( LINE_READER& aReader, const char* aLine,
- const char** aOutput )
-{
- switch ( parseChar( aReader, aLine, aOutput ) )
- {
- case 'F': return FILL_T::FILLED_SHAPE;
- case 'f': return FILL_T::FILLED_WITH_BG_BODYCOLOR;
- case 'N': return FILL_T::NO_FILL;
- default: SCH_PARSE_ERROR( "invalid fill type, expected f, F, or N", aReader, aLine );
- }
-
- // This will never be reached but quiets the compiler warnings
- return FILL_T::NO_FILL;
-}
-
-
-LIB_SHAPE* SCH_LEGACY_PLUGIN_CACHE::loadArc( std::unique_ptr& aSymbol,
- LINE_READER& aReader )
-{
- const char* line = aReader.Line();
-
- wxCHECK_MSG( strCompare( "A", line, &line ), nullptr, "Invalid arc definition" );
-
- LIB_SHAPE* arc = new LIB_SHAPE( aSymbol.get(), SHAPE_T::ARC );
-
- VECTOR2I center;
-
- center.x = Mils2Iu( parseInt( aReader, line, &line ) );
- center.y = Mils2Iu( parseInt( aReader, line, &line ) );
-
- arc->SetPosition( center );
-
- int radius = Mils2Iu( parseInt( aReader, line, &line ) );
- EDA_ANGLE angle1( parseInt( aReader, line, &line ), TENTHS_OF_A_DEGREE_T );
- EDA_ANGLE angle2( parseInt( aReader, line, &line ), TENTHS_OF_A_DEGREE_T );
-
- angle1.Normalize();
- angle2.Normalize();
-
- arc->SetUnit( parseInt( aReader, line, &line ) );
- arc->SetConvert( parseInt( aReader, line, &line ) );
- arc->SetStroke( STROKE_PARAMS( Mils2Iu( parseInt( aReader, line, &line ) ),
- PLOT_DASH_TYPE::SOLID ) );
-
- // Old libraries (version <= 2.2) do not have always this FILL MODE param
- // when fill mode is no fill (default mode).
- if( *line != 0 )
- arc->SetFillMode( parseFillMode( aReader, line, &line ) );
-
- // Actual Coordinates of arc ends are read from file
- if( *line != 0 )
- {
- VECTOR2I arcStart, arcEnd;
-
- arcStart.x = Mils2Iu( parseInt( aReader, line, &line ) );
- arcStart.y = Mils2Iu( parseInt( aReader, line, &line ) );
- arcEnd.x = Mils2Iu( parseInt( aReader, line, &line ) );
- arcEnd.y = Mils2Iu( parseInt( aReader, line, &line ) );
-
- arc->SetStart( arcStart );
- arc->SetEnd( arcEnd );
- }
- else
- {
- // Actual Coordinates of arc ends are not read from file
- // (old library), calculate them
- VECTOR2I arcStart( radius, 0 );
- VECTOR2I arcEnd( radius, 0 );
-
- RotatePoint( &arcStart.x, &arcStart.y, -angle1 );
- arcStart += arc->GetCenter();
- arc->SetStart( arcStart );
- RotatePoint( &arcEnd.x, &arcEnd.y, -angle2 );
- arcEnd += arc->GetCenter();
- arc->SetEnd( arcEnd );
- }
-
- /**
- * This accounts for an oddity in the old library format, where the symbol is overdefined.
- * The previous draw (based on wxwidgets) used start point and end point and always drew
- * counter-clockwise. The new GAL draw takes center, radius and start/end angles. All of
- * these points were stored in the file, so we need to mimic the swapping of start/end
- * points rather than using the stored angles in order to properly map edge cases.
- */
- if( !TRANSFORM().MapAngles( &angle1, &angle2 ) )
- {
- VECTOR2I temp = arc->GetStart();
- arc->SetStart( arc->GetEnd() );
- arc->SetEnd( temp );
- }
-
- return arc;
-}
-
-
-LIB_SHAPE* SCH_LEGACY_PLUGIN_CACHE::loadCircle( std::unique_ptr& aSymbol,
- LINE_READER& aReader )
-{
- const char* line = aReader.Line();
-
- wxCHECK_MSG( strCompare( "C", line, &line ), nullptr, "Invalid circle definition" );
-
- LIB_SHAPE* circle = new LIB_SHAPE( aSymbol.get(), SHAPE_T::CIRCLE );
-
- VECTOR2I center;
-
- center.x = Mils2Iu( parseInt( aReader, line, &line ) );
- center.y = Mils2Iu( parseInt( aReader, line, &line ) );
-
- int radius = Mils2Iu( parseInt( aReader, line, &line ) );
-
- circle->SetStart( center );
- circle->SetEnd( VECTOR2I( center.x + radius, center.y ) );
- circle->SetUnit( parseInt( aReader, line, &line ) );
- circle->SetConvert( parseInt( aReader, line, &line ) );
- circle->SetStroke( STROKE_PARAMS( Mils2Iu( parseInt( aReader, line, &line ) ),
- PLOT_DASH_TYPE::SOLID ) );
-
- if( *line != 0 )
- circle->SetFillMode( parseFillMode( aReader, line, &line ) );
-
- return circle;
-}
-
-
-LIB_TEXT* SCH_LEGACY_PLUGIN_CACHE::loadText( std::unique_ptr& aSymbol,
- LINE_READER& aReader,
- int aMajorVersion,
- int aMinorVersion )
-{
- const char* line = aReader.Line();
-
- wxCHECK_MSG( strCompare( "T", line, &line ), nullptr, "Invalid LIB_TEXT definition" );
-
- LIB_TEXT* text = new LIB_TEXT( aSymbol.get() );
- double angleInTenths = parseInt( aReader, line, &line );
-
- text->SetTextAngle( EDA_ANGLE( angleInTenths, TENTHS_OF_A_DEGREE_T ) );
-
- VECTOR2I center;
-
- center.x = Mils2Iu( parseInt( aReader, line, &line ) );
- center.y = Mils2Iu( parseInt( aReader, line, &line ) );
- text->SetPosition( center );
-
- wxSize size;
-
- size.x = size.y = Mils2Iu( parseInt( aReader, line, &line ) );
- text->SetTextSize( size );
- text->SetVisible( !parseInt( aReader, line, &line ) );
- text->SetUnit( parseInt( aReader, line, &line ) );
- text->SetConvert( parseInt( aReader, line, &line ) );
-
- wxString str;
-
- // If quoted string loading fails, load as not quoted string.
- if( *line == '"' )
- {
- parseQuotedString( str, aReader, line, &line );
-
- str = ConvertToNewOverbarNotation( str );
- }
- else
- {
- parseUnquotedString( str, aReader, line, &line );
-
- // In old libs, "spaces" are replaced by '~' in unquoted strings:
- str.Replace( "~", " " );
- }
-
- if( !str.IsEmpty() )
- {
- // convert two apostrophes back to double quote
- str.Replace( "''", "\"" );
- }
-
- text->SetText( str );
-
- // Here things are murky and not well defined. At some point it appears the format
- // was changed to add text properties. However rather than add the token to the end of
- // the text definition, it was added after the string and no mention if the file
- // verion was bumped or not so this code make break on very old symbol libraries.
- //
- // Update: apparently even in the latest version this can be different so added a test
- // for end of line before checking for the text properties.
- if( LIB_VERSION( aMajorVersion, aMinorVersion ) > 0
- && LIB_VERSION( aMajorVersion, aMinorVersion ) > LIB_VERSION( 2, 0 ) && !is_eol( *line ) )
- {
- if( strCompare( "Italic", line, &line ) )
- text->SetItalic( true );
- else if( !strCompare( "Normal", line, &line ) )
- SCH_PARSE_ERROR( "invalid text stype, expected 'Normal' or 'Italic'", aReader, line );
-
- if( parseInt( aReader, line, &line ) > 0 )
- text->SetBold( true );
-
- // Some old libaries version > 2.0 do not have these options for text justification:
- if( !is_eol( *line ) )
- {
- switch( parseChar( aReader, line, &line ) )
- {
- case 'L': text->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT ); break;
- case 'C': text->SetHorizJustify( GR_TEXT_H_ALIGN_CENTER ); break;
- case 'R': text->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT ); break;
- default: SCH_PARSE_ERROR( "invalid horizontal text justication; expected L, C, or R",
- aReader, line );
- }
-
- switch( parseChar( aReader, line, &line ) )
- {
- case 'T': text->SetVertJustify( GR_TEXT_V_ALIGN_TOP ); break;
- case 'C': text->SetVertJustify( GR_TEXT_V_ALIGN_CENTER ); break;
- case 'B': text->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM ); break;
- default: SCH_PARSE_ERROR( "invalid vertical text justication; expected T, C, or B",
- aReader, line );
- }
- }
- }
-
- return text;
-}
-
-
-LIB_SHAPE* SCH_LEGACY_PLUGIN_CACHE::loadRect( std::unique_ptr& aSymbol,
- LINE_READER& aReader )
-{
- const char* line = aReader.Line();
-
- wxCHECK_MSG( strCompare( "S", line, &line ), nullptr, "Invalid rectangle definition" );
-
- LIB_SHAPE* rectangle = new LIB_SHAPE( aSymbol.get(), SHAPE_T::RECT );
-
- VECTOR2I pos;
-
- pos.x = Mils2Iu( parseInt( aReader, line, &line ) );
- pos.y = Mils2Iu( parseInt( aReader, line, &line ) );
- rectangle->SetPosition( pos );
-
- VECTOR2I end;
-
- end.x = Mils2Iu( parseInt( aReader, line, &line ) );
- end.y = Mils2Iu( parseInt( aReader, line, &line ) );
- rectangle->SetEnd( end );
-
- rectangle->SetUnit( parseInt( aReader, line, &line ) );
- rectangle->SetConvert( parseInt( aReader, line, &line ) );
- rectangle->SetStroke( STROKE_PARAMS( Mils2Iu( parseInt( aReader, line, &line ) ),
- PLOT_DASH_TYPE::SOLID ) );
-
- if( *line != 0 )
- rectangle->SetFillMode( parseFillMode( aReader, line, &line ) );
-
- return rectangle;
-}
-
-
-LIB_PIN* SCH_LEGACY_PLUGIN_CACHE::loadPin( std::unique_ptr& aSymbol,
- LINE_READER& aReader )
-{
- const char* line = aReader.Line();
-
- wxCHECK_MSG( strCompare( "X", line, &line ), nullptr, "Invalid LIB_PIN definition" );
-
- wxString name;
- wxString number;
-
- size_t pos = 2; // "X" plus ' ' space character.
- wxString tmp;
- wxString utf8Line = wxString::FromUTF8( line );
- wxStringTokenizer tokens( utf8Line, " \r\n\t" );
-
- if( tokens.CountTokens() < 11 )
- SCH_PARSE_ERROR( "invalid pin definition", aReader, line );
-
- tmp = tokens.GetNextToken();
- name = tmp;
- pos += tmp.size() + 1;
-
- tmp = tokens.GetNextToken();
- number = tmp ;
- pos += tmp.size() + 1;
-
- long num;
- VECTOR2I position;
-
- tmp = tokens.GetNextToken();
-
- if( !tmp.ToLong( &num ) )
- THROW_PARSE_ERROR( "invalid pin X coordinate", aReader.GetSource(), aReader.Line(),
- aReader.LineNumber(), pos );
-
- pos += tmp.size() + 1;
- position.x = Mils2Iu( (int) num );
-
- tmp = tokens.GetNextToken();
-
- if( !tmp.ToLong( &num ) )
- THROW_PARSE_ERROR( "invalid pin Y coordinate", aReader.GetSource(), aReader.Line(),
- aReader.LineNumber(), pos );
-
- pos += tmp.size() + 1;
- position.y = Mils2Iu( (int) num );
-
- tmp = tokens.GetNextToken();
-
- if( !tmp.ToLong( &num ) )
- THROW_PARSE_ERROR( "invalid pin length", aReader.GetSource(), aReader.Line(),
- aReader.LineNumber(), pos );
-
- pos += tmp.size() + 1;
- int length = Mils2Iu( (int) num );
-
-
- tmp = tokens.GetNextToken();
-
- if( tmp.size() > 1 )
- THROW_PARSE_ERROR( "invalid pin orientation", aReader.GetSource(), aReader.Line(),
- aReader.LineNumber(), pos );
-
- pos += tmp.size() + 1;
- int orientation = tmp[0];
-
- tmp = tokens.GetNextToken();
-
- if( !tmp.ToLong( &num ) )
- THROW_PARSE_ERROR( "invalid pin number text size", aReader.GetSource(), aReader.Line(),
- aReader.LineNumber(), pos );
-
- pos += tmp.size() + 1;
- int numberTextSize = Mils2Iu( (int) num );
-
- tmp = tokens.GetNextToken();
-
- if( !tmp.ToLong( &num ) )
- THROW_PARSE_ERROR( "invalid pin name text size", aReader.GetSource(), aReader.Line(),
- aReader.LineNumber(), pos );
-
- pos += tmp.size() + 1;
- int nameTextSize = Mils2Iu( (int) num );
-
- tmp = tokens.GetNextToken();
-
- if( !tmp.ToLong( &num ) )
- THROW_PARSE_ERROR( "invalid pin unit", aReader.GetSource(), aReader.Line(),
- aReader.LineNumber(), pos );
-
- pos += tmp.size() + 1;
- int unit = (int) num;
-
- tmp = tokens.GetNextToken();
-
- if( !tmp.ToLong( &num ) )
- THROW_PARSE_ERROR( "invalid pin alternate body type", aReader.GetSource(), aReader.Line(),
- aReader.LineNumber(), pos );
-
- pos += tmp.size() + 1;
- int convert = (int) num;
-
- tmp = tokens.GetNextToken();
-
- if( tmp.size() != 1 )
- THROW_PARSE_ERROR( "invalid pin type", aReader.GetSource(), aReader.Line(),
- aReader.LineNumber(), pos );
-
- pos += tmp.size() + 1;
- char type = tmp[0];
- ELECTRICAL_PINTYPE pinType;
-
- switch( type )
- {
- case 'I': pinType = ELECTRICAL_PINTYPE::PT_INPUT; break;
- case 'O': pinType = ELECTRICAL_PINTYPE::PT_OUTPUT; break;
- case 'B': pinType = ELECTRICAL_PINTYPE::PT_BIDI; break;
- case 'T': pinType = ELECTRICAL_PINTYPE::PT_TRISTATE; break;
- case 'P': pinType = ELECTRICAL_PINTYPE::PT_PASSIVE; break;
- case 'U': pinType = ELECTRICAL_PINTYPE::PT_UNSPECIFIED; break;
- case 'W': pinType = ELECTRICAL_PINTYPE::PT_POWER_IN; break;
- case 'w': pinType = ELECTRICAL_PINTYPE::PT_POWER_OUT; break;
- case 'C': pinType = ELECTRICAL_PINTYPE::PT_OPENCOLLECTOR; break;
- case 'E': pinType = ELECTRICAL_PINTYPE::PT_OPENEMITTER; break;
- case 'N': pinType = ELECTRICAL_PINTYPE::PT_NC; break;
- default:
- THROW_PARSE_ERROR( "unknown pin type", aReader.GetSource(), aReader.Line(),
- aReader.LineNumber(), pos );
- }
-
-
- LIB_PIN* pin = new LIB_PIN( aSymbol.get(),
- ConvertToNewOverbarNotation( name ),
- ConvertToNewOverbarNotation( number ),
- orientation,
- pinType,
- length,
- nameTextSize,
- numberTextSize,
- convert,
- position,
- unit );
-
- // Optional
- if( tokens.HasMoreTokens() ) /* Special Symbol defined */
- {
- tmp = tokens.GetNextToken();
-
- enum
- {
- INVERTED = 1 << 0,
- CLOCK = 1 << 1,
- LOWLEVEL_IN = 1 << 2,
- LOWLEVEL_OUT = 1 << 3,
- FALLING_EDGE = 1 << 4,
- NONLOGIC = 1 << 5
- };
-
- int flags = 0;
-
- for( int j = tmp.size(); j > 0; )
- {
- switch( tmp[--j].GetValue() )
- {
- case '~': break;
- case 'N': pin->SetVisible( false ); break;
- case 'I': flags |= INVERTED; break;
- case 'C': flags |= CLOCK; break;
- case 'L': flags |= LOWLEVEL_IN; break;
- case 'V': flags |= LOWLEVEL_OUT; break;
- case 'F': flags |= FALLING_EDGE; break;
- case 'X': flags |= NONLOGIC; break;
- default: THROW_PARSE_ERROR( "invalid pin attribut", aReader.GetSource(),
- aReader.Line(), aReader.LineNumber(), pos );
- }
-
- pos += 1;
- }
-
- switch( flags )
- {
- case 0: pin->SetShape( GRAPHIC_PINSHAPE::LINE ); break;
- case INVERTED: pin->SetShape( GRAPHIC_PINSHAPE::INVERTED ); break;
- case CLOCK: pin->SetShape( GRAPHIC_PINSHAPE::CLOCK ); break;
- case INVERTED | CLOCK: pin->SetShape( GRAPHIC_PINSHAPE::INVERTED_CLOCK ); break;
- case LOWLEVEL_IN: pin->SetShape( GRAPHIC_PINSHAPE::INPUT_LOW ); break;
- case LOWLEVEL_IN | CLOCK: pin->SetShape( GRAPHIC_PINSHAPE::CLOCK_LOW ); break;
- case LOWLEVEL_OUT: pin->SetShape( GRAPHIC_PINSHAPE::OUTPUT_LOW ); break;
- case FALLING_EDGE: pin->SetShape( GRAPHIC_PINSHAPE::FALLING_EDGE_CLOCK ); break;
- case NONLOGIC: pin->SetShape( GRAPHIC_PINSHAPE::NONLOGIC ); break;
- default:
- SCH_PARSE_ERROR( "pin attributes do not define a valid pin shape", aReader, line );
- }
- }
-
- return pin;
-}
-
-
-LIB_SHAPE* SCH_LEGACY_PLUGIN_CACHE::loadPolyLine( std::unique_ptr& aSymbol,
- LINE_READER& aReader )
-{
- const char* line = aReader.Line();
-
- wxCHECK_MSG( strCompare( "P", line, &line ), nullptr, "Invalid poly definition" );
-
- LIB_SHAPE* polyLine = new LIB_SHAPE( aSymbol.get(), SHAPE_T::POLY );
-
- int points = parseInt( aReader, line, &line );
- polyLine->SetUnit( parseInt( aReader, line, &line ) );
- polyLine->SetConvert( parseInt( aReader, line, &line ) );
- polyLine->SetStroke( STROKE_PARAMS( Mils2Iu( parseInt( aReader, line, &line ) ),
- PLOT_DASH_TYPE::SOLID ) );
-
- VECTOR2I pt;
-
- for( int i = 0; i < points; i++ )
- {
- pt.x = Mils2Iu( parseInt( aReader, line, &line ) );
- pt.y = Mils2Iu( parseInt( aReader, line, &line ) );
- polyLine->AddPoint( pt );
- }
-
- if( *line != 0 )
- polyLine->SetFillMode( parseFillMode( aReader, line, &line ) );
-
- return polyLine;
-}
-
-
-LIB_SHAPE* SCH_LEGACY_PLUGIN_CACHE::loadBezier( std::unique_ptr& aSymbol,
- LINE_READER& aReader )
-{
- const char* line = aReader.Line();
-
- wxCHECK_MSG( strCompare( "B", line, &line ), nullptr, "Invalid Bezier definition" );
-
- int points = parseInt( aReader, line, &line );
-
- wxCHECK_MSG( points == 4, NULL, "Invalid Bezier curve definition" );
-
- LIB_SHAPE* bezier = new LIB_SHAPE( aSymbol.get(), SHAPE_T::BEZIER );
-
- bezier->SetUnit( parseInt( aReader, line, &line ) );
- bezier->SetConvert( parseInt( aReader, line, &line ) );
- bezier->SetStroke( STROKE_PARAMS( Mils2Iu( parseInt( aReader, line, &line ) ),
- PLOT_DASH_TYPE::SOLID ) );
-
- bezier->SetStart( VECTOR2I( Mils2Iu( parseInt( aReader, line, &line ) ),
- Mils2Iu( parseInt( aReader, line, &line ) ) ) );
-
- bezier->SetBezierC1( VECTOR2I( Mils2Iu( parseInt( aReader, line, &line ) ),
- Mils2Iu( parseInt( aReader, line, &line ) ) ) );
-
- bezier->SetBezierC2( VECTOR2I( Mils2Iu( parseInt( aReader, line, &line ) ),
- Mils2Iu( parseInt( aReader, line, &line ) ) ) );
-
- bezier->SetEnd( VECTOR2I( Mils2Iu( parseInt( aReader, line, &line ) ),
- Mils2Iu( parseInt( aReader, line, &line ) ) ) );
-
- bezier->RebuildBezierToSegmentsPointsList( bezier->GetWidth() );
-
- if( *line != 0 )
- bezier->SetFillMode( parseFillMode( aReader, line, &line ) );
-
- return bezier;
-}
-
-
-void SCH_LEGACY_PLUGIN_CACHE::loadFootprintFilters( std::unique_ptr& aSymbol,
- LINE_READER& aReader )
-{
- const char* line = aReader.Line();
-
- wxCHECK_RET( strCompare( "$FPLIST", line, &line ), "Invalid footprint filter list" );
-
- line = aReader.ReadLine();
-
- wxArrayString footprintFilters;
-
- while( line )
- {
- if( strCompare( "$ENDFPLIST", line, &line ) )
- {
- aSymbol->SetFPFilters( footprintFilters );
- return;
- }
-
- wxString footprint;
-
- parseUnquotedString( footprint, aReader, line, &line );
- footprintFilters.Add( footprint );
- line = aReader.ReadLine();
- }
-
- SCH_PARSE_ERROR( "File ended prematurely while loading footprint filters.", aReader, line );
-}
-
-
-void SCH_LEGACY_PLUGIN_CACHE::Save( bool aSaveDocFile )
-{
- if( !m_isModified )
- return;
-
- // Write through symlinks, don't replace them
- wxFileName fn = GetRealFile();
-
- auto formatter = std::make_unique( fn.GetFullPath() );
- formatter->Print( 0, "%s %d.%d\n", LIBFILE_IDENT, LIB_VERSION_MAJOR, LIB_VERSION_MINOR );
- formatter->Print( 0, "#encoding utf-8\n");
-
- for( LIB_SYMBOL_MAP::iterator it = m_symbols.begin(); it != m_symbols.end(); it++ )
- {
- if( !it->second->IsRoot() )
- continue;
-
- SaveSymbol( it->second, *formatter.get(), &m_symbols );
- }
-
- formatter->Print( 0, "#\n#End Library\n" );
- formatter.reset();
-
- m_fileModTime = fn.GetModificationTime();
- m_isModified = false;
-
- if( aSaveDocFile )
- saveDocFile();
-}
-
-
-void SCH_LEGACY_PLUGIN_CACHE::SaveSymbol( LIB_SYMBOL* aSymbol, OUTPUTFORMATTER& aFormatter,
- LIB_SYMBOL_MAP* aMap )
-{
- /*
- * NB:
- * Some of the rescue code still uses the legacy format as an intermediary, so we have
- * to keep this code.
- */
-
- wxCHECK_RET( aSymbol && aSymbol->IsRoot(), "Invalid LIB_SYMBOL pointer." );
-
- // LIB_ALIAS objects are deprecated but we still need to gather up the derived symbols
- // and save their names for the old file format.
- wxArrayString aliasNames;
-
- if( aMap )
- {
- for( auto entry : *aMap )
- {
- LIB_SYMBOL* symbol = entry.second;
-
- if( symbol->IsAlias() && symbol->GetParent().lock() == aSymbol->SharedPtr() )
- aliasNames.Add( symbol->GetName() );
- }
- }
-
- LIB_FIELD& value = aSymbol->GetValueField();
-
- // First line: it s a comment (symbol name for readers)
- aFormatter.Print( 0, "#\n# %s\n#\n", TO_UTF8( value.GetText() ) );
-
- // Save data
- aFormatter.Print( 0, "DEF" );
- aFormatter.Print( 0, " %s", TO_UTF8( value.GetText() ) );
-
- LIB_FIELD& reference = aSymbol->GetReferenceField();
-
- if( !reference.GetText().IsEmpty() )
- aFormatter.Print( 0, " %s", TO_UTF8( reference.GetText() ) );
- else
- aFormatter.Print( 0, " ~" );
-
- aFormatter.Print( 0, " %d %d %c %c %d %c %c\n",
- 0, Iu2Mils( aSymbol->GetPinNameOffset() ),
- aSymbol->ShowPinNumbers() ? 'Y' : 'N',
- aSymbol->ShowPinNames() ? 'Y' : 'N',
- aSymbol->GetUnitCount(), aSymbol->UnitsLocked() ? 'L' : 'F',
- aSymbol->IsPower() ? 'P' : 'N' );
-
- timestamp_t dateModified = aSymbol->GetLastModDate();
-
- if( dateModified != 0 )
- {
- int sec = dateModified & 63;
- int min = ( dateModified >> 6 ) & 63;
- int hour = ( dateModified >> 12 ) & 31;
- int day = ( dateModified >> 17 ) & 31;
- int mon = ( dateModified >> 22 ) & 15;
- int year = ( dateModified >> 26 ) + 1990;
-
- aFormatter.Print( 0, "Ti %d/%d/%d %d:%d:%d\n", year, mon, day, hour, min, sec );
- }
-
- std::vector fields;
- aSymbol->GetFields( fields );
-
- // Mandatory fields:
- // may have their own save policy so there is a separate loop for them.
- // Empty fields are saved, because the user may have set visibility,
- // size and orientation
- for( int i = 0; i < MANDATORY_FIELDS; ++i )
- saveField( fields[i], aFormatter );
-
- // User defined fields:
- // may have their own save policy so there is a separate loop for them.
- int fieldId = MANDATORY_FIELDS; // really wish this would go away.
-
- for( unsigned i = MANDATORY_FIELDS; i < fields.size(); ++i )
- {
- // There is no need to save empty fields, i.e. no reason to preserve field
- // names now that fields names come in dynamically through the template
- // fieldnames.
- if( !fields[i]->GetText().IsEmpty() )
- {
- fields[i]->SetId( fieldId++ );
- saveField( fields[i], aFormatter );
- }
- }
-
- // Save the alias list: a line starting by "ALIAS".
- if( !aliasNames.IsEmpty() )
- {
- aFormatter.Print( 0, "ALIAS" );
-
- for( unsigned i = 0; i < aliasNames.GetCount(); i++ )
- aFormatter.Print( 0, " %s", TO_UTF8( aliasNames[i] ) );
-
- aFormatter.Print( 0, "\n" );
- }
-
- wxArrayString footprints = aSymbol->GetFPFilters();
-
- // Write the footprint filter list
- if( footprints.GetCount() != 0 )
- {
- aFormatter.Print( 0, "$FPLIST\n" );
-
- for( unsigned i = 0; i < footprints.GetCount(); i++ )
- aFormatter.Print( 0, " %s\n", TO_UTF8( footprints[i] ) );
-
- aFormatter.Print( 0, "$ENDFPLIST\n" );
- }
-
- // Save graphics items (including pins)
- if( !aSymbol->GetDrawItems().empty() )
- {
- // Sort the draw items in order to editing a file editing by hand.
- aSymbol->GetDrawItems().sort();
-
- aFormatter.Print( 0, "DRAW\n" );
-
- for( LIB_ITEM& item : aSymbol->GetDrawItems() )
- {
- switch( item.Type() )
- {
- default:
- case LIB_FIELD_T: /* Fields have already been saved above. */ break;
- case LIB_PIN_T: savePin( (LIB_PIN* ) &item, aFormatter ); break;
- case LIB_TEXT_T: saveText( ( LIB_TEXT* ) &item, aFormatter ); break;
- case LIB_SHAPE_T:
- {
- LIB_SHAPE& shape = static_cast( item );
-
- switch( shape.GetShape() )
- {
- case SHAPE_T::ARC: saveArc( &shape, aFormatter ); break;
- case SHAPE_T::BEZIER: saveBezier( &shape, aFormatter ); break;
- case SHAPE_T::CIRCLE: saveCircle( &shape, aFormatter ); break;
- case SHAPE_T::POLY: savePolyLine( &shape, aFormatter ); break;
- case SHAPE_T::RECT: saveRectangle( &shape, aFormatter ); break;
- default: break;
- }
- }
- }
- }
-
- aFormatter.Print( 0, "ENDDRAW\n" );
- }
-
- aFormatter.Print( 0, "ENDDEF\n" );
-}
-
-
-void SCH_LEGACY_PLUGIN_CACHE::saveArc( LIB_SHAPE* aArc, OUTPUTFORMATTER& aFormatter )
-{
- wxCHECK_RET( aArc && aArc->GetShape() == SHAPE_T::ARC, "Invalid ARC object." );
-
- EDA_ANGLE startAngle, endAngle;
-
- aArc->CalcArcAngles( startAngle, endAngle );
- startAngle.Normalize180();
- endAngle.Normalize180();
-
- aFormatter.Print( 0, "A %d %d %d %d %d %d %d %d %c %d %d %d %d\n",
- Iu2Mils( aArc->GetPosition().x ),
- Iu2Mils( aArc->GetPosition().y ),
- Iu2Mils( aArc->GetRadius() ),
- startAngle.AsTenthsOfADegree(),
- endAngle.AsTenthsOfADegree(),
- aArc->GetUnit(),
- aArc->GetConvert(),
- Iu2Mils( aArc->GetWidth() ),
- fill_tab[ static_cast( aArc->GetFillMode() ) - 1 ],
- Iu2Mils( aArc->GetStart().x ),
- Iu2Mils( aArc->GetStart().y ),
- Iu2Mils( aArc->GetEnd().x ),
- Iu2Mils( aArc->GetEnd().y ) );
-}
-
-
-void SCH_LEGACY_PLUGIN_CACHE::saveBezier( LIB_SHAPE* aBezier, OUTPUTFORMATTER& aFormatter )
-{
- wxCHECK_RET( aBezier && aBezier->GetShape() == SHAPE_T::BEZIER, "Invalid BEZIER object." );
-
- aFormatter.Print( 0, "B %u %d %d %d",
- (unsigned)aBezier->GetBezierPoints().size(),
- aBezier->GetUnit(),
- aBezier->GetConvert(),
- Iu2Mils( aBezier->GetWidth() ) );
-
- for( const VECTOR2I& pt : aBezier->GetBezierPoints() )
- aFormatter.Print( 0, " %d %d", Iu2Mils( pt.x ), Iu2Mils( pt.y ) );
-
- aFormatter.Print( 0, " %c\n", fill_tab[ static_cast( aBezier->GetFillMode() ) - 1 ] );
-}
-
-
-void SCH_LEGACY_PLUGIN_CACHE::saveCircle( LIB_SHAPE* aCircle, OUTPUTFORMATTER& aFormatter )
-{
- wxCHECK_RET( aCircle && aCircle->GetShape() == SHAPE_T::CIRCLE, "Invalid CIRCLE object." );
-
- aFormatter.Print( 0, "C %d %d %d %d %d %d %c\n",
- Iu2Mils( aCircle->GetPosition().x ),
- Iu2Mils( aCircle->GetPosition().y ),
- Iu2Mils( aCircle->GetRadius() ),
- aCircle->GetUnit(),
- aCircle->GetConvert(),
- Iu2Mils( aCircle->GetWidth() ),
- fill_tab[ static_cast( aCircle->GetFillMode() ) - 1 ] );
-}
-
-
-void SCH_LEGACY_PLUGIN_CACHE::saveField( const LIB_FIELD* aField, OUTPUTFORMATTER& aFormatter )
-{
- wxCHECK_RET( aField && aField->Type() == LIB_FIELD_T, "Invalid LIB_FIELD object." );
-
- int hjustify, vjustify;
- int id = aField->GetId();
- wxString text = aField->GetText();
-
- hjustify = 'C';
-
- if( aField->GetHorizJustify() == GR_TEXT_H_ALIGN_LEFT )
- hjustify = 'L';
- else if( aField->GetHorizJustify() == GR_TEXT_H_ALIGN_RIGHT )
- hjustify = 'R';
-
- vjustify = 'C';
-
- if( aField->GetVertJustify() == GR_TEXT_V_ALIGN_BOTTOM )
- vjustify = 'B';
- else if( aField->GetVertJustify() == GR_TEXT_V_ALIGN_TOP )
- vjustify = 'T';
-
- aFormatter.Print( 0, "F%d %s %d %d %d %c %c %c %c%c%c",
- id,
- EscapedUTF8( text ).c_str(), // wraps in quotes
- Iu2Mils( aField->GetTextPos().x ),
- Iu2Mils( aField->GetTextPos().y ),
- Iu2Mils( aField->GetTextWidth() ),
- aField->GetTextAngle().IsHorizontal() ? 'H' : 'V',
- aField->IsVisible() ? 'V' : 'I',
- hjustify, vjustify,
- aField->IsItalic() ? 'I' : 'N',
- aField->IsBold() ? 'B' : 'N' );
-
- /* Save field name, if necessary
- * Field name is saved only if it is not the default name.
- * Just because default name depends on the language and can change from
- * a country to another
- */
- wxString defName = TEMPLATE_FIELDNAME::GetDefaultFieldName( id );
-
- if( id >= MANDATORY_FIELDS && !aField->m_name.IsEmpty() && aField->m_name != defName )
- aFormatter.Print( 0, " %s", EscapedUTF8( aField->m_name ).c_str() );
-
- aFormatter.Print( 0, "\n" );
-}
-
-
-void SCH_LEGACY_PLUGIN_CACHE::savePin( const LIB_PIN* aPin, OUTPUTFORMATTER& aFormatter )
-{
- wxCHECK_RET( aPin && aPin->Type() == LIB_PIN_T, "Invalid LIB_PIN object." );
-
- int Etype;
-
- switch( aPin->GetType() )
- {
- default:
- case ELECTRICAL_PINTYPE::PT_INPUT: Etype = 'I'; break;
- case ELECTRICAL_PINTYPE::PT_OUTPUT: Etype = 'O'; break;
- case ELECTRICAL_PINTYPE::PT_BIDI: Etype = 'B'; break;
- case ELECTRICAL_PINTYPE::PT_TRISTATE: Etype = 'T'; break;
- case ELECTRICAL_PINTYPE::PT_PASSIVE: Etype = 'P'; break;
- case ELECTRICAL_PINTYPE::PT_UNSPECIFIED: Etype = 'U'; break;
- case ELECTRICAL_PINTYPE::PT_POWER_IN: Etype = 'W'; break;
- case ELECTRICAL_PINTYPE::PT_POWER_OUT: Etype = 'w'; break;
- case ELECTRICAL_PINTYPE::PT_OPENCOLLECTOR: Etype = 'C'; break;
- case ELECTRICAL_PINTYPE::PT_OPENEMITTER: Etype = 'E'; break;
- case ELECTRICAL_PINTYPE::PT_NC: Etype = 'N'; break;
- }
-
- if( !aPin->GetName().IsEmpty() )
- aFormatter.Print( 0, "X %s", TO_UTF8( aPin->GetName() ) );
- else
- aFormatter.Print( 0, "X ~" );
-
- aFormatter.Print( 0, " %s %d %d %d %c %d %d %d %d %c",
- aPin->GetNumber().IsEmpty() ? "~" : TO_UTF8( aPin->GetNumber() ),
- Iu2Mils( aPin->GetPosition().x ),
- Iu2Mils( aPin->GetPosition().y ),
- Iu2Mils( (int) aPin->GetLength() ),
- (int) aPin->GetOrientation(),
- Iu2Mils( aPin->GetNumberTextSize() ),
- Iu2Mils( aPin->GetNameTextSize() ),
- aPin->GetUnit(),
- aPin->GetConvert(),
- Etype );
-
- if( aPin->GetShape() != GRAPHIC_PINSHAPE::LINE || !aPin->IsVisible() )
- aFormatter.Print( 0, " " );
-
- if( !aPin->IsVisible() )
- aFormatter.Print( 0, "N" );
-
- switch( aPin->GetShape() )
- {
- case GRAPHIC_PINSHAPE::LINE: break;
- case GRAPHIC_PINSHAPE::INVERTED: aFormatter.Print( 0, "I" ); break;
- case GRAPHIC_PINSHAPE::CLOCK: aFormatter.Print( 0, "C" ); break;
- case GRAPHIC_PINSHAPE::INVERTED_CLOCK: aFormatter.Print( 0, "IC" ); break;
- case GRAPHIC_PINSHAPE::INPUT_LOW: aFormatter.Print( 0, "L" ); break;
- case GRAPHIC_PINSHAPE::CLOCK_LOW: aFormatter.Print( 0, "CL" ); break;
- case GRAPHIC_PINSHAPE::OUTPUT_LOW: aFormatter.Print( 0, "V" ); break;
- case GRAPHIC_PINSHAPE::FALLING_EDGE_CLOCK: aFormatter.Print( 0, "F" ); break;
- case GRAPHIC_PINSHAPE::NONLOGIC: aFormatter.Print( 0, "X" ); break;
- default: wxFAIL_MSG( "Invalid pin shape" );
- }
-
- aFormatter.Print( 0, "\n" );
-
- const_cast( aPin )->ClearFlags( IS_CHANGED );
-}
-
-
-void SCH_LEGACY_PLUGIN_CACHE::savePolyLine( LIB_SHAPE* aPolyLine, OUTPUTFORMATTER& aFormatter )
-{
- wxCHECK_RET( aPolyLine && aPolyLine->GetShape() == SHAPE_T::POLY, "Invalid POLY object." );
-
- aFormatter.Print( 0, "P %d %d %d %d",
- (int) aPolyLine->GetPolyShape().Outline( 0 ).GetPointCount(),
- aPolyLine->GetUnit(),
- aPolyLine->GetConvert(),
- Iu2Mils( aPolyLine->GetWidth() ) );
-
- for( const VECTOR2I& pt : aPolyLine->GetPolyShape().Outline( 0 ).CPoints() )
- aFormatter.Print( 0, " %d %d", Iu2Mils( pt.x ), Iu2Mils( pt.y ) );
-
- aFormatter.Print( 0, " %c\n", fill_tab[ static_cast( aPolyLine->GetFillMode() ) - 1 ] );
-}
-
-
-void SCH_LEGACY_PLUGIN_CACHE::saveRectangle( LIB_SHAPE* aRectangle, OUTPUTFORMATTER& aFormatter )
-{
- wxCHECK_RET( aRectangle && aRectangle->GetShape() == SHAPE_T::RECT, "Invalid RECT object." );
-
- aFormatter.Print( 0, "S %d %d %d %d %d %d %d %c\n",
- Iu2Mils( aRectangle->GetPosition().x ),
- Iu2Mils( aRectangle->GetPosition().y ),
- Iu2Mils( aRectangle->GetEnd().x ),
- Iu2Mils( aRectangle->GetEnd().y ),
- aRectangle->GetUnit(),
- aRectangle->GetConvert(),
- Iu2Mils( aRectangle->GetWidth() ),
- fill_tab[ static_cast( aRectangle->GetFillMode() ) - 1 ] );
-}
-
-
-void SCH_LEGACY_PLUGIN_CACHE::saveText( const LIB_TEXT* aText, OUTPUTFORMATTER& aFormatter )
-{
- wxCHECK_RET( aText && aText->Type() == LIB_TEXT_T, "Invalid LIB_TEXT object." );
-
- wxString text = aText->GetText();
-
- if( text.Contains( wxT( " " ) ) || text.Contains( wxT( "~" ) ) || text.Contains( wxT( "\"" ) ) )
- {
- // convert double quote to similar-looking two apostrophes
- text.Replace( wxT( "\"" ), wxT( "''" ) );
- text = wxT( "\"" ) + text + wxT( "\"" );
- }
-
- aFormatter.Print( 0, "T %g %d %d %d %d %d %d %s",
- (double) aText->GetTextAngle().AsTenthsOfADegree(),
- Iu2Mils( aText->GetTextPos().x ),
- Iu2Mils( aText->GetTextPos().y ),
- Iu2Mils( aText->GetTextWidth() ),
- !aText->IsVisible(),
- aText->GetUnit(),
- aText->GetConvert(),
- TO_UTF8( text ) );
-
- aFormatter.Print( 0, " %s %d", aText->IsItalic() ? "Italic" : "Normal", aText->IsBold() );
-
- char hjustify = 'C';
-
- if( aText->GetHorizJustify() == GR_TEXT_H_ALIGN_LEFT )
- hjustify = 'L';
- else if( aText->GetHorizJustify() == GR_TEXT_H_ALIGN_RIGHT )
- hjustify = 'R';
-
- char vjustify = 'C';
-
- if( aText->GetVertJustify() == GR_TEXT_V_ALIGN_BOTTOM )
- vjustify = 'B';
- else if( aText->GetVertJustify() == GR_TEXT_V_ALIGN_TOP )
- vjustify = 'T';
-
- aFormatter.Print( 0, " %c %c\n", hjustify, vjustify );
-}
-
-
-void SCH_LEGACY_PLUGIN_CACHE::saveDocFile()
-{
- /*
- * NB:
- * Some of the rescue code still uses the legacy format as an intermediary, so we have
- * to keep this code.
- */
-
- wxFileName fileName = m_libFileName;
-
- fileName.SetExt( LegacySymbolDocumentFileExtension );
- FILE_OUTPUTFORMATTER formatter( fileName.GetFullPath() );
-
- formatter.Print( 0, "%s\n", DOCFILE_IDENT );
-
- for( LIB_SYMBOL_MAP::iterator it = m_symbols.begin(); it != m_symbols.end(); ++it )
- {
- wxString description = it->second->GetDescription();
- wxString keyWords = it->second->GetKeyWords();
- wxString docFileName = it->second->GetDatasheetField().GetText();
-
- if( description.IsEmpty() && keyWords.IsEmpty() && docFileName.IsEmpty() )
- continue;
-
- formatter.Print( 0, "#\n$CMP %s\n", TO_UTF8( it->second->GetName() ) );
-
- if( !description.IsEmpty() )
- formatter.Print( 0, "D %s\n", TO_UTF8( description ) );
-
- if( !keyWords.IsEmpty() )
- formatter.Print( 0, "K %s\n", TO_UTF8( keyWords ) );
-
- if( !docFileName.IsEmpty() )
- formatter.Print( 0, "F %s\n", TO_UTF8( docFileName ) );
-
- formatter.Print( 0, "$ENDCMP\n" );
- }
-
- formatter.Print( 0, "#\n#End Doc Library\n" );
-}
-
-
-void SCH_LEGACY_PLUGIN_CACHE::DeleteSymbol( const wxString& aSymbolName )
-{
- LIB_SYMBOL_MAP::iterator it = m_symbols.find( aSymbolName );
-
- if( it == m_symbols.end() )
- THROW_IO_ERROR( wxString::Format( _( "library %s does not contain a symbol named %s" ),
- m_libFileName.GetFullName(), aSymbolName ) );
-
- LIB_SYMBOL* symbol = it->second;
-
- if( symbol->IsRoot() )
- {
- LIB_SYMBOL* rootSymbol = symbol;
-
- // Remove the root symbol and all its children.
- m_symbols.erase( it );
-
- LIB_SYMBOL_MAP::iterator it1 = m_symbols.begin();
-
- while( it1 != m_symbols.end() )
- {
- if( it1->second->IsAlias()
- && it1->second->GetParent().lock() == rootSymbol->SharedPtr() )
- {
- delete it1->second;
- it1 = m_symbols.erase( it1 );
- }
- else
- {
- it1++;
- }
- }
-
- delete rootSymbol;
- }
- else
- {
- // Just remove the alias.
- m_symbols.erase( it );
- delete symbol;
- }
-
- SCH_LEGACY_PLUGIN_CACHE::IncrementModifyHash();
- m_isModified = true;
-}
-
-
void SCH_LEGACY_PLUGIN::cacheLib( const wxString& aLibraryFileName, const PROPERTIES* aProperties )
{
if( !m_cache || !m_cache->IsFile( aLibraryFileName ) || m_cache->IsFileChanged() )
diff --git a/eeschema/sch_plugins/legacy/sch_legacy_plugin_helpers.h b/eeschema/sch_plugins/legacy/sch_legacy_plugin_helpers.h
index a241485110..17d2a2a654 100644
--- a/eeschema/sch_plugins/legacy/sch_legacy_plugin_helpers.h
+++ b/eeschema/sch_plugins/legacy/sch_legacy_plugin_helpers.h
@@ -27,6 +27,8 @@
THROW_PARSE_ERROR( text, reader.GetSource(), reader.Line(), \
reader.LineNumber(), pos - reader.Line() )
+#define Mils2Iu( x ) Mils2iu( x )
+
class LINE_READER;
class wxString;
diff --git a/eeschema/sch_plugins/sch_lib_plugin_cache.cpp b/eeschema/sch_plugins/sch_lib_plugin_cache.cpp
new file mode 100644
index 0000000000..225f2eb79c
--- /dev/null
+++ b/eeschema/sch_plugins/sch_lib_plugin_cache.cpp
@@ -0,0 +1,176 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2022 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * @author Wayne Stambaugh
+ *
+ * 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, see .
+ */
+
+#include "sch_lib_plugin_cache.h"
+
+#include
+#include
+
+
+int SCH_LIB_PLUGIN_CACHE::m_modHash = 1; // starts at 1 and goes up
+std::mutex SCH_LIB_PLUGIN_CACHE::m_modHashMutex;
+
+
+SCH_LIB_PLUGIN_CACHE::SCH_LIB_PLUGIN_CACHE( const wxString& aFullPathAndFileName ) :
+ m_fileName( aFullPathAndFileName ),
+ m_libFileName( aFullPathAndFileName ),
+ m_isWritable( true ),
+ m_isModified( false )
+{
+ m_libType = SCH_LIB_TYPE::LT_EESCHEMA;
+}
+
+
+SCH_LIB_PLUGIN_CACHE::~SCH_LIB_PLUGIN_CACHE()
+{
+ // When the cache is destroyed, all of the alias objects on the heap should be deleted.
+ for( auto& symbol : m_symbols )
+ delete symbol.second;
+
+ m_symbols.clear();
+}
+
+
+void SCH_LIB_PLUGIN_CACHE::Save( const std::optional& aOpt )
+{
+ wxCHECK( false, /* void */ );
+}
+
+
+wxFileName SCH_LIB_PLUGIN_CACHE::GetRealFile() const
+{
+ wxFileName fn( m_libFileName );
+
+ // If m_libFileName is a symlink follow it to the real source file
+ WX_FILENAME::ResolvePossibleSymlinks( fn );
+ return fn;
+}
+
+
+wxDateTime SCH_LIB_PLUGIN_CACHE::GetLibModificationTime()
+{
+ wxFileName fn = GetRealFile();
+
+ // update the writable flag while we have a wxFileName, in a network this
+ // is possibly quite dynamic anyway.
+ m_isWritable = fn.IsFileWritable();
+
+ return fn.GetModificationTime();
+}
+
+
+bool SCH_LIB_PLUGIN_CACHE::IsFile( const wxString& aFullPathAndFileName ) const
+{
+ return m_fileName == aFullPathAndFileName;
+}
+
+
+bool SCH_LIB_PLUGIN_CACHE::IsFileChanged() const
+{
+ wxFileName fn = GetRealFile();
+
+ if( m_fileModTime.IsValid() && fn.IsOk() && fn.FileExists() )
+ return fn.GetModificationTime() != m_fileModTime;
+
+ return false;
+}
+
+
+LIB_SYMBOL* SCH_LIB_PLUGIN_CACHE::removeSymbol( LIB_SYMBOL* aSymbol )
+{
+ wxCHECK_MSG( aSymbol != nullptr, nullptr, "NULL pointer cannot be removed from library." );
+
+ LIB_SYMBOL* firstChild = nullptr;
+ LIB_SYMBOL_MAP::iterator it = m_symbols.find( aSymbol->GetName() );
+
+ if( it == m_symbols.end() )
+ return nullptr;
+
+ // If the entry pointer doesn't match the name it is mapped to in the library, we
+ // have done something terribly wrong.
+ wxCHECK_MSG( *it->second == aSymbol, nullptr,
+ "Pointer mismatch while attempting to remove alias entry <" + aSymbol->GetName() +
+ "> from library cache <" + m_libFileName.GetName() + ">." );
+
+ // If the symbol is a root symbol used by other symbols find the first alias that uses
+ // the root symbol and make it the new root.
+ if( aSymbol->IsRoot() )
+ {
+ for( auto entry : m_symbols )
+ {
+ if( entry.second->IsAlias()
+ && entry.second->GetParent().lock() == aSymbol->SharedPtr() )
+ {
+ firstChild = entry.second;
+ break;
+ }
+ }
+
+ if( firstChild )
+ {
+ for( LIB_ITEM& drawItem : aSymbol->GetDrawItems() )
+ {
+ if( drawItem.Type() == LIB_FIELD_T )
+ {
+ LIB_FIELD& field = static_cast( drawItem );
+
+ if( firstChild->FindField( field.GetCanonicalName() ) )
+ continue;
+ }
+
+ LIB_ITEM* newItem = (LIB_ITEM*) drawItem.Clone();
+ drawItem.SetParent( firstChild );
+ firstChild->AddDrawItem( newItem );
+ }
+
+ // Reparent the remaining aliases.
+ for( auto entry : m_symbols )
+ {
+ if( entry.second->IsAlias()
+ && entry.second->GetParent().lock() == aSymbol->SharedPtr() )
+ entry.second->SetParent( firstChild );
+ }
+ }
+ }
+
+ m_symbols.erase( it );
+ delete aSymbol;
+ m_isModified = true;
+ SCH_LIB_PLUGIN_CACHE::IncrementModifyHash();
+ return firstChild;
+}
+
+
+void SCH_LIB_PLUGIN_CACHE::AddSymbol( const LIB_SYMBOL* aSymbol )
+{
+ // aSymbol is cloned in SYMBOL_LIB::AddSymbol(). The cache takes ownership of aSymbol.
+ wxString name = aSymbol->GetName();
+ LIB_SYMBOL_MAP::iterator it = m_symbols.find( name );
+
+ if( it != m_symbols.end() )
+ {
+ removeSymbol( it->second );
+ }
+
+ m_symbols[ name ] = const_cast< LIB_SYMBOL* >( aSymbol );
+ m_isModified = true;
+ SCH_LIB_PLUGIN_CACHE::IncrementModifyHash();
+}
diff --git a/eeschema/sch_plugins/sch_lib_plugin_cache.h b/eeschema/sch_plugins/sch_lib_plugin_cache.h
new file mode 100644
index 0000000000..4d7b419b2a
--- /dev/null
+++ b/eeschema/sch_plugins/sch_lib_plugin_cache.h
@@ -0,0 +1,103 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2022 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * @author Wayne Stambaugh
+ *
+ * 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, see .
+ */
+
+#ifndef _SCH_LIB_PLUGIN_CACHE_H_
+#define _SCH_LIB_PLUGIN_CACHE_H_
+
+#include
+#include
+
+#include
+
+#include
+
+
+class LIB_SYMBOL;
+class OUTPUTFORMATTER;
+
+
+/**
+ * A base cache assistant implementation for the symbol library portion of the #SCH_PLUGIN API.
+ */
+class SCH_LIB_PLUGIN_CACHE
+{
+public:
+ SCH_LIB_PLUGIN_CACHE( const wxString& aLibraryPath );
+ virtual ~SCH_LIB_PLUGIN_CACHE();
+
+ static void IncrementModifyHash()
+ {
+ std::lock_guard mut( SCH_LIB_PLUGIN_CACHE::m_modHashMutex );
+ SCH_LIB_PLUGIN_CACHE::m_modHash++;
+ }
+
+ static int GetModifyHash()
+ {
+ std::lock_guard mut( SCH_LIB_PLUGIN_CACHE::m_modHashMutex );
+ return SCH_LIB_PLUGIN_CACHE::m_modHash;
+ }
+
+ // Most all functions in this class throw IO_ERROR exceptions. There are no
+ // error codes nor user interface calls from here, nor in any SCH_PLUGIN objects.
+ // Catch these exceptions higher up please.
+
+ /// Save the entire library to file m_libFileName;
+ virtual void Save( const std::optional& aOpt = std::nullopt );
+
+ virtual void Load() = 0;
+
+ virtual void AddSymbol( const LIB_SYMBOL* aSymbol );
+
+ virtual void DeleteSymbol( const wxString& aName ) = 0;
+
+ // If m_libFileName is a symlink follow it to the real source file
+ wxFileName GetRealFile() const;
+
+ wxDateTime GetLibModificationTime();
+
+ bool IsFile( const wxString& aFullPathAndFileName ) const;
+
+ bool IsFileChanged() const;
+
+ void SetModified( bool aModified = true ) { m_isModified = aModified; }
+
+ wxString GetLogicalName() const { return m_libFileName.GetName(); }
+
+ void SetFileName( const wxString& aFileName ) { m_libFileName = aFileName; }
+
+ wxString GetFileName() const { return m_libFileName.GetFullPath(); }
+
+protected:
+ LIB_SYMBOL* removeSymbol( LIB_SYMBOL* aAlias );
+
+ static int m_modHash; // Keep track of the modification status of the library.
+ static std::mutex m_modHashMutex;
+
+ wxString m_fileName; // Absolute path and file name.
+ wxFileName m_libFileName; // Absolute path and file name is required here.
+ wxDateTime m_fileModTime;
+ LIB_SYMBOL_MAP m_symbols; // Map of names of #LIB_SYMBOL pointers.
+ bool m_isWritable;
+ bool m_isModified;
+ SCH_LIB_TYPE m_libType; // Is this cache a symbol or symbol library.
+};
+
+#endif // _SCH_LIB_PLUGIN_CACHE_H_