1 /*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2016 CERN
5 * Copyright (C) 2016-2021 KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * @author Wayne Stambaugh <stambaughw@gmail.com>
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License
11 * as published by the Free Software Foundation; either version 2
12 * of the License, or (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License along
20 * with this program. If not, see <http://www.gnu.org/licenses/>.
21 */
22
23 #include <algorithm>
24 #include <boost/algorithm/string/join.hpp>
25 #include <cctype>
26 #include <mutex>
27 #include <set>
28
29 #include <wx/mstream.h>
30 #include <wx/filename.h>
31 #include <wx/log.h>
32 #include <wx/textfile.h>
33 #include <wx/tokenzr.h>
34 #include <wx_filename.h> // For ::ResolvePossibleSymlinks()
35
36 #include <kiway.h>
37 #include <string_utils.h>
38 #include <locale_io.h>
39 #include <richio.h>
40 #include <trace_helpers.h>
41 #include <trigo.h>
42 #include <progress_reporter.h>
43 #include <general.h>
44 #include <sch_bitmap.h>
45 #include <sch_bus_entry.h>
46 #include <sch_symbol.h>
47 #include <sch_junction.h>
48 #include <sch_line.h>
49 #include <sch_marker.h>
50 #include <sch_no_connect.h>
51 #include <sch_text.h>
52 #include <sch_sheet.h>
53 #include <sch_sheet_pin.h>
54 #include <bus_alias.h>
55 #include <sch_plugins/legacy/sch_legacy_plugin.h>
56 #include <sch_screen.h>
57 #include <schematic.h>
58 #include <symbol_library.h>
59 #include <lib_shape.h>
60 #include <lib_field.h>
61 #include <lib_pin.h>
62 #include <lib_text.h>
63 #include <eeschema_id.h> // for MAX_UNIT_COUNT_PER_PACKAGE definition
64 #include <tool/selection.h>
65
66
67 #define Mils2Iu( x ) Mils2iu( x )
68
69
70 // Must be the first line of symbol library document (.dcm) files.
71 #define DOCFILE_IDENT "EESchema-DOCLIB Version 2.0"
72
73 #define SCH_PARSE_ERROR( text, reader, pos ) \
74 THROW_PARSE_ERROR( text, reader.GetSource(), reader.Line(), \
75 reader.LineNumber(), pos - reader.Line() )
76
77
78 // Token delimiters.
79 const char* delims = " \t\r\n";
80
81 // Tokens to read/save graphic lines style
82 #define T_STYLE "style"
83 #define T_COLOR "rgb" // cannot be modified (used by wxWidgets)
84 #define T_COLORA "rgba" // cannot be modified (used by wxWidgets)
85 #define T_WIDTH "width"
86
87
is_eol(char c)88 static bool is_eol( char c )
89 {
90 // The default file eol character used internally by KiCad.
91 // |
92 // | Possible eol if someone edited the file by hand on certain platforms.
93 // | |
94 // | | May have gone past eol with strtok().
95 // | | |
96 if( c == '\n' || c == '\r' || c == 0 )
97 return true;
98
99 return false;
100 }
101
102
103 /**
104 * Compare \a aString to the string starting at \a aLine and advances the character point to
105 * the end of \a String and returns the new pointer position in \a aOutput if it is not NULL.
106 *
107 * @param aString - A pointer to the string to compare.
108 * @param aLine - A pointer to string to begin the comparison.
109 * @param aOutput - A pointer to a string pointer to the end of the comparison if not NULL.
110 * @return true if \a aString was found starting at \a aLine. Otherwise false.
111 */
strCompare(const char * aString,const char * aLine,const char ** aOutput=nullptr)112 static bool strCompare( const char* aString, const char* aLine, const char** aOutput = nullptr )
113 {
114 size_t len = strlen( aString );
115 bool retv = ( strncasecmp( aLine, aString, len ) == 0 ) &&
116 ( isspace( aLine[ len ] ) || aLine[ len ] == 0 );
117
118 if( retv && aOutput )
119 {
120 const char* tmp = aLine;
121
122 // Move past the end of the token.
123 tmp += len;
124
125 // Move to the beginning of the next token.
126 while( *tmp && isspace( *tmp ) )
127 tmp++;
128
129 *aOutput = tmp;
130 }
131
132 return retv;
133 }
134
135
136 /**
137 * Parse an ASCII integer string with possible leading whitespace into
138 * an integer and updates the pointer at \a aOutput if it is not NULL, just
139 * like "man strtol()".
140 *
141 * @param aReader - The line reader used to generate exception throw information.
142 * @param aLine - A pointer the current position in a string.
143 * @param aOutput - The pointer to a string pointer to copy the string pointer position when
144 * the parsing is complete.
145 * @return A valid integer value.
146 * @throw An #IO_ERROR on an unexpected end of line.
147 * @throw A #PARSE_ERROR if the parsed token is not a valid integer.
148 */
parseInt(LINE_READER & aReader,const char * aLine,const char ** aOutput=nullptr)149 static int parseInt( LINE_READER& aReader, const char* aLine, const char** aOutput = nullptr )
150 {
151 if( !*aLine )
152 SCH_PARSE_ERROR( _( "unexpected end of line" ), aReader, aLine );
153
154 // Clear errno before calling strtol() in case some other crt call set it.
155 errno = 0;
156
157 long retv = strtol( aLine, (char**) aOutput, 10 );
158
159 // Make sure no error occurred when calling strtol().
160 if( errno == ERANGE )
161 SCH_PARSE_ERROR( "invalid integer value", aReader, aLine );
162
163 // strtol does not strip off whitespace before the next token.
164 if( aOutput )
165 {
166 const char* next = *aOutput;
167
168 while( *next && isspace( *next ) )
169 next++;
170
171 *aOutput = next;
172 }
173
174 return (int) retv;
175 }
176
177
178 /**
179 * Parse an ASCII hex integer string with possible leading whitespace into
180 * a long integer and updates the pointer at \a aOutput if it is not NULL, just
181 * like "man strtoll".
182 *
183 * @param aReader - The line reader used to generate exception throw information.
184 * @param aLine - A pointer the current position in a string.
185 * @param aOutput - The pointer to a string pointer to copy the string pointer position when
186 * the parsing is complete.
187 * @return A valid uint32_t value.
188 * @throw IO_ERROR on an unexpected end of line.
189 * @throw PARSE_ERROR if the parsed token is not a valid integer.
190 */
parseHex(LINE_READER & aReader,const char * aLine,const char ** aOutput=nullptr)191 static uint32_t parseHex( LINE_READER& aReader, const char* aLine, const char** aOutput = nullptr )
192 {
193 if( !*aLine )
194 SCH_PARSE_ERROR( _( "unexpected end of line" ), aReader, aLine );
195
196 // Due to some issues between some files created by a 64 bits version and those
197 // created by a 32 bits version, we use here a temporary at least 64 bits storage:
198 unsigned long long retv;
199
200 // Clear errno before calling strtoull() in case some other crt call set it.
201 errno = 0;
202 retv = strtoull( aLine, (char**) aOutput, 16 );
203
204 // Make sure no error occurred when calling strtoull().
205 if( errno == ERANGE )
206 SCH_PARSE_ERROR( "invalid hexadecimal number", aReader, aLine );
207
208 // Strip off whitespace before the next token.
209 if( aOutput )
210 {
211 const char* next = *aOutput;
212
213 while( *next && isspace( *next ) )
214 next++;
215
216 *aOutput = next;
217 }
218
219 return (uint32_t)retv;
220 }
221
222
223 /**
224 * Parses an ASCII point string with possible leading whitespace into a double precision
225 * floating point number and updates the pointer at \a aOutput if it is not NULL, just
226 * like "man strtod".
227 *
228 * @param aReader - The line reader used to generate exception throw information.
229 * @param aLine - A pointer the current position in a string.
230 * @param aOutput - The pointer to a string pointer to copy the string pointer position when
231 * the parsing is complete.
232 * @return A valid double value.
233 * @throw IO_ERROR on an unexpected end of line.
234 * @throw PARSE_ERROR if the parsed token is not a valid integer.
235 */
parseDouble(LINE_READER & aReader,const char * aLine,const char ** aOutput=nullptr)236 static double parseDouble( LINE_READER& aReader, const char* aLine,
237 const char** aOutput = nullptr )
238 {
239 if( !*aLine )
240 SCH_PARSE_ERROR( _( "unexpected end of line" ), aReader, aLine );
241
242 // Clear errno before calling strtod() in case some other crt call set it.
243 errno = 0;
244
245 double retv = strtod( aLine, (char**) aOutput );
246
247 // Make sure no error occurred when calling strtod().
248 if( errno == ERANGE )
249 SCH_PARSE_ERROR( "invalid floating point number", aReader, aLine );
250
251 // strtod does not strip off whitespace before the next token.
252 if( aOutput )
253 {
254 const char* next = *aOutput;
255
256 while( *next && isspace( *next ) )
257 next++;
258
259 *aOutput = next;
260 }
261
262 return retv;
263 }
264
265
266 /**
267 * Parse a single ASCII character and updates the pointer at \a aOutput if it is not NULL.
268 *
269 * @param aReader - The line reader used to generate exception throw information.
270 * @param aCurrentToken - A pointer the current position in a string.
271 * @param aNextToken - The pointer to a string pointer to copy the string pointer position when
272 * the parsing is complete.
273 * @return A valid ASCII character.
274 * @throw IO_ERROR on an unexpected end of line.
275 * @throw PARSE_ERROR if the parsed token is not a single character token.
276 */
parseChar(LINE_READER & aReader,const char * aCurrentToken,const char ** aNextToken=nullptr)277 static char parseChar( LINE_READER& aReader, const char* aCurrentToken,
278 const char** aNextToken = nullptr )
279 {
280 while( *aCurrentToken && isspace( *aCurrentToken ) )
281 aCurrentToken++;
282
283 if( !*aCurrentToken )
284 SCH_PARSE_ERROR( _( "unexpected end of line" ), aReader, aCurrentToken );
285
286 if( !isspace( *( aCurrentToken + 1 ) ) )
287 SCH_PARSE_ERROR( "expected single character token", aReader, aCurrentToken );
288
289 if( aNextToken )
290 {
291 const char* next = aCurrentToken + 2;
292
293 while( *next && isspace( *next ) )
294 next++;
295
296 *aNextToken = next;
297 }
298
299 return *aCurrentToken;
300 }
301
302
303 /**
304 * Parse an unquoted utf8 string and updates the pointer at \a aOutput if it is not NULL.
305 *
306 * The parsed string must be a continuous string with no white space.
307 *
308 * @param aString - A reference to the parsed string.
309 * @param aReader - The line reader used to generate exception throw information.
310 * @param aCurrentToken - A pointer the current position in a string.
311 * @param aNextToken - The pointer to a string pointer to copy the string pointer position when
312 * the parsing is complete.
313 * @param aCanBeEmpty - True if the parsed string is optional. False if it is mandatory.
314 * @throw IO_ERROR on an unexpected end of line.
315 * @throw PARSE_ERROR if the \a aCanBeEmpty is false and no string was parsed.
316 */
parseUnquotedString(wxString & aString,LINE_READER & aReader,const char * aCurrentToken,const char ** aNextToken=nullptr,bool aCanBeEmpty=false)317 static void parseUnquotedString( wxString& aString, LINE_READER& aReader,
318 const char* aCurrentToken, const char** aNextToken = nullptr,
319 bool aCanBeEmpty = false )
320 {
321 if( !*aCurrentToken )
322 {
323 if( aCanBeEmpty )
324 return;
325 else
326 SCH_PARSE_ERROR( _( "unexpected end of line" ), aReader, aCurrentToken );
327 }
328
329 const char* tmp = aCurrentToken;
330
331 while( *tmp && isspace( *tmp ) )
332 tmp++;
333
334 if( !*tmp )
335 {
336 if( aCanBeEmpty )
337 return;
338 else
339 SCH_PARSE_ERROR( _( "unexpected end of line" ), aReader, aCurrentToken );
340 }
341
342 std::string utf8;
343
344 while( *tmp && !isspace( *tmp ) )
345 utf8 += *tmp++;
346
347 aString = FROM_UTF8( utf8.c_str() );
348
349 if( aString.IsEmpty() && !aCanBeEmpty )
350 SCH_PARSE_ERROR( _( "expected unquoted string" ), aReader, aCurrentToken );
351
352 if( aNextToken )
353 {
354 const char* next = tmp;
355
356 while( *next && isspace( *next ) )
357 next++;
358
359 *aNextToken = next;
360 }
361 }
362
363
364 /**
365 * Parse an quoted ASCII utf8 and updates the pointer at \a aOutput if it is not NULL.
366 *
367 * The parsed string must be contained within a single line. There are no multi-line
368 * quoted strings in the legacy schematic file format.
369 *
370 * @param aString - A reference to the parsed string.
371 * @param aReader - The line reader used to generate exception throw information.
372 * @param aCurrentToken - A pointer the current position in a string.
373 * @param aNextToken - The pointer to a string pointer to copy the string pointer position when
374 * the parsing is complete.
375 * @param aCanBeEmpty - True if the parsed string is optional. False if it is mandatory.
376 * @throw IO_ERROR on an unexpected end of line.
377 * @throw PARSE_ERROR if the \a aCanBeEmpty is false and no string was parsed.
378 */
parseQuotedString(wxString & aString,LINE_READER & aReader,const char * aCurrentToken,const char ** aNextToken=nullptr,bool aCanBeEmpty=false)379 static void parseQuotedString( wxString& aString, LINE_READER& aReader,
380 const char* aCurrentToken, const char** aNextToken = nullptr,
381 bool aCanBeEmpty = false )
382 {
383 if( !*aCurrentToken )
384 {
385 if( aCanBeEmpty )
386 return;
387 else
388 SCH_PARSE_ERROR( _( "unexpected end of line" ), aReader, aCurrentToken );
389 }
390
391 const char* tmp = aCurrentToken;
392
393 while( *tmp && isspace( *tmp ) )
394 tmp++;
395
396 if( !*tmp )
397 {
398 if( aCanBeEmpty )
399 return;
400 else
401 SCH_PARSE_ERROR( _( "unexpected end of line" ), aReader, aCurrentToken );
402 }
403
404 // Verify opening quote.
405 if( *tmp != '"' )
406 SCH_PARSE_ERROR( "expecting opening quote", aReader, aCurrentToken );
407
408 tmp++;
409
410 std::string utf8; // utf8 without escapes and quotes.
411
412 // Fetch everything up to closing quote.
413 while( *tmp )
414 {
415 if( *tmp == '\\' )
416 {
417 tmp++;
418
419 if( !*tmp )
420 SCH_PARSE_ERROR( _( "unexpected end of line" ), aReader, aCurrentToken );
421
422 // Do not copy the escape byte if it is followed by \ or "
423 if( *tmp != '"' && *tmp != '\\' )
424 utf8 += '\\';
425
426 utf8 += *tmp;
427 }
428 else if( *tmp == '"' ) // Closing double quote.
429 {
430 break;
431 }
432 else
433 {
434 utf8 += *tmp;
435 }
436
437 tmp++;
438 }
439
440 aString = FROM_UTF8( utf8.c_str() );
441
442 if( aString.IsEmpty() && !aCanBeEmpty )
443 SCH_PARSE_ERROR( "expected quoted string", aReader, aCurrentToken );
444
445 if( *tmp && *tmp != '"' )
446 SCH_PARSE_ERROR( "no closing quote for string found", aReader, tmp );
447
448 // Move past the closing quote.
449 tmp++;
450
451 if( aNextToken )
452 {
453 const char* next = tmp;
454
455 while( *next == ' ' )
456 next++;
457
458 *aNextToken = next;
459 }
460 }
461
462
463 /**
464 * A cache assistant for the symbol library portion of the #SCH_PLUGIN API, and only for the
465 * #SCH_LEGACY_PLUGIN, so therefore is private to this implementation file, i.e. not placed
466 * into a header.
467 */
468 class SCH_LEGACY_PLUGIN_CACHE
469 {
470 static int s_modHash; // Keep track of the modification status of the library.
471 wxString m_fileName; // Absolute path and file name.
472 wxFileName m_libFileName; // Absolute path and file name is required here.
473 wxDateTime m_fileModTime;
474 LIB_SYMBOL_MAP m_symbols; // Map of names of #LIB_SYMBOL pointers.
475 bool m_isWritable;
476 bool m_isModified;
477 int m_versionMajor;
478 int m_versionMinor;
479 SCH_LIB_TYPE m_libType; // Is this cache a symbol or symbol library.
480
481 void loadHeader( FILE_LINE_READER& aReader );
482 static void loadAliases( std::unique_ptr<LIB_SYMBOL>& aSymbol, LINE_READER& aReader,
483 LIB_SYMBOL_MAP* aMap = nullptr );
484 static void loadField( std::unique_ptr<LIB_SYMBOL>& aSymbol, LINE_READER& aReader );
485 static void loadDrawEntries( std::unique_ptr<LIB_SYMBOL>& aSymbol, LINE_READER& aReader,
486 int aMajorVersion, int aMinorVersion );
487 static void loadFootprintFilters( std::unique_ptr<LIB_SYMBOL>& aSymbol,
488 LINE_READER& aReader );
489 void loadDocs();
490 static LIB_SHAPE* loadArc( std::unique_ptr<LIB_SYMBOL>& aSymbol, LINE_READER& aReader );
491 static LIB_SHAPE* loadCircle( std::unique_ptr<LIB_SYMBOL>& aSymbol, LINE_READER& aReader );
492 static LIB_TEXT* loadText( std::unique_ptr<LIB_SYMBOL>& aSymbol, LINE_READER& aReader,
493 int aMajorVersion, int aMinorVersion );
494 static LIB_SHAPE* loadRect( std::unique_ptr<LIB_SYMBOL>& aSymbol, LINE_READER& aReader );
495 static LIB_PIN* loadPin( std::unique_ptr<LIB_SYMBOL>& aSymbol, LINE_READER& aReader );
496 static LIB_SHAPE* loadPolyLine( std::unique_ptr<LIB_SYMBOL>& aSymbol, LINE_READER& aReader );
497 static LIB_SHAPE* loadBezier( std::unique_ptr<LIB_SYMBOL>& aSymbol, LINE_READER& aReader );
498
499 static FILL_T parseFillMode( LINE_READER& aReader, const char* aLine, const char** aOutput );
500 LIB_SYMBOL* removeSymbol( LIB_SYMBOL* aAlias );
501
502 void saveDocFile();
503 static void saveArc( LIB_SHAPE* aArc, OUTPUTFORMATTER& aFormatter );
504 static void saveBezier( LIB_SHAPE* aBezier, OUTPUTFORMATTER& aFormatter );
505 static void saveCircle( LIB_SHAPE* aCircle, OUTPUTFORMATTER& aFormatter );
506 static void saveField( const LIB_FIELD* aField, OUTPUTFORMATTER& aFormatter );
507 static void savePin( const LIB_PIN* aPin, OUTPUTFORMATTER& aFormatter );
508 static void savePolyLine( LIB_SHAPE* aPolyLine, OUTPUTFORMATTER& aFormatter );
509 static void saveRectangle( LIB_SHAPE* aRectangle, OUTPUTFORMATTER& aFormatter );
510 static void saveText( const LIB_TEXT* aText, OUTPUTFORMATTER& aFormatter );
511
512 friend SCH_LEGACY_PLUGIN;
513
514 static std::mutex s_modHashMutex;
515
516 public:
517 SCH_LEGACY_PLUGIN_CACHE( const wxString& aLibraryPath );
518 ~SCH_LEGACY_PLUGIN_CACHE();
519
IncrementModifyHash()520 static void IncrementModifyHash()
521 {
522 std::lock_guard<std::mutex> mut( SCH_LEGACY_PLUGIN_CACHE::s_modHashMutex );
523 SCH_LEGACY_PLUGIN_CACHE::s_modHash++;
524 }
525
GetModifyHash()526 static int GetModifyHash()
527 {
528 std::lock_guard<std::mutex> mut( SCH_LEGACY_PLUGIN_CACHE::s_modHashMutex );
529 return SCH_LEGACY_PLUGIN_CACHE::s_modHash;
530 }
531
532 // Most all functions in this class throw IO_ERROR exceptions. There are no
533 // error codes nor user interface calls from here, nor in any SCH_PLUGIN objects.
534 // Catch these exceptions higher up please.
535
536 /// Save the entire library to file m_libFileName;
537 void Save( bool aSaveDocFile = true );
538
539 void Load();
540
541 void AddSymbol( const LIB_SYMBOL* aSymbol );
542
543 void DeleteSymbol( const wxString& aName );
544
545 // If m_libFileName is a symlink follow it to the real source file
546 wxFileName GetRealFile() const;
547
548 wxDateTime GetLibModificationTime();
549
550 bool IsFile( const wxString& aFullPathAndFileName ) const;
551
552 bool IsFileChanged() const;
553
SetModified(bool aModified=true)554 void SetModified( bool aModified = true ) { m_isModified = aModified; }
555
GetLogicalName() const556 wxString GetLogicalName() const { return m_libFileName.GetName(); }
557
SetFileName(const wxString & aFileName)558 void SetFileName( const wxString& aFileName ) { m_libFileName = aFileName; }
559
GetFileName() const560 wxString GetFileName() const { return m_libFileName.GetFullPath(); }
561
562 static LIB_SYMBOL* LoadPart( LINE_READER& aReader, int aMajorVersion, int aMinorVersion,
563 LIB_SYMBOL_MAP* aMap = nullptr );
564 static void SaveSymbol( LIB_SYMBOL* aSymbol, OUTPUTFORMATTER& aFormatter,
565 LIB_SYMBOL_MAP* aMap = nullptr );
566 };
567
568
SCH_LEGACY_PLUGIN()569 SCH_LEGACY_PLUGIN::SCH_LEGACY_PLUGIN() :
570 m_progressReporter( nullptr ),
571 m_lineReader( nullptr ),
572 m_lastProgressLine( 0 ),
573 m_lineCount( 0 )
574 {
575 init( nullptr );
576 }
577
578
~SCH_LEGACY_PLUGIN()579 SCH_LEGACY_PLUGIN::~SCH_LEGACY_PLUGIN()
580 {
581 delete m_cache;
582 }
583
584
init(SCHEMATIC * aSchematic,const PROPERTIES * aProperties)585 void SCH_LEGACY_PLUGIN::init( SCHEMATIC* aSchematic, const PROPERTIES* aProperties )
586 {
587 m_version = 0;
588 m_rootSheet = nullptr;
589 m_schematic = aSchematic;
590 m_cache = nullptr;
591 m_out = nullptr;
592 }
593
594
checkpoint()595 void SCH_LEGACY_PLUGIN::checkpoint()
596 {
597 const unsigned PROGRESS_DELTA = 250;
598
599 if( m_progressReporter )
600 {
601 unsigned curLine = m_lineReader->LineNumber();
602
603 if( curLine > m_lastProgressLine + PROGRESS_DELTA )
604 {
605 m_progressReporter->SetCurrentProgress( ( (double) curLine )
606 / std::max( 1U, m_lineCount ) );
607
608 if( !m_progressReporter->KeepRefreshing() )
609 THROW_IO_ERROR( ( "Open cancelled by user." ) );
610
611 m_lastProgressLine = curLine;
612 }
613 }
614 }
615
616
Load(const wxString & aFileName,SCHEMATIC * aSchematic,SCH_SHEET * aAppendToMe,const PROPERTIES * aProperties)617 SCH_SHEET* SCH_LEGACY_PLUGIN::Load( const wxString& aFileName, SCHEMATIC* aSchematic,
618 SCH_SHEET* aAppendToMe, const PROPERTIES* aProperties )
619 {
620 wxASSERT( !aFileName || aSchematic != nullptr );
621
622 LOCALE_IO toggle; // toggles on, then off, the C locale.
623 SCH_SHEET* sheet;
624
625 wxFileName fn = aFileName;
626
627 // Unfortunately child sheet file names the legacy schematic file format are not fully
628 // qualified and are always appended to the project path. The aFileName attribute must
629 // always be an absolute path so the project path can be used for load child sheet files.
630 wxASSERT( fn.IsAbsolute() );
631
632 if( aAppendToMe )
633 {
634 wxLogTrace( traceSchLegacyPlugin, "Append \"%s\" to sheet \"%s\".",
635 aFileName, aAppendToMe->GetFileName() );
636
637 wxFileName normedFn = aAppendToMe->GetFileName();
638
639 if( !normedFn.IsAbsolute() )
640 {
641 if( aFileName.Right( normedFn.GetFullPath().Length() ) == normedFn.GetFullPath() )
642 m_path = aFileName.Left( aFileName.Length() - normedFn.GetFullPath().Length() );
643 }
644
645 if( m_path.IsEmpty() )
646 m_path = aSchematic->Prj().GetProjectPath();
647
648 wxLogTrace( traceSchLegacyPlugin, "m_Normalized append path \"%s\".", m_path );
649 }
650 else
651 {
652 m_path = aSchematic->Prj().GetProjectPath();
653 }
654
655 m_currentPath.push( m_path );
656 init( aSchematic, aProperties );
657
658 if( aAppendToMe == nullptr )
659 {
660 // Clean up any allocated memory if an exception occurs loading the schematic.
661 std::unique_ptr<SCH_SHEET> newSheet = std::make_unique<SCH_SHEET>( aSchematic );
662 newSheet->SetFileName( aFileName );
663 m_rootSheet = newSheet.get();
664 loadHierarchy( newSheet.get() );
665
666 // If we got here, the schematic loaded successfully.
667 sheet = newSheet.release();
668 m_rootSheet = nullptr; // Quiet Coverity warning.
669 }
670 else
671 {
672 wxCHECK_MSG( aSchematic->IsValid(), nullptr, "Can't append to a schematic with no root!" );
673 m_rootSheet = &aSchematic->Root();
674 sheet = aAppendToMe;
675 loadHierarchy( sheet );
676 }
677
678 wxASSERT( m_currentPath.size() == 1 ); // only the project path should remain
679
680 return sheet;
681 }
682
683
684 // Everything below this comment is recursive. Modify with care.
685
loadHierarchy(SCH_SHEET * aSheet)686 void SCH_LEGACY_PLUGIN::loadHierarchy( SCH_SHEET* aSheet )
687 {
688 SCH_SCREEN* screen = nullptr;
689
690 if( !aSheet->GetScreen() )
691 {
692 // SCH_SCREEN objects store the full path and file name where the SCH_SHEET object only
693 // stores the file name and extension. Add the project path to the file name and
694 // extension to compare when calling SCH_SHEET::SearchHierarchy().
695 wxFileName fileName = aSheet->GetFileName();
696 fileName.SetExt( "sch" );
697
698 if( !fileName.IsAbsolute() )
699 fileName.MakeAbsolute( m_currentPath.top() );
700
701 // Save the current path so that it gets restored when descending and ascending the
702 // sheet hierarchy which allows for sheet schematic files to be nested in folders
703 // relative to the last path a schematic was loaded from.
704 wxLogTrace( traceSchLegacyPlugin, "Saving path '%s'", m_currentPath.top() );
705 m_currentPath.push( fileName.GetPath() );
706 wxLogTrace( traceSchLegacyPlugin, "Current path '%s'", m_currentPath.top() );
707 wxLogTrace( traceSchLegacyPlugin, "Loading '%s'", fileName.GetFullPath() );
708
709 m_rootSheet->SearchHierarchy( fileName.GetFullPath(), &screen );
710
711 if( screen )
712 {
713 aSheet->SetScreen( screen );
714 screen->SetParent( m_schematic );
715 // Do not need to load the sub-sheets - this has already been done.
716 }
717 else
718 {
719 aSheet->SetScreen( new SCH_SCREEN( m_schematic ) );
720 aSheet->GetScreen()->SetFileName( fileName.GetFullPath() );
721
722 try
723 {
724 loadFile( fileName.GetFullPath(), aSheet->GetScreen() );
725 }
726 catch( const IO_ERROR& ioe )
727 {
728 // If there is a problem loading the root sheet, there is no recovery.
729 if( aSheet == m_rootSheet )
730 throw( ioe );
731
732 // For all subsheets, queue up the error message for the caller.
733 if( !m_error.IsEmpty() )
734 m_error += "\n";
735
736 m_error += ioe.What();
737 }
738
739 aSheet->GetScreen()->SetFileReadOnly( !fileName.IsFileWritable() );
740 aSheet->GetScreen()->SetFileExists( true );
741
742 for( auto aItem : aSheet->GetScreen()->Items().OfType( SCH_SHEET_T ) )
743 {
744 wxCHECK2( aItem->Type() == SCH_SHEET_T, continue );
745 auto sheet = static_cast<SCH_SHEET*>( aItem );
746
747 // Set the parent to aSheet. This effectively creates a method to find
748 // the root sheet from any sheet so a pointer to the root sheet does not
749 // need to be stored globally. Note: this is not the same as a hierarchy.
750 // Complex hierarchies can have multiple copies of a sheet. This only
751 // provides a simple tree to find the root sheet.
752 sheet->SetParent( aSheet );
753
754 // Recursion starts here.
755 loadHierarchy( sheet );
756 }
757 }
758
759 m_currentPath.pop();
760 wxLogTrace( traceSchLegacyPlugin, "Restoring path \"%s\"", m_currentPath.top() );
761 }
762 }
763
764
loadFile(const wxString & aFileName,SCH_SCREEN * aScreen)765 void SCH_LEGACY_PLUGIN::loadFile( const wxString& aFileName, SCH_SCREEN* aScreen )
766 {
767 FILE_LINE_READER reader( aFileName );
768
769 if( m_progressReporter )
770 {
771 m_progressReporter->Report( wxString::Format( _( "Loading %s..." ), aFileName ) );
772
773 if( !m_progressReporter->KeepRefreshing() )
774 THROW_IO_ERROR( ( "Open cancelled by user." ) );
775
776 m_lineReader = &reader;
777 m_lineCount = 0;
778
779 while( reader.ReadLine() )
780 m_lineCount++;
781
782 reader.Rewind();
783 }
784
785 loadHeader( reader, aScreen );
786
787 LoadContent( reader, aScreen, m_version );
788
789 // Unfortunately schematic files prior to version 2 are not terminated with $EndSCHEMATC
790 // so checking for its existance will fail so just exit here and take our chances. :(
791 if( m_version > 1 )
792 {
793 char* line = reader.Line();
794
795 while( *line == ' ' )
796 line++;
797
798 if( !strCompare( "$EndSCHEMATC", line ) )
799 THROW_IO_ERROR( "'$EndSCHEMATC' not found" );
800 }
801 }
802
803
LoadContent(LINE_READER & aReader,SCH_SCREEN * aScreen,int version)804 void SCH_LEGACY_PLUGIN::LoadContent( LINE_READER& aReader, SCH_SCREEN* aScreen, int version )
805 {
806 m_version = version;
807
808 // We cannot safely load content without a set root level.
809 wxCHECK_RET( m_rootSheet,
810 "Cannot call SCH_LEGACY_PLUGIN::LoadContent() without setting root sheet." );
811
812 while( aReader.ReadLine() )
813 {
814 checkpoint();
815
816 char* line = aReader.Line();
817
818 while( *line == ' ' )
819 line++;
820
821 // Either an object will be loaded properly or the file load will fail and raise
822 // an exception.
823 if( strCompare( "$Descr", line ) )
824 loadPageSettings( aReader, aScreen );
825 else if( strCompare( "$Comp", line ) )
826 aScreen->Append( loadSymbol( aReader ) );
827 else if( strCompare( "$Sheet", line ) )
828 aScreen->Append( loadSheet( aReader ) );
829 else if( strCompare( "$Bitmap", line ) )
830 aScreen->Append( loadBitmap( aReader ) );
831 else if( strCompare( "Connection", line ) )
832 aScreen->Append( loadJunction( aReader ) );
833 else if( strCompare( "NoConn", line ) )
834 aScreen->Append( loadNoConnect( aReader ) );
835 else if( strCompare( "Wire", line ) )
836 aScreen->Append( loadWire( aReader ) );
837 else if( strCompare( "Entry", line ) )
838 aScreen->Append( loadBusEntry( aReader ) );
839 else if( strCompare( "Text", line ) )
840 aScreen->Append( loadText( aReader ) );
841 else if( strCompare( "BusAlias", line ) )
842 aScreen->AddBusAlias( loadBusAlias( aReader, aScreen ) );
843 else if( strCompare( "$EndSCHEMATC", line ) )
844 return;
845 else
846 SCH_PARSE_ERROR( "unrecognized token", aReader, line );
847 }
848 }
849
850
loadHeader(LINE_READER & aReader,SCH_SCREEN * aScreen)851 void SCH_LEGACY_PLUGIN::loadHeader( LINE_READER& aReader, SCH_SCREEN* aScreen )
852 {
853 const char* line = aReader.ReadLine();
854
855 if( !line || !strCompare( "Eeschema Schematic File Version", line, &line ) )
856 {
857 m_error.Printf( _( "'%s' does not appear to be an Eeschema file." ),
858 aScreen->GetFileName() );
859 THROW_IO_ERROR( m_error );
860 }
861
862 // get the file version here.
863 m_version = parseInt( aReader, line, &line );
864
865 // The next lines are the lib list section, and are mainly comments, like:
866 // LIBS:power
867 // the lib list is not used, but is in schematic file just in case.
868 // It is usually not empty, but we accept empty list.
869 // If empty, there is a legacy section, not used
870 // EELAYER i j
871 // and the last line is
872 // EELAYER END
873 // Skip all lines until the end of header "EELAYER END" is found
874 while( aReader.ReadLine() )
875 {
876 checkpoint();
877
878 line = aReader.Line();
879
880 while( *line == ' ' )
881 line++;
882
883 if( strCompare( "EELAYER END", line ) )
884 return;
885 }
886
887 THROW_IO_ERROR( _( "Missing 'EELAYER END'" ) );
888 }
889
890
loadPageSettings(LINE_READER & aReader,SCH_SCREEN * aScreen)891 void SCH_LEGACY_PLUGIN::loadPageSettings( LINE_READER& aReader, SCH_SCREEN* aScreen )
892 {
893 wxASSERT( aScreen != nullptr );
894
895 wxString buf;
896 const char* line = aReader.Line();
897
898 PAGE_INFO pageInfo;
899 TITLE_BLOCK tb;
900
901 wxCHECK_RET( strCompare( "$Descr", line, &line ), "Invalid sheet description" );
902
903 parseUnquotedString( buf, aReader, line, &line );
904
905 if( !pageInfo.SetType( buf ) )
906 SCH_PARSE_ERROR( "invalid page size", aReader, line );
907
908 int pagew = parseInt( aReader, line, &line );
909 int pageh = parseInt( aReader, line, &line );
910
911 if( buf == PAGE_INFO::Custom )
912 {
913 pageInfo.SetWidthMils( pagew );
914 pageInfo.SetHeightMils( pageh );
915 }
916 else
917 {
918 wxString orientation;
919
920 // Non custom size, set portrait if its present. Can be empty string which defaults
921 // to landscape.
922 parseUnquotedString( orientation, aReader, line, &line, true );
923
924 if( orientation == "portrait" )
925 pageInfo.SetPortrait( true );
926 }
927
928 aScreen->SetPageSettings( pageInfo );
929
930 while( line != nullptr )
931 {
932 buf.clear();
933
934 if( !aReader.ReadLine() )
935 SCH_PARSE_ERROR( _( "unexpected end of file" ), aReader, line );
936
937 line = aReader.Line();
938
939 if( strCompare( "Sheet", line, &line ) )
940 {
941 aScreen->SetVirtualPageNumber( parseInt( aReader, line, &line ) );
942 aScreen->SetPageCount( parseInt( aReader, line, &line ) );
943 }
944 else if( strCompare( "Title", line, &line ) )
945 {
946 parseQuotedString( buf, aReader, line, &line, true );
947 tb.SetTitle( buf );
948 }
949 else if( strCompare( "Date", line, &line ) )
950 {
951 parseQuotedString( buf, aReader, line, &line, true );
952 tb.SetDate( buf );
953 }
954 else if( strCompare( "Rev", line, &line ) )
955 {
956 parseQuotedString( buf, aReader, line, &line, true );
957 tb.SetRevision( buf );
958 }
959 else if( strCompare( "Comp", line, &line ) )
960 {
961 parseQuotedString( buf, aReader, line, &line, true );
962 tb.SetCompany( buf );
963 }
964 else if( strCompare( "Comment1", line, &line ) )
965 {
966 parseQuotedString( buf, aReader, line, &line, true );
967 tb.SetComment( 0, buf );
968 }
969 else if( strCompare( "Comment2", line, &line ) )
970 {
971 parseQuotedString( buf, aReader, line, &line, true );
972 tb.SetComment( 1, buf );
973 }
974 else if( strCompare( "Comment3", line, &line ) )
975 {
976 parseQuotedString( buf, aReader, line, &line, true );
977 tb.SetComment( 2, buf );
978 }
979 else if( strCompare( "Comment4", line, &line ) )
980 {
981 parseQuotedString( buf, aReader, line, &line, true );
982 tb.SetComment( 3, buf );
983 }
984 else if( strCompare( "Comment5", line, &line ) )
985 {
986 parseQuotedString( buf, aReader, line, &line, true );
987 tb.SetComment( 4, buf );
988 }
989 else if( strCompare( "Comment6", line, &line ) )
990 {
991 parseQuotedString( buf, aReader, line, &line, true );
992 tb.SetComment( 5, buf );
993 }
994 else if( strCompare( "Comment7", line, &line ) )
995 {
996 parseQuotedString( buf, aReader, line, &line, true );
997 tb.SetComment( 6, buf );
998 }
999 else if( strCompare( "Comment8", line, &line ) )
1000 {
1001 parseQuotedString( buf, aReader, line, &line, true );
1002 tb.SetComment( 7, buf );
1003 }
1004 else if( strCompare( "Comment9", line, &line ) )
1005 {
1006 parseQuotedString( buf, aReader, line, &line, true );
1007 tb.SetComment( 8, buf );
1008 }
1009 else if( strCompare( "$EndDescr", line ) )
1010 {
1011 aScreen->SetTitleBlock( tb );
1012 return;
1013 }
1014 }
1015
1016 SCH_PARSE_ERROR( "missing 'EndDescr'", aReader, line );
1017 }
1018
1019
loadSheet(LINE_READER & aReader)1020 SCH_SHEET* SCH_LEGACY_PLUGIN::loadSheet( LINE_READER& aReader )
1021 {
1022 std::unique_ptr<SCH_SHEET> sheet = std::make_unique<SCH_SHEET>();
1023
1024 const char* line = aReader.ReadLine();
1025
1026 while( line != nullptr )
1027 {
1028 if( strCompare( "S", line, &line ) ) // Sheet dimensions.
1029 {
1030 wxPoint position;
1031
1032 position.x = Mils2Iu( parseInt( aReader, line, &line ) );
1033 position.y = Mils2Iu( parseInt( aReader, line, &line ) );
1034 sheet->SetPosition( position );
1035
1036 wxSize size;
1037
1038 size.SetWidth( Mils2Iu( parseInt( aReader, line, &line ) ) );
1039 size.SetHeight( Mils2Iu( parseInt( aReader, line, &line ) ) );
1040 sheet->SetSize( size );
1041 }
1042 else if( strCompare( "U", line, &line ) ) // Sheet UUID.
1043 {
1044 wxString text;
1045 parseUnquotedString( text, aReader, line );
1046
1047 if( text != "00000000" )
1048 const_cast<KIID&>( sheet->m_Uuid ) = KIID( text );
1049 }
1050 else if( *line == 'F' ) // Sheet field.
1051 {
1052 line++;
1053
1054 wxString text;
1055 int size;
1056 int fieldId = parseInt( aReader, line, &line );
1057
1058 if( fieldId == 0 || fieldId == 1 ) // Sheet name and file name.
1059 {
1060 parseQuotedString( text, aReader, line, &line );
1061 size = Mils2Iu( parseInt( aReader, line, &line ) );
1062
1063 SCH_FIELD& field = sheet->GetFields()[ fieldId ];
1064 field.SetText( text );
1065 field.SetTextSize( wxSize( size, size ) );
1066 }
1067 else // Sheet pin.
1068 {
1069 // Use a unique_ptr so that we clean up in the case of a throw
1070 std::unique_ptr<SCH_SHEET_PIN> sheetPin = std::make_unique<SCH_SHEET_PIN>( sheet.get() );
1071
1072 sheetPin->SetNumber( fieldId );
1073
1074 // Can be empty fields.
1075 parseQuotedString( text, aReader, line, &line, true );
1076
1077 sheetPin->SetText( ConvertToNewOverbarNotation( text ) );
1078
1079 if( line == nullptr )
1080 THROW_IO_ERROR( _( "unexpected end of line" ) );
1081
1082 switch( parseChar( aReader, line, &line ) )
1083 {
1084 case 'I': sheetPin->SetShape( PINSHEETLABEL_SHAPE::PS_INPUT ); break;
1085 case 'O': sheetPin->SetShape( PINSHEETLABEL_SHAPE::PS_OUTPUT ); break;
1086 case 'B': sheetPin->SetShape( PINSHEETLABEL_SHAPE::PS_BIDI ); break;
1087 case 'T': sheetPin->SetShape( PINSHEETLABEL_SHAPE::PS_TRISTATE ); break;
1088 case 'U': sheetPin->SetShape( PINSHEETLABEL_SHAPE::PS_UNSPECIFIED ); break;
1089 default: SCH_PARSE_ERROR( "invalid sheet pin type", aReader, line );
1090 }
1091
1092 switch( parseChar( aReader, line, &line ) )
1093 {
1094 case 'R': sheetPin->SetSide( SHEET_SIDE::RIGHT ); break;
1095 case 'T': sheetPin->SetSide( SHEET_SIDE::TOP ); break;
1096 case 'B': sheetPin->SetSide( SHEET_SIDE::BOTTOM ); break;
1097 case 'L': sheetPin->SetSide( SHEET_SIDE::LEFT ); break;
1098 default:
1099 SCH_PARSE_ERROR( "invalid sheet pin side", aReader, line );
1100 }
1101
1102 wxPoint position;
1103
1104 position.x = Mils2Iu( parseInt( aReader, line, &line ) );
1105 position.y = Mils2Iu( parseInt( aReader, line, &line ) );
1106 sheetPin->SetPosition( position );
1107
1108 size = Mils2Iu( parseInt( aReader, line, &line ) );
1109
1110 sheetPin->SetTextSize( wxSize( size, size ) );
1111
1112 sheet->AddPin( sheetPin.release() );
1113 }
1114 }
1115 else if( strCompare( "$EndSheet", line ) )
1116 {
1117 sheet->AutoplaceFields( /* aScreen */ nullptr, /* aManual */ false );
1118 return sheet.release();
1119 }
1120
1121 line = aReader.ReadLine();
1122 }
1123
1124 SCH_PARSE_ERROR( "missing '$EndSheet`", aReader, line );
1125
1126 return nullptr; // Prevents compiler warning. Should never get here.
1127 }
1128
1129
loadBitmap(LINE_READER & aReader)1130 SCH_BITMAP* SCH_LEGACY_PLUGIN::loadBitmap( LINE_READER& aReader )
1131 {
1132 std::unique_ptr<SCH_BITMAP> bitmap = std::make_unique<SCH_BITMAP>();
1133
1134 const char* line = aReader.Line();
1135
1136 wxCHECK( strCompare( "$Bitmap", line, &line ), nullptr );
1137
1138 line = aReader.ReadLine();
1139
1140 while( line != nullptr )
1141 {
1142 if( strCompare( "Pos", line, &line ) )
1143 {
1144 wxPoint position;
1145
1146 position.x = Mils2Iu( parseInt( aReader, line, &line ) );
1147 position.y = Mils2Iu( parseInt( aReader, line, &line ) );
1148 bitmap->SetPosition( position );
1149 }
1150 else if( strCompare( "Scale", line, &line ) )
1151 {
1152 auto scalefactor = parseDouble( aReader, line, &line );
1153
1154 // Prevent scalefactor values that cannot be displayed.
1155 // In the case of a bad value, we accept that the image might be mis-scaled
1156 // rather than removing the full image. Users can then edit the scale factor in
1157 // Eeschema to the appropriate value
1158 if( !std::isnormal( scalefactor ) )
1159 scalefactor = 1.0;
1160
1161 bitmap->GetImage()->SetScale( scalefactor );
1162 }
1163 else if( strCompare( "Data", line, &line ) )
1164 {
1165 wxMemoryOutputStream stream;
1166
1167 while( line )
1168 {
1169 if( !aReader.ReadLine() )
1170 SCH_PARSE_ERROR( _( "Unexpected end of file" ), aReader, line );
1171
1172 line = aReader.Line();
1173
1174 if( strCompare( "EndData", line ) )
1175 {
1176 // all the PNG date is read.
1177 // We expect here m_image and m_bitmap are void
1178 wxImage* image = new wxImage();
1179 wxMemoryInputStream istream( stream );
1180 image->LoadFile( istream, wxBITMAP_TYPE_PNG );
1181 bitmap->GetImage()->SetImage( image );
1182 bitmap->GetImage()->SetBitmap( new wxBitmap( *image ) );
1183 break;
1184 }
1185
1186 // Read PNG data, stored in hexadecimal,
1187 // each byte = 2 hexadecimal digits and a space between 2 bytes
1188 // and put it in memory stream buffer
1189 // Note:
1190 // Some old files created bu the V4 schematic versions have a extra
1191 // "$EndBitmap" at the end of the hexadecimal data. (Probably due to
1192 // a bug). So discard it
1193 int len = strlen( line );
1194
1195 for( ; len > 0 && !isspace( *line ) && '$' != *line; len -= 3, line += 3 )
1196 {
1197 int value = 0;
1198
1199 if( sscanf( line, "%X", &value ) == 1 )
1200 stream.PutC( (char) value );
1201 else
1202 THROW_IO_ERROR( "invalid PNG data" );
1203 }
1204 }
1205
1206 if( line == nullptr )
1207 THROW_IO_ERROR( _( "unexpected end of file" ) );
1208 }
1209 else if( strCompare( "$EndBitmap", line ) )
1210 return bitmap.release();
1211
1212 line = aReader.ReadLine();
1213 }
1214
1215 THROW_IO_ERROR( _( "unexpected end of file" ) );
1216 }
1217
1218
loadJunction(LINE_READER & aReader)1219 SCH_JUNCTION* SCH_LEGACY_PLUGIN::loadJunction( LINE_READER& aReader )
1220 {
1221 std::unique_ptr<SCH_JUNCTION> junction = std::make_unique<SCH_JUNCTION>();
1222
1223 const char* line = aReader.Line();
1224
1225 wxCHECK( strCompare( "Connection", line, &line ), nullptr );
1226
1227 wxString name;
1228
1229 parseUnquotedString( name, aReader, line, &line );
1230
1231 wxPoint position;
1232
1233 position.x = Mils2Iu( parseInt( aReader, line, &line ) );
1234 position.y = Mils2Iu( parseInt( aReader, line, &line ) );
1235 junction->SetPosition( position );
1236
1237 return junction.release();
1238 }
1239
1240
loadNoConnect(LINE_READER & aReader)1241 SCH_NO_CONNECT* SCH_LEGACY_PLUGIN::loadNoConnect( LINE_READER& aReader )
1242 {
1243 std::unique_ptr<SCH_NO_CONNECT> no_connect = std::make_unique<SCH_NO_CONNECT>();
1244
1245 const char* line = aReader.Line();
1246
1247 wxCHECK( strCompare( "NoConn", line, &line ), nullptr );
1248
1249 wxString name;
1250
1251 parseUnquotedString( name, aReader, line, &line );
1252
1253 wxPoint position;
1254
1255 position.x = Mils2Iu( parseInt( aReader, line, &line ) );
1256 position.y = Mils2Iu( parseInt( aReader, line, &line ) );
1257 no_connect->SetPosition( position );
1258
1259 return no_connect.release();
1260 }
1261
1262
loadWire(LINE_READER & aReader)1263 SCH_LINE* SCH_LEGACY_PLUGIN::loadWire( LINE_READER& aReader )
1264 {
1265 std::unique_ptr<SCH_LINE> wire = std::make_unique<SCH_LINE>();
1266
1267 const char* line = aReader.Line();
1268
1269 wxCHECK( strCompare( "Wire", line, &line ), nullptr );
1270
1271 if( strCompare( "Wire", line, &line ) )
1272 wire->SetLayer( LAYER_WIRE );
1273 else if( strCompare( "Bus", line, &line ) )
1274 wire->SetLayer( LAYER_BUS );
1275 else if( strCompare( "Notes", line, &line ) )
1276 wire->SetLayer( LAYER_NOTES );
1277 else
1278 SCH_PARSE_ERROR( "invalid line type", aReader, line );
1279
1280 if( !strCompare( "Line", line, &line ) )
1281 SCH_PARSE_ERROR( "invalid wire definition", aReader, line );
1282
1283 // Since Sept 15, 2017, a line style is alloved (width, style, color)
1284 // Only non default values are stored
1285 while( !is_eol( *line ) )
1286 {
1287 wxString buf;
1288
1289 parseUnquotedString( buf, aReader, line, &line );
1290
1291 if( buf == ")" )
1292 continue;
1293
1294 else if( buf == T_WIDTH )
1295 {
1296 int size = Mils2Iu( parseInt( aReader, line, &line ) );
1297 wire->SetLineWidth( size );
1298 }
1299 else if( buf == T_STYLE )
1300 {
1301 parseUnquotedString( buf, aReader, line, &line );
1302 PLOT_DASH_TYPE style = SCH_LINE::GetLineStyleByName( buf );
1303 wire->SetLineStyle( style );
1304 }
1305 else // should be the color parameter.
1306 {
1307 // The color param is something like rgb(150, 40, 191)
1308 // and because there is no space between ( and 150
1309 // the first param is inside buf.
1310 // So break keyword and the first param into 2 separate strings.
1311 wxString prm, keyword;
1312 keyword = buf.BeforeLast( '(', &prm );
1313
1314 if( ( keyword == T_COLOR ) || ( keyword == T_COLORA ) )
1315 {
1316 long color[4] = { 0 };
1317
1318 int ii = 0;
1319
1320 if( !prm.IsEmpty() )
1321 {
1322 prm.ToLong( &color[ii] );
1323 ii++;
1324 }
1325
1326 int prm_count = ( keyword == T_COLORA ) ? 4 : 3;
1327 // fix opacity to 1.0 or 255, when not exists in file
1328 color[3] = 255;
1329
1330 for(; ii < prm_count && !is_eol( *line ); ii++ )
1331 {
1332 color[ii] = parseInt( aReader, line, &line );
1333
1334 // Skip the separator between values
1335 if( *line == ',' || *line == ' ')
1336 line++;
1337 }
1338
1339 wire->SetLineColor( color[0]/255.0, color[1]/255.0, color[2]/255.0,color[3]/255.0 );
1340 }
1341 }
1342 }
1343
1344 // Read the segment en points coordinates:
1345 line = aReader.ReadLine();
1346
1347 wxPoint begin, end;
1348
1349 begin.x = Mils2Iu( parseInt( aReader, line, &line ) );
1350 begin.y = Mils2Iu( parseInt( aReader, line, &line ) );
1351 end.x = Mils2Iu( parseInt( aReader, line, &line ) );
1352 end.y = Mils2Iu( parseInt( aReader, line, &line ) );
1353
1354 wire->SetStartPoint( begin );
1355 wire->SetEndPoint( end );
1356
1357 return wire.release();
1358 }
1359
1360
loadBusEntry(LINE_READER & aReader)1361 SCH_BUS_ENTRY_BASE* SCH_LEGACY_PLUGIN::loadBusEntry( LINE_READER& aReader )
1362 {
1363 const char* line = aReader.Line();
1364
1365 wxCHECK( strCompare( "Entry", line, &line ), nullptr );
1366
1367 std::unique_ptr<SCH_BUS_ENTRY_BASE> busEntry;
1368
1369 if( strCompare( "Wire", line, &line ) )
1370 {
1371 busEntry = std::make_unique<SCH_BUS_WIRE_ENTRY>();
1372
1373 if( !strCompare( "Line", line, &line ) )
1374 SCH_PARSE_ERROR( "invalid bus entry definition expected 'Line'", aReader, line );
1375 }
1376 else if( strCompare( "Bus", line, &line ) )
1377 {
1378 busEntry = std::make_unique<SCH_BUS_BUS_ENTRY>();
1379
1380 if( !strCompare( "Bus", line, &line ) )
1381 SCH_PARSE_ERROR( "invalid bus entry definition expected 'Bus'", aReader, line );
1382 }
1383 else
1384 SCH_PARSE_ERROR( "invalid bus entry type", aReader, line );
1385
1386 line = aReader.ReadLine();
1387
1388 wxPoint pos;
1389 wxSize size;
1390
1391 pos.x = Mils2Iu( parseInt( aReader, line, &line ) );
1392 pos.y = Mils2Iu( parseInt( aReader, line, &line ) );
1393 size.x = Mils2Iu( parseInt( aReader, line, &line ) );
1394 size.y = Mils2Iu( parseInt( aReader, line, &line ) );
1395
1396 size.x -= pos.x;
1397 size.y -= pos.y;
1398
1399 busEntry->SetPosition( pos );
1400 busEntry->SetSize( size );
1401
1402 return busEntry.release();
1403 }
1404
1405 // clang-format off
1406 const std::map<PINSHEETLABEL_SHAPE, const char*> sheetLabelNames
1407 {
1408 { PINSHEETLABEL_SHAPE::PS_INPUT, "Input" },
1409 { PINSHEETLABEL_SHAPE::PS_OUTPUT, "Output" },
1410 { PINSHEETLABEL_SHAPE::PS_BIDI, "BiDi" },
1411 { PINSHEETLABEL_SHAPE::PS_TRISTATE, "3State" },
1412 { PINSHEETLABEL_SHAPE::PS_UNSPECIFIED, "UnSpc" },
1413 };
1414 // clang-format on
1415
1416
loadText(LINE_READER & aReader)1417 SCH_TEXT* SCH_LEGACY_PLUGIN::loadText( LINE_READER& aReader )
1418 {
1419 const char* line = aReader.Line();
1420
1421 wxCHECK( strCompare( "Text", line, &line ), nullptr );
1422
1423 std::unique_ptr<SCH_TEXT> text;
1424
1425 if( strCompare( "Notes", line, &line ) )
1426 text.reset( new SCH_TEXT );
1427 else if( strCompare( "Label", line, &line ) )
1428 text.reset( new SCH_LABEL );
1429 else if( strCompare( "HLabel", line, &line ) )
1430 text.reset( new SCH_HIERLABEL );
1431 else if( strCompare( "GLabel", line, &line ) )
1432 {
1433 // Prior to version 2, the SCH_GLOBALLABEL object did not exist.
1434 if( m_version == 1 )
1435 text = std::make_unique<SCH_HIERLABEL>();
1436 else
1437 text = std::make_unique<SCH_GLOBALLABEL>();
1438 }
1439 else
1440 SCH_PARSE_ERROR( "unknown Text type", aReader, line );
1441
1442 // Parse the parameters common to all text objects.
1443 wxPoint position;
1444
1445 position.x = Mils2Iu( parseInt( aReader, line, &line ) );
1446 position.y = Mils2Iu( parseInt( aReader, line, &line ) );
1447 text->SetPosition( position );
1448
1449 int spinStyle = parseInt( aReader, line, &line );
1450
1451 // Sadly we store the orientation of hierarchical and global labels using a different
1452 // int encoding than that for local labels:
1453 // Global Local
1454 // Left justified 0 2
1455 // Up 1 3
1456 // Right justified 2 0
1457 // Down 3 1
1458 // So we must flip it as the enum is setup with the "global" numbering
1459 if( text->Type() != SCH_GLOBAL_LABEL_T && text->Type() != SCH_HIER_LABEL_T )
1460 {
1461 if( spinStyle == 0 )
1462 spinStyle = 2;
1463 else if( spinStyle == 2 )
1464 spinStyle = 0;
1465 }
1466
1467 text->SetLabelSpinStyle( (LABEL_SPIN_STYLE::SPIN) spinStyle );
1468
1469 int size = Mils2Iu( parseInt( aReader, line, &line ) );
1470
1471 text->SetTextSize( wxSize( size, size ) );
1472
1473 // Parse the global and hierarchical label type.
1474 if( text->Type() == SCH_HIER_LABEL_T || text->Type() == SCH_GLOBAL_LABEL_T )
1475 {
1476 auto resultIt = std::find_if( sheetLabelNames.begin(), sheetLabelNames.end(),
1477 [ &line ]( const auto& it )
1478 {
1479 return strCompare( it.second, line, &line );
1480 } );
1481
1482 if( resultIt != sheetLabelNames.end() )
1483 text->SetShape( resultIt->first );
1484 else
1485 SCH_PARSE_ERROR( "invalid label type", aReader, line );
1486 }
1487
1488 int penWidth = 0;
1489
1490 // The following tokens do not exist in version 1 schematic files,
1491 // and not always in version 2 for HLabels and GLabels
1492 if( m_version > 1 )
1493 {
1494 if( m_version > 2 || *line >= ' ' )
1495 {
1496 if( strCompare( "Italic", line, &line ) )
1497 text->SetItalic( true );
1498 else if( !strCompare( "~", line, &line ) )
1499 SCH_PARSE_ERROR( _( "expected 'Italics' or '~'" ), aReader, line );
1500 }
1501
1502 // The penWidth token does not exist in older versions of the schematic file format
1503 // so calling parseInt will be made only if the EOL is not reached.
1504 if( *line >= ' ' )
1505 penWidth = parseInt( aReader, line, &line );
1506 }
1507
1508 text->SetBold( penWidth != 0 );
1509 text->SetTextThickness( penWidth != 0 ? GetPenSizeForBold( size ) : 0 );
1510
1511 // Read the text string for the text.
1512 char* tmp = aReader.ReadLine();
1513
1514 tmp = strtok( tmp, "\r\n" );
1515 wxString val = FROM_UTF8( tmp );
1516
1517 for( ; ; )
1518 {
1519 int i = val.find( wxT( "\\n" ) );
1520
1521 if( i == wxNOT_FOUND )
1522 break;
1523
1524 val.erase( i, 2 );
1525 val.insert( i, wxT( "\n" ) );
1526 }
1527
1528 text->SetText( ConvertToNewOverbarNotation( val ) );
1529
1530 return text.release();
1531 }
1532
1533
loadSymbol(LINE_READER & aReader)1534 SCH_SYMBOL* SCH_LEGACY_PLUGIN::loadSymbol( LINE_READER& aReader )
1535 {
1536 const char* line = aReader.Line();
1537
1538 wxCHECK( strCompare( "$Comp", line, &line ), nullptr );
1539
1540 std::unique_ptr<SCH_SYMBOL> symbol = std::make_unique<SCH_SYMBOL>();
1541
1542 line = aReader.ReadLine();
1543
1544 while( line != nullptr )
1545 {
1546 if( strCompare( "L", line, &line ) )
1547 {
1548 wxString libName;
1549 size_t pos = 2; // "X" plus ' ' space character.
1550 wxString utf8Line = wxString::FromUTF8( line );
1551 wxStringTokenizer tokens( utf8Line, " \r\n\t" );
1552
1553 if( tokens.CountTokens() < 2 )
1554 THROW_PARSE_ERROR( "invalid symbol library definition", aReader.GetSource(),
1555 aReader.Line(), aReader.LineNumber(), pos );
1556
1557 libName = tokens.GetNextToken();
1558 libName.Replace( "~", " " );
1559
1560 LIB_ID libId;
1561
1562 // Prior to schematic version 4, library IDs did not have a library nickname so
1563 // parsing the symbol name with LIB_ID::Parse() would break symbol library links
1564 // that contained '/' and ':' characters.
1565 if( m_version > 3 )
1566 libId.Parse( libName, true );
1567 else
1568 libId.SetLibItemName( libName );
1569
1570 symbol->SetLibId( libId );
1571
1572 wxString refDesignator = tokens.GetNextToken();
1573
1574 refDesignator.Replace( "~", " " );
1575
1576 wxString prefix = refDesignator;
1577
1578 while( prefix.Length() )
1579 {
1580 if( ( prefix.Last() < '0' || prefix.Last() > '9') && prefix.Last() != '?' )
1581 break;
1582
1583 prefix.RemoveLast();
1584 }
1585
1586 // Avoid a prefix containing trailing/leading spaces
1587 prefix.Trim( true );
1588 prefix.Trim( false );
1589
1590 if( prefix.IsEmpty() )
1591 symbol->SetPrefix( wxString( "U" ) );
1592 else
1593 symbol->SetPrefix( prefix );
1594 }
1595 else if( strCompare( "U", line, &line ) )
1596 {
1597 // This fixes a potentially buggy files caused by unit being set to zero which
1598 // causes netlist issues. See https://bugs.launchpad.net/kicad/+bug/1677282.
1599 int unit = parseInt( aReader, line, &line );
1600
1601 if( unit == 0 )
1602 {
1603 unit = 1;
1604
1605 // Set the file as modified so the user can be warned.
1606 if( m_rootSheet->GetScreen() )
1607 m_rootSheet->GetScreen()->SetContentModified();
1608 }
1609
1610 symbol->SetUnit( unit );
1611
1612 // Same can also happen with the convert parameter
1613 int convert = parseInt( aReader, line, &line );
1614
1615 if( convert == 0 )
1616 {
1617 convert = 1;
1618
1619 // Set the file as modified so the user can be warned.
1620 if( m_rootSheet->GetScreen() )
1621 m_rootSheet->GetScreen()->SetContentModified();
1622 }
1623
1624 symbol->SetConvert( convert );
1625
1626 wxString text;
1627 parseUnquotedString( text, aReader, line, &line );
1628
1629 if( text != "00000000" )
1630 const_cast<KIID&>( symbol->m_Uuid ) = KIID( text );
1631 }
1632 else if( strCompare( "P", line, &line ) )
1633 {
1634 wxPoint pos;
1635
1636 pos.x = Mils2Iu( parseInt( aReader, line, &line ) );
1637 pos.y = Mils2Iu( parseInt( aReader, line, &line ) );
1638 symbol->SetPosition( pos );
1639 }
1640 else if( strCompare( "AR", line, &line ) )
1641 {
1642 const char* strCompare = "Path=";
1643 int len = strlen( strCompare );
1644
1645 if( strncasecmp( strCompare, line, len ) != 0 )
1646 SCH_PARSE_ERROR( "missing 'Path=' token", aReader, line );
1647
1648 line += len;
1649 wxString pathStr, reference, unit;
1650
1651 parseQuotedString( pathStr, aReader, line, &line );
1652
1653 // Note: AR path excludes root sheet, but includes symbol. Normalize to
1654 // internal format by shifting everything down one and adding the root sheet.
1655 KIID_PATH path( pathStr );
1656
1657 if( path.size() > 0 )
1658 {
1659 for( size_t i = path.size() - 1; i > 0; --i )
1660 path[i] = path[i-1];
1661
1662 path[0] = m_rootSheet->m_Uuid;
1663 }
1664 else
1665 path.push_back( m_rootSheet->m_Uuid );
1666
1667 strCompare = "Ref=";
1668 len = strlen( strCompare );
1669
1670 if( strncasecmp( strCompare, line, len ) != 0 )
1671 SCH_PARSE_ERROR( "missing 'Ref=' token", aReader, line );
1672
1673 line+= len;
1674 parseQuotedString( reference, aReader, line, &line );
1675
1676 strCompare = "Part=";
1677 len = strlen( strCompare );
1678
1679 if( strncasecmp( strCompare, line, len ) != 0 )
1680 SCH_PARSE_ERROR( "missing 'Part=' token", aReader, line );
1681
1682 line+= len;
1683 parseQuotedString( unit, aReader, line, &line );
1684
1685 long tmp;
1686
1687 if( !unit.ToLong( &tmp, 10 ) )
1688 SCH_PARSE_ERROR( "expected integer value", aReader, line );
1689
1690 if( tmp < 0 || tmp > MAX_UNIT_COUNT_PER_PACKAGE )
1691 SCH_PARSE_ERROR( "unit value out of range", aReader, line );
1692
1693 symbol->AddHierarchicalReference( path, reference, (int)tmp );
1694 symbol->GetField( REFERENCE_FIELD )->SetText( reference );
1695
1696 }
1697 else if( strCompare( "F", line, &line ) )
1698 {
1699 int index = parseInt( aReader, line, &line );
1700
1701 wxString text, name;
1702
1703 parseQuotedString( text, aReader, line, &line, true );
1704
1705 char orientation = parseChar( aReader, line, &line );
1706 wxPoint pos;
1707 pos.x = Mils2Iu( parseInt( aReader, line, &line ) );
1708 pos.y = Mils2Iu( parseInt( aReader, line, &line ) );
1709 int size = Mils2Iu( parseInt( aReader, line, &line ) );
1710 int attributes = parseHex( aReader, line, &line );
1711
1712 if( index >= symbol->GetFieldCount() )
1713 {
1714 // The first MANDATOR_FIELDS _must_ be constructed within the SCH_SYMBOL
1715 // constructor. This assert is simply here to guard against a change in that
1716 // constructor.
1717 wxASSERT( symbol->GetFieldCount() >= MANDATORY_FIELDS );
1718
1719 // Ignore the _supplied_ fieldNdx. It is not important anymore if within the
1720 // user defined fields region (i.e. >= MANDATORY_FIELDS).
1721 // We freely renumber the index to fit the next available field slot.
1722 index = symbol->GetFieldCount(); // new has this index after insertion
1723
1724 SCH_FIELD field( wxPoint( 0, 0 ), index, symbol.get(), name );
1725 symbol->AddField( field );
1726 }
1727
1728 SCH_FIELD& field = symbol->GetFields()[index];
1729
1730 // Prior to version 2 of the schematic file format, none of the following existed.
1731 if( m_version > 1 )
1732 {
1733 wxString textAttrs;
1734 char hjustify = parseChar( aReader, line, &line );
1735
1736 parseUnquotedString( textAttrs, aReader, line, &line );
1737
1738 // The name of the field is optional.
1739 parseQuotedString( name, aReader, line, &line, true );
1740
1741 if( hjustify == 'L' )
1742 field.SetHorizJustify( GR_TEXT_HJUSTIFY_LEFT );
1743 else if( hjustify == 'R' )
1744 field.SetHorizJustify( GR_TEXT_HJUSTIFY_RIGHT );
1745 else if( hjustify != 'C' )
1746 SCH_PARSE_ERROR( "symbol field text horizontal justification must be "
1747 "L, R, or C", aReader, line );
1748
1749 // We are guaranteed to have a least one character here for older file formats
1750 // otherwise an exception would have been raised..
1751 if( textAttrs[0] == 'T' )
1752 field.SetVertJustify( GR_TEXT_VJUSTIFY_TOP );
1753 else if( textAttrs[0] == 'B' )
1754 field.SetVertJustify( GR_TEXT_VJUSTIFY_BOTTOM );
1755 else if( textAttrs[0] != 'C' )
1756 SCH_PARSE_ERROR( "symbol field text vertical justification must be "
1757 "B, T, or C", aReader, line );
1758
1759 // Newer file formats include the bold and italics text attribute.
1760 if( textAttrs.Length() > 1 )
1761 {
1762 if( textAttrs.Length() != 3 )
1763 SCH_PARSE_ERROR( _( "symbol field text attributes must be 3 characters wide" ),
1764 aReader, line );
1765
1766 if( textAttrs[1] == 'I' )
1767 field.SetItalic( true );
1768 else if( textAttrs[1] != 'N' )
1769 SCH_PARSE_ERROR( "symbol field text italics indicator must be I or N",
1770 aReader, line );
1771
1772 if( textAttrs[2] == 'B' )
1773 field.SetBold( true );
1774 else if( textAttrs[2] != 'N' )
1775 SCH_PARSE_ERROR( "symbol field text bold indicator must be B or N",
1776 aReader, line );
1777 }
1778 }
1779
1780 field.SetText( text );
1781 field.SetTextPos( pos );
1782 field.SetVisible( !attributes );
1783 field.SetTextSize( wxSize( size, size ) );
1784
1785 if( orientation == 'H' )
1786 field.SetTextAngle( TEXT_ANGLE_HORIZ );
1787 else if( orientation == 'V' )
1788 field.SetTextAngle( TEXT_ANGLE_VERT );
1789 else
1790 SCH_PARSE_ERROR( "symbol field orientation must be H or V", aReader, line );
1791
1792 if( name.IsEmpty() )
1793 name = TEMPLATE_FIELDNAME::GetDefaultFieldName( index );
1794
1795 field.SetName( name );
1796 }
1797 else if( strCompare( "$EndComp", line ) )
1798 {
1799 // Ensure all flags (some are set by previous initializations) are reset:
1800 symbol->ClearFlags();
1801 return symbol.release();
1802 }
1803 else
1804 {
1805 // There are two lines that begin with a tab or spaces that includes a line with the
1806 // redundant position information and the transform matrix settings.
1807
1808 // Parse the redundant position information just the same to check for formatting
1809 // errors.
1810 parseInt( aReader, line, &line ); // Always 1.
1811 parseInt( aReader, line, &line ); // The X coordinate.
1812 parseInt( aReader, line, &line ); // The Y coordinate.
1813
1814 line = aReader.ReadLine();
1815
1816 TRANSFORM transform;
1817
1818 transform.x1 = parseInt( aReader, line, &line );
1819
1820 if( transform.x1 < -1 || transform.x1 > 1 )
1821 SCH_PARSE_ERROR( "invalid symbol X1 transform value", aReader, line );
1822
1823 transform.y1 = parseInt( aReader, line, &line );
1824
1825 if( transform.y1 < -1 || transform.y1 > 1 )
1826 SCH_PARSE_ERROR( "invalid symbol Y1 transform value", aReader, line );
1827
1828 transform.x2 = parseInt( aReader, line, &line );
1829
1830 if( transform.x2 < -1 || transform.x2 > 1 )
1831 SCH_PARSE_ERROR( "invalid symbol X2 transform value", aReader, line );
1832
1833 transform.y2 = parseInt( aReader, line, &line );
1834
1835 if( transform.y2 < -1 || transform.y2 > 1 )
1836 SCH_PARSE_ERROR( "invalid symbol Y2 transform value", aReader, line );
1837
1838 symbol->SetTransform( transform );
1839 }
1840
1841 line = aReader.ReadLine();
1842 }
1843
1844 SCH_PARSE_ERROR( "invalid symbol line", aReader, line );
1845
1846 return nullptr; // Prevents compiler warning. Should never get here.
1847 }
1848
1849
loadBusAlias(LINE_READER & aReader,SCH_SCREEN * aScreen)1850 std::shared_ptr<BUS_ALIAS> SCH_LEGACY_PLUGIN::loadBusAlias( LINE_READER& aReader,
1851 SCH_SCREEN* aScreen )
1852 {
1853 auto busAlias = std::make_shared<BUS_ALIAS>( aScreen );
1854 const char* line = aReader.Line();
1855
1856 wxCHECK( strCompare( "BusAlias", line, &line ), nullptr );
1857
1858 wxString buf;
1859 parseUnquotedString( buf, aReader, line, &line );
1860 busAlias->SetName( buf );
1861
1862 while( *line != '\0' )
1863 {
1864 buf.clear();
1865 parseUnquotedString( buf, aReader, line, &line, true );
1866
1867 if( buf.Len() > 0 )
1868 {
1869 busAlias->AddMember( buf );
1870 }
1871 }
1872
1873 return busAlias;
1874 }
1875
1876
Save(const wxString & aFileName,SCH_SHEET * aSheet,SCHEMATIC * aSchematic,const PROPERTIES * aProperties)1877 void SCH_LEGACY_PLUGIN::Save( const wxString& aFileName, SCH_SHEET* aSheet, SCHEMATIC* aSchematic,
1878 const PROPERTIES* aProperties )
1879 {
1880 wxCHECK_RET( aSheet != nullptr, "NULL SCH_SHEET object." );
1881 wxCHECK_RET( !aFileName.IsEmpty(), "No schematic file name defined." );
1882
1883 LOCALE_IO toggle; // toggles on, then off, the C locale, to write floating point values.
1884
1885 init( aSchematic, aProperties );
1886
1887 wxFileName fn = aFileName;
1888
1889 // File names should be absolute. Don't assume everything relative to the project path
1890 // works properly.
1891 wxASSERT( fn.IsAbsolute() );
1892
1893 FILE_OUTPUTFORMATTER formatter( fn.GetFullPath() );
1894
1895 m_out = &formatter; // no ownership
1896
1897 Format( aSheet );
1898
1899 aSheet->GetScreen()->SetFileExists( true );
1900 }
1901
1902
Format(SCH_SHEET * aSheet)1903 void SCH_LEGACY_PLUGIN::Format( SCH_SHEET* aSheet )
1904 {
1905 wxCHECK_RET( aSheet != nullptr, "NULL SCH_SHEET* object." );
1906 wxCHECK_RET( m_schematic != nullptr, "NULL SCHEMATIC* object." );
1907
1908 SCH_SCREEN* screen = aSheet->GetScreen();
1909
1910 wxCHECK( screen, /* void */ );
1911
1912 // Write the header
1913 m_out->Print( 0, "%s %s %d\n", "EESchema", SCHEMATIC_HEAD_STRING, EESCHEMA_VERSION );
1914
1915 // This section is not used, but written for file compatibility
1916 m_out->Print( 0, "EELAYER %d %d\n", SCH_LAYER_ID_COUNT, 0 );
1917 m_out->Print( 0, "EELAYER END\n" );
1918
1919 /* Write page info, ScreenNumber and NumberOfScreen; not very meaningful for
1920 * SheetNumber and Sheet Count in a complex hierarchy, but useful in
1921 * simple hierarchy and flat hierarchy. Used also to search the root
1922 * sheet ( ScreenNumber = 1 ) within the files
1923 */
1924 const TITLE_BLOCK& tb = screen->GetTitleBlock();
1925 const PAGE_INFO& page = screen->GetPageSettings();
1926
1927 m_out->Print( 0, "$Descr %s %d %d%s\n", TO_UTF8( page.GetType() ),
1928 page.GetWidthMils(),
1929 page.GetHeightMils(),
1930 !page.IsCustom() && page.IsPortrait() ? " portrait" : "" );
1931 m_out->Print( 0, "encoding utf-8\n" );
1932 m_out->Print( 0, "Sheet %d %d\n", screen->GetVirtualPageNumber(), screen->GetPageCount() );
1933 m_out->Print( 0, "Title %s\n", EscapedUTF8( tb.GetTitle() ).c_str() );
1934 m_out->Print( 0, "Date %s\n", EscapedUTF8( tb.GetDate() ).c_str() );
1935 m_out->Print( 0, "Rev %s\n", EscapedUTF8( tb.GetRevision() ).c_str() );
1936 m_out->Print( 0, "Comp %s\n", EscapedUTF8( tb.GetCompany() ).c_str() );
1937 m_out->Print( 0, "Comment1 %s\n", EscapedUTF8( tb.GetComment( 0 ) ).c_str() );
1938 m_out->Print( 0, "Comment2 %s\n", EscapedUTF8( tb.GetComment( 1 ) ).c_str() );
1939 m_out->Print( 0, "Comment3 %s\n", EscapedUTF8( tb.GetComment( 2 ) ).c_str() );
1940 m_out->Print( 0, "Comment4 %s\n", EscapedUTF8( tb.GetComment( 3 ) ).c_str() );
1941 m_out->Print( 0, "Comment5 %s\n", EscapedUTF8( tb.GetComment( 4 ) ).c_str() );
1942 m_out->Print( 0, "Comment6 %s\n", EscapedUTF8( tb.GetComment( 5 ) ).c_str() );
1943 m_out->Print( 0, "Comment7 %s\n", EscapedUTF8( tb.GetComment( 6 ) ).c_str() );
1944 m_out->Print( 0, "Comment8 %s\n", EscapedUTF8( tb.GetComment( 7 ) ).c_str() );
1945 m_out->Print( 0, "Comment9 %s\n", EscapedUTF8( tb.GetComment( 8 ) ).c_str() );
1946 m_out->Print( 0, "$EndDescr\n" );
1947
1948 for( const auto& alias : screen->GetBusAliases() )
1949 {
1950 saveBusAlias( alias );
1951 }
1952
1953 // Enforce item ordering
1954 auto cmp = []( const SCH_ITEM* a, const SCH_ITEM* b ) { return *a < *b; };
1955 std::multiset<SCH_ITEM*, decltype( cmp )> save_map( cmp );
1956
1957 for( auto item : screen->Items() )
1958 save_map.insert( item );
1959
1960
1961 for( auto& item : save_map )
1962 {
1963 switch( item->Type() )
1964 {
1965 case SCH_SYMBOL_T:
1966 saveSymbol( static_cast<SCH_SYMBOL*>( item ) );
1967 break;
1968 case SCH_BITMAP_T:
1969 saveBitmap( static_cast<SCH_BITMAP*>( item ) );
1970 break;
1971 case SCH_SHEET_T:
1972 saveSheet( static_cast<SCH_SHEET*>( item ) );
1973 break;
1974 case SCH_JUNCTION_T:
1975 saveJunction( static_cast<SCH_JUNCTION*>( item ) );
1976 break;
1977 case SCH_NO_CONNECT_T:
1978 saveNoConnect( static_cast<SCH_NO_CONNECT*>( item ) );
1979 break;
1980 case SCH_BUS_WIRE_ENTRY_T:
1981 case SCH_BUS_BUS_ENTRY_T:
1982 saveBusEntry( static_cast<SCH_BUS_ENTRY_BASE*>( item ) );
1983 break;
1984 case SCH_LINE_T:
1985 saveLine( static_cast<SCH_LINE*>( item ) );
1986 break;
1987 case SCH_TEXT_T:
1988 case SCH_LABEL_T:
1989 case SCH_GLOBAL_LABEL_T:
1990 case SCH_HIER_LABEL_T:
1991 saveText( static_cast<SCH_TEXT*>( item ) );
1992 break;
1993 default:
1994 wxASSERT( "Unexpected schematic object type in SCH_LEGACY_PLUGIN::Format()" );
1995 }
1996 }
1997
1998 m_out->Print( 0, "$EndSCHEMATC\n" );
1999 }
2000
2001
Format(SELECTION * aSelection,OUTPUTFORMATTER * aFormatter)2002 void SCH_LEGACY_PLUGIN::Format( SELECTION* aSelection, OUTPUTFORMATTER* aFormatter )
2003 {
2004 m_out = aFormatter;
2005
2006 for( unsigned i = 0; i < aSelection->GetSize(); ++i )
2007 {
2008 SCH_ITEM* item = (SCH_ITEM*) aSelection->GetItem( i );
2009
2010 switch( item->Type() )
2011 {
2012 case SCH_SYMBOL_T:
2013 saveSymbol( static_cast< SCH_SYMBOL* >( item ) );
2014 break;
2015 case SCH_BITMAP_T:
2016 saveBitmap( static_cast< SCH_BITMAP* >( item ) );
2017 break;
2018 case SCH_SHEET_T:
2019 saveSheet( static_cast< SCH_SHEET* >( item ) );
2020 break;
2021 case SCH_JUNCTION_T:
2022 saveJunction( static_cast< SCH_JUNCTION* >( item ) );
2023 break;
2024 case SCH_NO_CONNECT_T:
2025 saveNoConnect( static_cast< SCH_NO_CONNECT* >( item ) );
2026 break;
2027 case SCH_BUS_WIRE_ENTRY_T:
2028 case SCH_BUS_BUS_ENTRY_T:
2029 saveBusEntry( static_cast< SCH_BUS_ENTRY_BASE* >( item ) );
2030 break;
2031 case SCH_LINE_T:
2032 saveLine( static_cast< SCH_LINE* >( item ) );
2033 break;
2034 case SCH_TEXT_T:
2035 case SCH_LABEL_T:
2036 case SCH_GLOBAL_LABEL_T:
2037 case SCH_HIER_LABEL_T:
2038 saveText( static_cast< SCH_TEXT* >( item ) );
2039 break;
2040 default:
2041 wxASSERT( "Unexpected schematic object type in SCH_LEGACY_PLUGIN::Format()" );
2042 }
2043 }
2044 }
2045
2046
saveSymbol(SCH_SYMBOL * aSymbol)2047 void SCH_LEGACY_PLUGIN::saveSymbol( SCH_SYMBOL* aSymbol )
2048 {
2049 std::string name1;
2050 std::string name2;
2051
2052 static wxString delimiters( wxT( " " ) );
2053
2054 // This is redundant with the AR entries below, but it makes the files backwards-compatible.
2055 if( aSymbol->GetInstanceReferences().size() > 0 )
2056 {
2057 const SYMBOL_INSTANCE_REFERENCE& instance = aSymbol->GetInstanceReferences()[0];
2058 name1 = toUTFTildaText( instance.m_Reference );
2059 }
2060 else
2061 {
2062 if( aSymbol->GetField( REFERENCE_FIELD )->GetText().IsEmpty() )
2063 name1 = toUTFTildaText( aSymbol->GetPrefix() );
2064 else
2065 name1 = toUTFTildaText( aSymbol->GetField( REFERENCE_FIELD )->GetText() );
2066 }
2067
2068 wxString symbol_name = aSymbol->GetLibId().Format();
2069
2070 if( symbol_name.size() )
2071 {
2072 name2 = toUTFTildaText( symbol_name );
2073 }
2074 else
2075 {
2076 name2 = "_NONAME_";
2077 }
2078
2079 m_out->Print( 0, "$Comp\n" );
2080 m_out->Print( 0, "L %s %s\n", name2.c_str(), name1.c_str() );
2081
2082 // Generate unit number, conversion and timestamp
2083 m_out->Print( 0, "U %d %d %8.8X\n",
2084 aSymbol->GetUnit(),
2085 aSymbol->GetConvert(),
2086 aSymbol->m_Uuid.AsLegacyTimestamp() );
2087
2088 // Save the position
2089 m_out->Print( 0, "P %d %d\n",
2090 Iu2Mils( aSymbol->GetPosition().x ),
2091 Iu2Mils( aSymbol->GetPosition().y ) );
2092
2093 /* If this is a complex hierarchy; save hierarchical references.
2094 * but for simple hierarchies it is not necessary.
2095 * the reference inf is already saved
2096 * this is useful for old Eeschema version compatibility
2097 */
2098 if( aSymbol->GetInstanceReferences().size() > 1 )
2099 {
2100 for( const SYMBOL_INSTANCE_REFERENCE& instance : aSymbol->GetInstanceReferences() )
2101 {
2102 /*format:
2103 * AR Path="/140/2" Ref="C99" Part="1"
2104 * where 140 is the uid of the containing sheet and 2 is the timestamp of this symbol.
2105 * (timestamps are actually 8 hex chars)
2106 * Ref is the conventional symbol reference designator for this 'path'
2107 * Part is the conventional symbol unit selection for this 'path'
2108 */
2109 wxString path = "/";
2110
2111 // Skip root sheet
2112 for( int i = 1; i < (int) instance.m_Path.size(); ++i )
2113 path += instance.m_Path[i].AsLegacyTimestampString() + "/";
2114
2115 m_out->Print( 0, "AR Path=\"%s\" Ref=\"%s\" Part=\"%d\" \n",
2116 TO_UTF8( path + aSymbol->m_Uuid.AsLegacyTimestampString() ),
2117 TO_UTF8( instance.m_Reference ),
2118 instance.m_Unit );
2119 }
2120 }
2121
2122 // update the ugly field id, which I would like to see go away someday soon.
2123 for( int i = 0; i < aSymbol->GetFieldCount(); ++i )
2124 aSymbol->GetFields()[i].SetId( i );
2125
2126 // Fixed fields:
2127 // Save mandatory fields even if they are blank,
2128 // because the visibility, size and orientation are set from library editor.
2129 for( unsigned i = 0; i < MANDATORY_FIELDS; ++i )
2130 saveField( &aSymbol->GetFields()[i] );
2131
2132 // User defined fields:
2133 // The *policy* about which user defined fields are symbol of a symbol is now
2134 // only in the dialog editors. No policy should be enforced here, simply
2135 // save all the user defined fields, they are present because a dialog editor
2136 // thought they should be. If you disagree, go fix the dialog editors.
2137 for( int i = MANDATORY_FIELDS; i < aSymbol->GetFieldCount(); ++i )
2138 saveField( &aSymbol->GetFields()[i] );
2139
2140 // Unit number, position, box ( old standard )
2141 m_out->Print( 0, "\t%-4d %-4d %-4d\n", aSymbol->GetUnit(),
2142 Iu2Mils( aSymbol->GetPosition().x ),
2143 Iu2Mils( aSymbol->GetPosition().y ) );
2144
2145 TRANSFORM transform = aSymbol->GetTransform();
2146
2147 m_out->Print( 0, "\t%-4d %-4d %-4d %-4d\n",
2148 transform.x1, transform.y1, transform.x2, transform.y2 );
2149 m_out->Print( 0, "$EndComp\n" );
2150 }
2151
2152
saveField(SCH_FIELD * aField)2153 void SCH_LEGACY_PLUGIN::saveField( SCH_FIELD* aField )
2154 {
2155 char hjustify = 'C';
2156
2157 if( aField->GetHorizJustify() == GR_TEXT_HJUSTIFY_LEFT )
2158 hjustify = 'L';
2159 else if( aField->GetHorizJustify() == GR_TEXT_HJUSTIFY_RIGHT )
2160 hjustify = 'R';
2161
2162 char vjustify = 'C';
2163
2164 if( aField->GetVertJustify() == GR_TEXT_VJUSTIFY_BOTTOM )
2165 vjustify = 'B';
2166 else if( aField->GetVertJustify() == GR_TEXT_VJUSTIFY_TOP )
2167 vjustify = 'T';
2168
2169 m_out->Print( 0, "F %d %s %c %-3d %-3d %-3d %4.4X %c %c%c%c",
2170 aField->GetId(),
2171 EscapedUTF8( aField->GetText() ).c_str(), // wraps in quotes too
2172 aField->GetTextAngle() == TEXT_ANGLE_HORIZ ? 'H' : 'V',
2173 Iu2Mils( aField->GetLibPosition().x ),
2174 Iu2Mils( aField->GetLibPosition().y ),
2175 Iu2Mils( aField->GetTextWidth() ),
2176 !aField->IsVisible(),
2177 hjustify, vjustify,
2178 aField->IsItalic() ? 'I' : 'N',
2179 aField->IsBold() ? 'B' : 'N' );
2180
2181 // Save field name, if the name is user definable
2182 if( aField->GetId() >= MANDATORY_FIELDS )
2183 m_out->Print( 0, " %s", EscapedUTF8( aField->GetName() ).c_str() );
2184
2185 m_out->Print( 0, "\n" );
2186 }
2187
2188
saveBitmap(SCH_BITMAP * aBitmap)2189 void SCH_LEGACY_PLUGIN::saveBitmap( SCH_BITMAP* aBitmap )
2190 {
2191 wxCHECK_RET( aBitmap != nullptr, "SCH_BITMAP* is NULL" );
2192
2193 const wxImage* image = aBitmap->GetImage()->GetImageData();
2194
2195 wxCHECK_RET( image != nullptr, "wxImage* is NULL" );
2196
2197 m_out->Print( 0, "$Bitmap\n" );
2198 m_out->Print( 0, "Pos %-4d %-4d\n",
2199 Iu2Mils( aBitmap->GetPosition().x ),
2200 Iu2Mils( aBitmap->GetPosition().y ) );
2201 m_out->Print( 0, "Scale %f\n", aBitmap->GetImage()->GetScale() );
2202 m_out->Print( 0, "Data\n" );
2203
2204 wxMemoryOutputStream stream;
2205
2206 image->SaveFile( stream, wxBITMAP_TYPE_PNG );
2207
2208 // Write binary data in hexadecimal form (ASCII)
2209 wxStreamBuffer* buffer = stream.GetOutputStreamBuffer();
2210 char* begin = (char*) buffer->GetBufferStart();
2211
2212 for( int ii = 0; begin < buffer->GetBufferEnd(); begin++, ii++ )
2213 {
2214 if( ii >= 32 )
2215 {
2216 ii = 0;
2217
2218 m_out->Print( 0, "\n" );
2219 }
2220
2221 m_out->Print( 0, "%2.2X ", *begin & 0xFF );
2222 }
2223
2224 m_out->Print( 0, "\nEndData\n" );
2225 m_out->Print( 0, "$EndBitmap\n" );
2226 }
2227
2228
saveSheet(SCH_SHEET * aSheet)2229 void SCH_LEGACY_PLUGIN::saveSheet( SCH_SHEET* aSheet )
2230 {
2231 wxCHECK_RET( aSheet != nullptr, "SCH_SHEET* is NULL" );
2232
2233 m_out->Print( 0, "$Sheet\n" );
2234 m_out->Print( 0, "S %-4d %-4d %-4d %-4d\n",
2235 Iu2Mils( aSheet->GetPosition().x ),
2236 Iu2Mils( aSheet->GetPosition().y ),
2237 Iu2Mils( aSheet->GetSize().x ),
2238 Iu2Mils( aSheet->GetSize().y ) );
2239
2240 m_out->Print( 0, "U %8.8X\n", aSheet->m_Uuid.AsLegacyTimestamp() );
2241
2242 SCH_FIELD& sheetName = aSheet->GetFields()[SHEETNAME];
2243 SCH_FIELD& fileName = aSheet->GetFields()[SHEETFILENAME];
2244
2245 if( !sheetName.GetText().IsEmpty() )
2246 m_out->Print( 0, "F0 %s %d\n",
2247 EscapedUTF8( sheetName.GetText() ).c_str(),
2248 Iu2Mils( sheetName.GetTextSize().x ) );
2249
2250 if( !fileName.GetText().IsEmpty() )
2251 m_out->Print( 0, "F1 %s %d\n",
2252 EscapedUTF8( fileName.GetText() ).c_str(),
2253 Iu2Mils( fileName.GetTextSize().x ) );
2254
2255 for( const SCH_SHEET_PIN* pin : aSheet->GetPins() )
2256 {
2257 int type, side;
2258
2259 if( pin->GetText().IsEmpty() )
2260 break;
2261
2262 switch( pin->GetSide() )
2263 {
2264 default:
2265 case SHEET_SIDE::LEFT: side = 'L'; break;
2266 case SHEET_SIDE::RIGHT: side = 'R'; break;
2267 case SHEET_SIDE::TOP: side = 'T'; break;
2268 case SHEET_SIDE::BOTTOM: side = 'B'; break;
2269 }
2270
2271 switch( pin->GetShape() )
2272 {
2273 default:
2274 case PINSHEETLABEL_SHAPE::PS_UNSPECIFIED: type = 'U'; break;
2275 case PINSHEETLABEL_SHAPE::PS_INPUT: type = 'I'; break;
2276 case PINSHEETLABEL_SHAPE::PS_OUTPUT: type = 'O'; break;
2277 case PINSHEETLABEL_SHAPE::PS_BIDI: type = 'B'; break;
2278 case PINSHEETLABEL_SHAPE::PS_TRISTATE: type = 'T'; break;
2279 }
2280
2281 m_out->Print( 0, "F%d %s %c %c %-3d %-3d %-3d\n",
2282 pin->GetNumber(),
2283 EscapedUTF8( pin->GetText() ).c_str(), // supplies wrapping quotes
2284 type, side, Iu2Mils( pin->GetPosition().x ),
2285 Iu2Mils( pin->GetPosition().y ),
2286 Iu2Mils( pin->GetTextWidth() ) );
2287 }
2288
2289 m_out->Print( 0, "$EndSheet\n" );
2290 }
2291
2292
saveJunction(SCH_JUNCTION * aJunction)2293 void SCH_LEGACY_PLUGIN::saveJunction( SCH_JUNCTION* aJunction )
2294 {
2295 wxCHECK_RET( aJunction != nullptr, "SCH_JUNCTION* is NULL" );
2296
2297 m_out->Print( 0, "Connection ~ %-4d %-4d\n",
2298 Iu2Mils( aJunction->GetPosition().x ),
2299 Iu2Mils( aJunction->GetPosition().y ) );
2300 }
2301
2302
saveNoConnect(SCH_NO_CONNECT * aNoConnect)2303 void SCH_LEGACY_PLUGIN::saveNoConnect( SCH_NO_CONNECT* aNoConnect )
2304 {
2305 wxCHECK_RET( aNoConnect != nullptr, "SCH_NOCONNECT* is NULL" );
2306
2307 m_out->Print( 0, "NoConn ~ %-4d %-4d\n",
2308 Iu2Mils( aNoConnect->GetPosition().x ),
2309 Iu2Mils( aNoConnect->GetPosition().y ) );
2310 }
2311
2312
saveBusEntry(SCH_BUS_ENTRY_BASE * aBusEntry)2313 void SCH_LEGACY_PLUGIN::saveBusEntry( SCH_BUS_ENTRY_BASE* aBusEntry )
2314 {
2315 wxCHECK_RET( aBusEntry != nullptr, "SCH_BUS_ENTRY_BASE* is NULL" );
2316
2317 if( aBusEntry->GetLayer() == LAYER_WIRE )
2318 m_out->Print( 0, "Entry Wire Line\n\t%-4d %-4d %-4d %-4d\n",
2319 Iu2Mils( aBusEntry->GetPosition().x ),
2320 Iu2Mils( aBusEntry->GetPosition().y ),
2321 Iu2Mils( aBusEntry->GetEnd().x ), Iu2Mils( aBusEntry->GetEnd().y ) );
2322 else
2323 m_out->Print( 0, "Entry Bus Bus\n\t%-4d %-4d %-4d %-4d\n",
2324 Iu2Mils( aBusEntry->GetPosition().x ),
2325 Iu2Mils( aBusEntry->GetPosition().y ),
2326 Iu2Mils( aBusEntry->GetEnd().x ), Iu2Mils( aBusEntry->GetEnd().y ) );
2327 }
2328
2329
saveLine(SCH_LINE * aLine)2330 void SCH_LEGACY_PLUGIN::saveLine( SCH_LINE* aLine )
2331 {
2332 wxCHECK_RET( aLine != nullptr, "SCH_LINE* is NULL" );
2333
2334 const char* layer = "Notes";
2335 const char* width = "Line";
2336
2337 if( aLine->GetLayer() == LAYER_WIRE )
2338 layer = "Wire";
2339 else if( aLine->GetLayer() == LAYER_BUS )
2340 layer = "Bus";
2341
2342 m_out->Print( 0, "Wire %s %s", layer, width );
2343
2344 // Write line style (width, type, color) only for non default values
2345 if( aLine->IsGraphicLine() )
2346 {
2347 if( aLine->GetLineSize() != 0 )
2348 m_out->Print( 0, " %s %d", T_WIDTH, Iu2Mils( aLine->GetLineSize() ) );
2349
2350 if( aLine->GetLineStyle() != aLine->GetDefaultStyle() )
2351 m_out->Print( 0, " %s %s", T_STYLE,
2352 SCH_LINE::GetLineStyleName( aLine->GetLineStyle() ) );
2353
2354 if( aLine->GetLineColor() != COLOR4D::UNSPECIFIED )
2355 m_out->Print( 0, " %s",
2356 TO_UTF8( aLine->GetLineColor().ToColour().GetAsString( wxC2S_CSS_SYNTAX ) ) );
2357 }
2358
2359 m_out->Print( 0, "\n" );
2360
2361 m_out->Print( 0, "\t%-4d %-4d %-4d %-4d",
2362 Iu2Mils( aLine->GetStartPoint().x ), Iu2Mils( aLine->GetStartPoint().y ),
2363 Iu2Mils( aLine->GetEndPoint().x ), Iu2Mils( aLine->GetEndPoint().y ) );
2364
2365 m_out->Print( 0, "\n");
2366 }
2367
2368
saveText(SCH_TEXT * aText)2369 void SCH_LEGACY_PLUGIN::saveText( SCH_TEXT* aText )
2370 {
2371 wxCHECK_RET( aText != nullptr, "SCH_TEXT* is NULL" );
2372
2373 const char* italics = "~";
2374 const char* textType = "Notes";
2375
2376 if( aText->IsItalic() )
2377 italics = "Italic";
2378
2379 wxString text = aText->GetText();
2380
2381 SCH_LAYER_ID layer = aText->GetLayer();
2382
2383 if( layer == LAYER_NOTES || layer == LAYER_LOCLABEL )
2384 {
2385 if( layer == LAYER_NOTES )
2386 {
2387 // For compatibility reasons, the text must be saved in only one text line
2388 // so replace all EOLs with \\n
2389 text.Replace( wxT( "\n" ), wxT( "\\n" ) );
2390
2391 // Here we should have no CR or LF character in line
2392 // This is not always the case if a multiline text was copied (using a copy/paste
2393 // function) from a text that uses E.O.L characters that differs from the current
2394 // EOL format. This is mainly the case under Linux using LF symbol when copying
2395 // a text from Windows (using CRLF symbol) so we must just remove the extra CR left
2396 // (or LF left under MacOSX)
2397 for( unsigned ii = 0; ii < text.Len(); )
2398 {
2399 if( text[ii] == 0x0A || text[ii] == 0x0D )
2400 text.erase( ii, 1 );
2401 else
2402 ii++;
2403 }
2404 }
2405 else
2406 {
2407 textType = "Label";
2408 }
2409
2410 // Local labels must have their spin style inverted for left and right
2411 int spinStyle = static_cast<int>( aText->GetLabelSpinStyle() );
2412
2413 if( spinStyle == 0 )
2414 spinStyle = 2;
2415 else if( spinStyle == 2 )
2416 spinStyle = 0;
2417
2418 m_out->Print( 0, "Text %s %-4d %-4d %-4d %-4d %s %d\n%s\n", textType,
2419 Iu2Mils( aText->GetPosition().x ), Iu2Mils( aText->GetPosition().y ),
2420 spinStyle,
2421 Iu2Mils( aText->GetTextWidth() ),
2422 italics, Iu2Mils( aText->GetTextThickness() ), TO_UTF8( text ) );
2423 }
2424 else if( layer == LAYER_GLOBLABEL || layer == LAYER_HIERLABEL )
2425 {
2426 textType = ( layer == LAYER_GLOBLABEL ) ? "GLabel" : "HLabel";
2427
2428 auto shapeLabelIt = sheetLabelNames.find( aText->GetShape() );
2429 wxCHECK_RET( shapeLabelIt != sheetLabelNames.end(), "Shape not found in names list" );
2430
2431 m_out->Print( 0, "Text %s %-4d %-4d %-4d %-4d %s %s %d\n%s\n", textType,
2432 Iu2Mils( aText->GetPosition().x ), Iu2Mils( aText->GetPosition().y ),
2433 static_cast<int>( aText->GetLabelSpinStyle() ),
2434 Iu2Mils( aText->GetTextWidth() ),
2435 shapeLabelIt->second,
2436 italics,
2437 Iu2Mils( aText->GetTextThickness() ), TO_UTF8( text ) );
2438 }
2439 }
2440
2441
saveBusAlias(std::shared_ptr<BUS_ALIAS> aAlias)2442 void SCH_LEGACY_PLUGIN::saveBusAlias( std::shared_ptr<BUS_ALIAS> aAlias )
2443 {
2444 wxCHECK_RET( aAlias != nullptr, "BUS_ALIAS* is NULL" );
2445
2446 wxString members = boost::algorithm::join( aAlias->Members(), " " );
2447
2448 m_out->Print( 0, "BusAlias %s %s\n",
2449 TO_UTF8( aAlias->GetName() ), TO_UTF8( members ) );
2450 }
2451
2452
2453 int SCH_LEGACY_PLUGIN_CACHE::s_modHash = 1; // starts at 1 and goes up
2454 std::mutex SCH_LEGACY_PLUGIN_CACHE::s_modHashMutex;
2455
2456
SCH_LEGACY_PLUGIN_CACHE(const wxString & aFullPathAndFileName)2457 SCH_LEGACY_PLUGIN_CACHE::SCH_LEGACY_PLUGIN_CACHE( const wxString& aFullPathAndFileName ) :
2458 m_fileName( aFullPathAndFileName ),
2459 m_libFileName( aFullPathAndFileName ),
2460 m_isWritable( true ),
2461 m_isModified( false )
2462 {
2463 m_versionMajor = -1;
2464 m_versionMinor = -1;
2465 m_libType = SCH_LIB_TYPE::LT_EESCHEMA;
2466 }
2467
2468
~SCH_LEGACY_PLUGIN_CACHE()2469 SCH_LEGACY_PLUGIN_CACHE::~SCH_LEGACY_PLUGIN_CACHE()
2470 {
2471 // When the cache is destroyed, all of the alias objects on the heap should be deleted.
2472 for( auto& symbol : m_symbols )
2473 delete symbol.second;
2474
2475 m_symbols.clear();
2476 }
2477
2478
2479 // If m_libFileName is a symlink follow it to the real source file
GetRealFile() const2480 wxFileName SCH_LEGACY_PLUGIN_CACHE::GetRealFile() const
2481 {
2482 wxFileName fn( m_libFileName );
2483 WX_FILENAME::ResolvePossibleSymlinks( fn );
2484 return fn;
2485 }
2486
2487
GetLibModificationTime()2488 wxDateTime SCH_LEGACY_PLUGIN_CACHE::GetLibModificationTime()
2489 {
2490 wxFileName fn = GetRealFile();
2491
2492 // update the writable flag while we have a wxFileName, in a network this
2493 // is possibly quite dynamic anyway.
2494 m_isWritable = fn.IsFileWritable();
2495
2496 return fn.GetModificationTime();
2497 }
2498
2499
IsFile(const wxString & aFullPathAndFileName) const2500 bool SCH_LEGACY_PLUGIN_CACHE::IsFile( const wxString& aFullPathAndFileName ) const
2501 {
2502 return m_fileName == aFullPathAndFileName;
2503 }
2504
2505
IsFileChanged() const2506 bool SCH_LEGACY_PLUGIN_CACHE::IsFileChanged() const
2507 {
2508 wxFileName fn = GetRealFile();
2509
2510 if( m_fileModTime.IsValid() && fn.IsOk() && fn.FileExists() )
2511 return fn.GetModificationTime() != m_fileModTime;
2512
2513 return false;
2514 }
2515
2516
removeSymbol(LIB_SYMBOL * aSymbol)2517 LIB_SYMBOL* SCH_LEGACY_PLUGIN_CACHE::removeSymbol( LIB_SYMBOL* aSymbol )
2518 {
2519 wxCHECK_MSG( aSymbol != nullptr, nullptr, "NULL pointer cannot be removed from library." );
2520
2521 LIB_SYMBOL* firstChild = nullptr;
2522 LIB_SYMBOL_MAP::iterator it = m_symbols.find( aSymbol->GetName() );
2523
2524 if( it == m_symbols.end() )
2525 return nullptr;
2526
2527 // If the entry pointer doesn't match the name it is mapped to in the library, we
2528 // have done something terribly wrong.
2529 wxCHECK_MSG( *it->second == aSymbol, nullptr,
2530 "Pointer mismatch while attempting to remove alias entry <" + aSymbol->GetName() +
2531 "> from library cache <" + m_libFileName.GetName() + ">." );
2532
2533 // If the symbol is a root symbol used by other symbols find the first alias that uses
2534 // the root symbol and make it the new root.
2535 if( aSymbol->IsRoot() )
2536 {
2537 for( auto entry : m_symbols )
2538 {
2539 if( entry.second->IsAlias()
2540 && entry.second->GetParent().lock() == aSymbol->SharedPtr() )
2541 {
2542 firstChild = entry.second;
2543 break;
2544 }
2545 }
2546
2547 if( firstChild )
2548 {
2549 for( LIB_ITEM& drawItem : aSymbol->GetDrawItems() )
2550 {
2551 if( drawItem.Type() == LIB_FIELD_T )
2552 {
2553 LIB_FIELD& field = static_cast<LIB_FIELD&>( drawItem );
2554
2555 if( firstChild->FindField( field.GetCanonicalName() ) )
2556 continue;
2557 }
2558
2559 LIB_ITEM* newItem = (LIB_ITEM*) drawItem.Clone();
2560 drawItem.SetParent( firstChild );
2561 firstChild->AddDrawItem( newItem );
2562 }
2563
2564 // Reparent the remaining aliases.
2565 for( auto entry : m_symbols )
2566 {
2567 if( entry.second->IsAlias()
2568 && entry.second->GetParent().lock() == aSymbol->SharedPtr() )
2569 entry.second->SetParent( firstChild );
2570 }
2571 }
2572 }
2573
2574 m_symbols.erase( it );
2575 delete aSymbol;
2576 m_isModified = true;
2577 SCH_LEGACY_PLUGIN_CACHE::IncrementModifyHash();
2578 return firstChild;
2579 }
2580
2581
AddSymbol(const LIB_SYMBOL * aSymbol)2582 void SCH_LEGACY_PLUGIN_CACHE::AddSymbol( const LIB_SYMBOL* aSymbol )
2583 {
2584 // aSymbol is cloned in SYMBOL_LIB::AddSymbol(). The cache takes ownership of aSymbol.
2585 wxString name = aSymbol->GetName();
2586 LIB_SYMBOL_MAP::iterator it = m_symbols.find( name );
2587
2588 if( it != m_symbols.end() )
2589 {
2590 removeSymbol( it->second );
2591 }
2592
2593 m_symbols[ name ] = const_cast< LIB_SYMBOL* >( aSymbol );
2594 m_isModified = true;
2595 SCH_LEGACY_PLUGIN_CACHE::IncrementModifyHash();
2596 }
2597
2598
Load()2599 void SCH_LEGACY_PLUGIN_CACHE::Load()
2600 {
2601 if( !m_libFileName.FileExists() )
2602 {
2603 THROW_IO_ERROR( wxString::Format( _( "Library file '%s' not found." ),
2604 m_libFileName.GetFullPath() ) );
2605 }
2606
2607 wxCHECK_RET( m_libFileName.IsAbsolute(),
2608 wxString::Format( "Cannot use relative file paths in legacy plugin to "
2609 "open library '%s'.", m_libFileName.GetFullPath() ) );
2610
2611 wxLogTrace( traceSchLegacyPlugin, "Loading legacy symbol file '%s'",
2612 m_libFileName.GetFullPath() );
2613
2614 FILE_LINE_READER reader( m_libFileName.GetFullPath() );
2615
2616 if( !reader.ReadLine() )
2617 THROW_IO_ERROR( _( "Unexpected end of file." ) );
2618
2619 const char* line = reader.Line();
2620
2621 if( !strCompare( "EESchema-LIBRARY Version", line, &line ) )
2622 {
2623 // Old .sym files (which are libraries with only one symbol, used to store and reuse shapes)
2624 // EESchema-LIB Version x.x SYMBOL. They are valid files.
2625 if( !strCompare( "EESchema-LIB Version", line, &line ) )
2626 SCH_PARSE_ERROR( "file is not a valid symbol or symbol library file", reader, line );
2627 }
2628
2629 m_versionMajor = parseInt( reader, line, &line );
2630
2631 if( *line != '.' )
2632 SCH_PARSE_ERROR( "invalid file version formatting in header", reader, line );
2633
2634 line++;
2635
2636 m_versionMinor = parseInt( reader, line, &line );
2637
2638 if( m_versionMajor < 1 || m_versionMinor < 0 || m_versionMinor > 99 )
2639 SCH_PARSE_ERROR( "invalid file version in header", reader, line );
2640
2641 // Check if this is a symbol library which is the same as a symbol library but without
2642 // any alias, documentation, footprint filters, etc.
2643 if( strCompare( "SYMBOL", line, &line ) )
2644 {
2645 // Symbol files add date and time stamp info to the header.
2646 m_libType = SCH_LIB_TYPE::LT_SYMBOL;
2647
2648 /// @todo Probably should check for a valid date and time stamp even though it's not used.
2649 }
2650 else
2651 {
2652 m_libType = SCH_LIB_TYPE::LT_EESCHEMA;
2653 }
2654
2655 while( reader.ReadLine() )
2656 {
2657 line = reader.Line();
2658
2659 if( *line == '#' || isspace( *line ) ) // Skip comments and blank lines.
2660 continue;
2661
2662 // Headers where only supported in older library file formats.
2663 if( m_libType == SCH_LIB_TYPE::LT_EESCHEMA && strCompare( "$HEADER", line ) )
2664 loadHeader( reader );
2665
2666 if( strCompare( "DEF", line ) )
2667 {
2668 // Read one DEF/ENDDEF symbol entry from library:
2669 LIB_SYMBOL* symbol = LoadPart( reader, m_versionMajor, m_versionMinor, &m_symbols );
2670
2671 m_symbols[ symbol->GetName() ] = symbol;
2672 }
2673 }
2674
2675 SCH_LEGACY_PLUGIN_CACHE::IncrementModifyHash();
2676
2677 // Remember the file modification time of library file when the
2678 // cache snapshot was made, so that in a networked environment we will
2679 // reload the cache as needed.
2680 m_fileModTime = GetLibModificationTime();
2681
2682 if( USE_OLD_DOC_FILE_FORMAT( m_versionMajor, m_versionMinor ) )
2683 loadDocs();
2684 }
2685
2686
loadDocs()2687 void SCH_LEGACY_PLUGIN_CACHE::loadDocs()
2688 {
2689 const char* line;
2690 wxString text;
2691 wxString aliasName;
2692 wxFileName fn = m_libFileName;
2693 LIB_SYMBOL* symbol = nullptr;;
2694
2695 fn.SetExt( DOC_EXT );
2696
2697 // Not all libraries will have a document file.
2698 if( !fn.FileExists() )
2699 return;
2700
2701 if( !fn.IsFileReadable() )
2702 {
2703 THROW_IO_ERROR( wxString::Format( _( "Insufficient permissions to read library '%s'." ),
2704 fn.GetFullPath() ) );
2705 }
2706
2707 FILE_LINE_READER reader( fn.GetFullPath() );
2708
2709 line = reader.ReadLine();
2710
2711 if( !line )
2712 THROW_IO_ERROR( _( "symbol document library file is empty" ) );
2713
2714 if( !strCompare( DOCFILE_IDENT, line, &line ) )
2715 SCH_PARSE_ERROR( "invalid document library file version formatting in header",
2716 reader, line );
2717
2718 while( reader.ReadLine() )
2719 {
2720 line = reader.Line();
2721
2722 if( *line == '#' ) // Comment line.
2723 continue;
2724
2725 if( !strCompare( "$CMP", line, &line ) != 0 )
2726 SCH_PARSE_ERROR( "$CMP command expected", reader, line );
2727
2728 aliasName = wxString::FromUTF8( line );
2729 aliasName.Trim();
2730 // aliasName = EscapeString( aliasName, CTX_LIBID );
2731
2732 LIB_SYMBOL_MAP::iterator it = m_symbols.find( aliasName );
2733
2734 if( it == m_symbols.end() )
2735 wxLogWarning( "Symbol '%s' not found in library:\n\n"
2736 "'%s'\n\nat line %d offset %d", aliasName, fn.GetFullPath(),
2737 reader.LineNumber(), (int) (line - reader.Line() ) );
2738 else
2739 symbol = it->second;
2740
2741 // Read the current alias associated doc.
2742 // if the alias does not exist, just skip the description
2743 // (Can happen if a .dcm is not synchronized with the corresponding .lib file)
2744 while( reader.ReadLine() )
2745 {
2746 line = reader.Line();
2747
2748 if( !line )
2749 SCH_PARSE_ERROR( "unexpected end of file", reader, line );
2750
2751 if( strCompare( "$ENDCMP", line, &line ) )
2752 break;
2753
2754 text = FROM_UTF8( line + 2 );
2755 // Remove spaces at eol, and eol chars:
2756 text = text.Trim();
2757
2758 switch( line[0] )
2759 {
2760 case 'D':
2761 if( symbol )
2762 symbol->SetDescription( text );
2763 break;
2764
2765 case 'K':
2766 if( symbol )
2767 symbol->SetKeyWords( text );
2768 break;
2769
2770 case 'F':
2771 if( symbol )
2772 symbol->GetFieldById( DATASHEET_FIELD )->SetText( text );
2773 break;
2774
2775 case 0:
2776 case '\n':
2777 case '\r':
2778 case '#':
2779 // Empty line or commment
2780 break;
2781
2782 default:
2783 SCH_PARSE_ERROR( "expected token in symbol definition", reader, line );
2784 }
2785 }
2786 }
2787 }
2788
2789
loadHeader(FILE_LINE_READER & aReader)2790 void SCH_LEGACY_PLUGIN_CACHE::loadHeader( FILE_LINE_READER& aReader )
2791 {
2792 const char* line = aReader.Line();
2793
2794 wxASSERT( strCompare( "$HEADER", line, &line ) );
2795
2796 while( aReader.ReadLine() )
2797 {
2798 line = (char*) aReader;
2799
2800 // The time stamp saved in old library files is not used or saved in the latest
2801 // library file version.
2802 if( strCompare( "TimeStamp", line, &line ) )
2803 continue;
2804 else if( strCompare( "$ENDHEADER", line, &line ) )
2805 return;
2806 }
2807
2808 SCH_PARSE_ERROR( "$ENDHEADER not found", aReader, line );
2809 }
2810
2811
LoadPart(LINE_READER & aReader,int aMajorVersion,int aMinorVersion,LIB_SYMBOL_MAP * aMap)2812 LIB_SYMBOL* SCH_LEGACY_PLUGIN_CACHE::LoadPart( LINE_READER& aReader, int aMajorVersion,
2813 int aMinorVersion, LIB_SYMBOL_MAP* aMap )
2814 {
2815 const char* line = aReader.Line();
2816
2817 while( *line == '#' )
2818 aReader.ReadLine();
2819
2820 if( !strCompare( "DEF", line, &line ) )
2821 SCH_PARSE_ERROR( "invalid symbol definition", aReader, line );
2822
2823 long num;
2824 size_t pos = 4; // "DEF" plus the first space.
2825 wxString utf8Line = wxString::FromUTF8( line );
2826 wxStringTokenizer tokens( utf8Line, " \r\n\t" );
2827
2828 if( tokens.CountTokens() < 8 )
2829 SCH_PARSE_ERROR( "invalid symbol definition", aReader, line );
2830
2831 // Read DEF line:
2832 std::unique_ptr<LIB_SYMBOL> symbol = std::make_unique<LIB_SYMBOL>( wxEmptyString );
2833
2834 wxString name, prefix, tmp;
2835
2836 name = tokens.GetNextToken();
2837 pos += name.size() + 1;
2838
2839 prefix = tokens.GetNextToken();
2840 pos += prefix.size() + 1;
2841
2842 tmp = tokens.GetNextToken();
2843 pos += tmp.size() + 1; // NumOfPins, unused.
2844
2845 tmp = tokens.GetNextToken(); // Pin name offset.
2846
2847 if( !tmp.ToLong( &num ) )
2848 THROW_PARSE_ERROR( "invalid pin offset", aReader.GetSource(), aReader.Line(),
2849 aReader.LineNumber(), pos );
2850
2851 pos += tmp.size() + 1;
2852 symbol->SetPinNameOffset( Mils2Iu( (int)num ) );
2853
2854 tmp = tokens.GetNextToken(); // Show pin numbers.
2855
2856 if( !( tmp == "Y" || tmp == "N") )
2857 THROW_PARSE_ERROR( "expected Y or N", aReader.GetSource(), aReader.Line(),
2858 aReader.LineNumber(), pos );
2859
2860 pos += tmp.size() + 1;
2861 symbol->SetShowPinNumbers( ( tmp == "N" ) ? false : true );
2862
2863 tmp = tokens.GetNextToken(); // Show pin names.
2864
2865 if( !( tmp == "Y" || tmp == "N") )
2866 THROW_PARSE_ERROR( "expected Y or N", aReader.GetSource(), aReader.Line(),
2867 aReader.LineNumber(), pos );
2868
2869 pos += tmp.size() + 1;
2870 symbol->SetShowPinNames( ( tmp == "N" ) ? false : true );
2871
2872 tmp = tokens.GetNextToken(); // Number of units.
2873
2874 if( !tmp.ToLong( &num ) )
2875 THROW_PARSE_ERROR( "invalid unit count", aReader.GetSource(), aReader.Line(),
2876 aReader.LineNumber(), pos );
2877
2878 pos += tmp.size() + 1;
2879 symbol->SetUnitCount( (int)num );
2880
2881 // Ensure m_unitCount is >= 1. Could be read as 0 in old libraries.
2882 if( symbol->GetUnitCount() < 1 )
2883 symbol->SetUnitCount( 1 );
2884
2885 // Copy symbol name and prefix.
2886
2887 // The root alias is added to the alias list by SetName() which is called by SetText().
2888 if( name.IsEmpty() )
2889 {
2890 symbol->SetName( "~" );
2891 }
2892 else if( name[0] != '~' )
2893 {
2894 symbol->SetName( name );
2895 }
2896 else
2897 {
2898 symbol->SetName( name.Right( name.Length() - 1 ) );
2899 symbol->GetValueField().SetVisible( false );
2900 }
2901
2902 // Don't set the library alias, this is determined by the symbol library table.
2903 symbol->SetLibId( LIB_ID( wxEmptyString, symbol->GetName() ) );
2904
2905 LIB_FIELD& reference = symbol->GetReferenceField();
2906
2907 if( prefix == "~" )
2908 {
2909 reference.Empty();
2910 reference.SetVisible( false );
2911 }
2912 else
2913 {
2914 reference.SetText( prefix );
2915 }
2916
2917 // In version 2.2 and earlier, this parameter was a '0' which was just a place holder.
2918 // The was no concept of interchangeable multiple unit symbols.
2919 if( LIB_VERSION( aMajorVersion, aMinorVersion ) > 0
2920 && LIB_VERSION( aMajorVersion, aMinorVersion ) <= LIB_VERSION( 2, 2 ) )
2921 {
2922 // Nothing needs to be set since the default setting for symbols with multiple
2923 // units were never interchangeable. Just parse the 0 an move on.
2924 tmp = tokens.GetNextToken();
2925 pos += tmp.size() + 1;
2926 }
2927 else
2928 {
2929 tmp = tokens.GetNextToken();
2930
2931 if( tmp == "L" )
2932 symbol->LockUnits( true );
2933 else if( tmp == "F" || tmp == "0" )
2934 symbol->LockUnits( false );
2935 else
2936 THROW_PARSE_ERROR( "expected L, F, or 0", aReader.GetSource(), aReader.Line(),
2937 aReader.LineNumber(), pos );
2938
2939 pos += tmp.size() + 1;
2940 }
2941
2942 // There is the optional power symbol flag.
2943 if( tokens.HasMoreTokens() )
2944 {
2945 tmp = tokens.GetNextToken();
2946
2947 if( tmp == "P" )
2948 symbol->SetPower();
2949 else if( tmp == "N" )
2950 symbol->SetNormal();
2951 else
2952 THROW_PARSE_ERROR( "expected P or N", aReader.GetSource(), aReader.Line(),
2953 aReader.LineNumber(), pos );
2954 }
2955
2956 line = aReader.ReadLine();
2957
2958 // Read lines until "ENDDEF" is found.
2959 while( line )
2960 {
2961 if( *line == '#' ) // Comment
2962 ;
2963 else if( strCompare( "Ti", line, &line ) ) // Modification date is ignored.
2964 continue;
2965 else if( strCompare( "ALIAS", line, &line ) ) // Aliases
2966 loadAliases( symbol, aReader, aMap );
2967 else if( *line == 'F' ) // Fields
2968 loadField( symbol, aReader );
2969 else if( strCompare( "DRAW", line, &line ) ) // Drawing objects.
2970 loadDrawEntries( symbol, aReader, aMajorVersion, aMinorVersion );
2971 else if( strCompare( "$FPLIST", line, &line ) ) // Footprint filter list
2972 loadFootprintFilters( symbol, aReader );
2973 else if( strCompare( "ENDDEF", line, &line ) ) // End of symbol description
2974 {
2975 return symbol.release();
2976 }
2977
2978 line = aReader.ReadLine();
2979 }
2980
2981 SCH_PARSE_ERROR( "missing ENDDEF", aReader, line );
2982 }
2983
2984
loadAliases(std::unique_ptr<LIB_SYMBOL> & aSymbol,LINE_READER & aReader,LIB_SYMBOL_MAP * aMap)2985 void SCH_LEGACY_PLUGIN_CACHE::loadAliases( std::unique_ptr<LIB_SYMBOL>& aSymbol,
2986 LINE_READER& aReader,
2987 LIB_SYMBOL_MAP* aMap )
2988 {
2989 wxString newAliasName;
2990 const char* line = aReader.Line();
2991
2992 wxCHECK_RET( strCompare( "ALIAS", line, &line ), "Invalid ALIAS section" );
2993
2994 wxString utf8Line = wxString::FromUTF8( line );
2995 wxStringTokenizer tokens( utf8Line, " \r\n\t" );
2996
2997 // Parse the ALIAS list.
2998 while( tokens.HasMoreTokens() )
2999 {
3000 newAliasName = tokens.GetNextToken();
3001
3002 if( aMap )
3003 {
3004 LIB_SYMBOL* newSymbol = new LIB_SYMBOL( newAliasName );
3005
3006 // Inherit the parent mandatory field attributes.
3007 for( int id = 0; id < MANDATORY_FIELDS; ++id )
3008 {
3009 LIB_FIELD* field = newSymbol->GetFieldById( id );
3010
3011 // the MANDATORY_FIELDS are exactly that in RAM.
3012 wxASSERT( field );
3013
3014 LIB_FIELD* parentField = aSymbol->GetFieldById( id );
3015
3016 wxASSERT( parentField );
3017
3018 *field = *parentField;
3019
3020 if( id == VALUE_FIELD )
3021 field->SetText( newAliasName );
3022
3023 field->SetParent( newSymbol );
3024 }
3025
3026 newSymbol->SetParent( aSymbol.get() );
3027
3028 // This will prevent duplicate aliases.
3029 (*aMap)[ newSymbol->GetName() ] = newSymbol;
3030 }
3031 }
3032 }
3033
3034
loadField(std::unique_ptr<LIB_SYMBOL> & aSymbol,LINE_READER & aReader)3035 void SCH_LEGACY_PLUGIN_CACHE::loadField( std::unique_ptr<LIB_SYMBOL>& aSymbol,
3036 LINE_READER& aReader )
3037 {
3038 const char* line = aReader.Line();
3039
3040 wxCHECK_RET( *line == 'F', "Invalid field line" );
3041
3042 wxString text;
3043 int id;
3044
3045 if( sscanf( line + 1, "%d", &id ) != 1 || id < 0 )
3046 SCH_PARSE_ERROR( "invalid field ID", aReader, line + 1 );
3047
3048 LIB_FIELD* field;
3049
3050 if( id >= 0 && id < MANDATORY_FIELDS )
3051 {
3052 field = aSymbol->GetFieldById( id );
3053
3054 // this will fire only if somebody broke a constructor or editor.
3055 // MANDATORY_FIELDS are always present in ram resident symbols, no
3056 // exceptions, and they always have their names set, even fixed fields.
3057 wxASSERT( field );
3058 }
3059 else
3060 {
3061 field = new LIB_FIELD( aSymbol.get(), id );
3062 aSymbol->AddDrawItem( field, false );
3063 }
3064
3065 // Skip to the first double quote.
3066 while( *line != '"' && *line != 0 )
3067 line++;
3068
3069 if( *line == 0 )
3070 SCH_PARSE_ERROR( _( "unexpected end of line" ), aReader, line );
3071
3072 parseQuotedString( text, aReader, line, &line, true );
3073
3074 // Doctor the *.lib file field which has a "~" in blank fields. New saves will
3075 // not save like this.
3076 if( text.size() == 1 && text[0] == '~' )
3077 field->SetText( wxEmptyString );
3078 else
3079 field->SetText( ConvertToNewOverbarNotation( text ) );
3080
3081 wxPoint pos;
3082
3083 pos.x = Mils2Iu( parseInt( aReader, line, &line ) );
3084 pos.y = Mils2Iu( parseInt( aReader, line, &line ) );
3085 field->SetPosition( pos );
3086
3087 wxSize textSize;
3088
3089 textSize.x = textSize.y = Mils2Iu( parseInt( aReader, line, &line ) );
3090 field->SetTextSize( textSize );
3091
3092 char textOrient = parseChar( aReader, line, &line );
3093
3094 if( textOrient == 'H' )
3095 field->SetTextAngle( TEXT_ANGLE_HORIZ );
3096 else if( textOrient == 'V' )
3097 field->SetTextAngle( TEXT_ANGLE_VERT );
3098 else
3099 SCH_PARSE_ERROR( "invalid field text orientation parameter", aReader, line );
3100
3101 char textVisible = parseChar( aReader, line, &line );
3102
3103 if( textVisible == 'V' )
3104 field->SetVisible( true );
3105 else if ( textVisible == 'I' )
3106 field->SetVisible( false );
3107 else
3108 SCH_PARSE_ERROR( "invalid field text visibility parameter", aReader, line );
3109
3110 // It may be technically correct to use the library version to determine if the field text
3111 // attributes are present. If anyone knows if that is valid and what version that would be,
3112 // please change this to test the library version rather than an EOL or the quoted string
3113 // of the field name.
3114 if( *line != 0 && *line != '"' )
3115 {
3116 char textHJustify = parseChar( aReader, line, &line );
3117
3118 if( textHJustify == 'C' )
3119 field->SetHorizJustify( GR_TEXT_HJUSTIFY_CENTER );
3120 else if( textHJustify == 'L' )
3121 field->SetHorizJustify( GR_TEXT_HJUSTIFY_LEFT );
3122 else if( textHJustify == 'R' )
3123 field->SetHorizJustify( GR_TEXT_HJUSTIFY_RIGHT );
3124 else
3125 SCH_PARSE_ERROR( "invalid field text horizontal justification", aReader, line );
3126
3127 wxString attributes;
3128
3129 parseUnquotedString( attributes, aReader, line, &line );
3130
3131 size_t attrSize = attributes.size();
3132
3133 if( !(attrSize == 3 || attrSize == 1 ) )
3134 SCH_PARSE_ERROR( "invalid field text attributes size", aReader, line );
3135
3136 switch( (wxChar) attributes[0] )
3137 {
3138 case 'C': field->SetVertJustify( GR_TEXT_VJUSTIFY_CENTER ); break;
3139 case 'B': field->SetVertJustify( GR_TEXT_VJUSTIFY_BOTTOM ); break;
3140 case 'T': field->SetVertJustify( GR_TEXT_VJUSTIFY_TOP ); break;
3141 default: SCH_PARSE_ERROR( "invalid field text vertical justification", aReader, line );
3142 }
3143
3144 if( attrSize == 3 )
3145 {
3146 wxChar attr_1 = attributes[1];
3147 wxChar attr_2 = attributes[2];
3148
3149 if( attr_1 == 'I' ) // Italic
3150 field->SetItalic( true );
3151 else if( attr_1 != 'N' ) // No italics is default, check for error.
3152 SCH_PARSE_ERROR( "invalid field text italic parameter", aReader, line );
3153
3154 if ( attr_2 == 'B' ) // Bold
3155 field->SetBold( true );
3156 else if( attr_2 != 'N' ) // No bold is default, check for error.
3157 SCH_PARSE_ERROR( "invalid field text bold parameter", aReader, line );
3158 }
3159 }
3160
3161 // Fields in RAM must always have names.
3162 if( id >= 0 && id < MANDATORY_FIELDS )
3163 {
3164 // Fields in RAM must always have names, because we are trying to get
3165 // less dependent on field ids and more dependent on names.
3166 // Plus assumptions are made in the field editors.
3167 field->m_name = TEMPLATE_FIELDNAME::GetDefaultFieldName( id );
3168
3169 // Ensure the VALUE field = the symbol name (can be not the case
3170 // with malformed libraries: edited by hand, or converted from other tools)
3171 if( id == VALUE_FIELD )
3172 field->SetText( aSymbol->GetName() );
3173 }
3174 else
3175 {
3176 parseQuotedString( field->m_name, aReader, line, &line, true ); // Optional.
3177 }
3178 }
3179
3180
loadDrawEntries(std::unique_ptr<LIB_SYMBOL> & aSymbol,LINE_READER & aReader,int aMajorVersion,int aMinorVersion)3181 void SCH_LEGACY_PLUGIN_CACHE::loadDrawEntries( std::unique_ptr<LIB_SYMBOL>& aSymbol,
3182 LINE_READER& aReader,
3183 int aMajorVersion,
3184 int aMinorVersion )
3185 {
3186 const char* line = aReader.Line();
3187
3188 wxCHECK_RET( strCompare( "DRAW", line, &line ), "Invalid DRAW section" );
3189
3190 line = aReader.ReadLine();
3191
3192 while( line )
3193 {
3194 if( strCompare( "ENDDRAW", line, &line ) )
3195 {
3196 aSymbol->GetDrawItems().sort();
3197 return;
3198 }
3199
3200 switch( line[0] )
3201 {
3202 case 'A': // Arc
3203 aSymbol->AddDrawItem( loadArc( aSymbol, aReader ), false );
3204 break;
3205
3206 case 'C': // Circle
3207 aSymbol->AddDrawItem( loadCircle( aSymbol, aReader ), false );
3208 break;
3209
3210 case 'T': // Text
3211 aSymbol->AddDrawItem( loadText( aSymbol, aReader, aMajorVersion,
3212 aMinorVersion ), false );
3213 break;
3214
3215 case 'S': // Square
3216 aSymbol->AddDrawItem( loadRect( aSymbol, aReader ), false );
3217 break;
3218
3219 case 'X': // Pin Description
3220 aSymbol->AddDrawItem( loadPin( aSymbol, aReader ), false );
3221 break;
3222
3223 case 'P': // Polyline
3224 aSymbol->AddDrawItem( loadPolyLine( aSymbol, aReader ), false );
3225 break;
3226
3227 case 'B': // Bezier Curves
3228 aSymbol->AddDrawItem( loadBezier( aSymbol, aReader ), false );
3229 break;
3230
3231 case '#': // Comment
3232 case '\n': // Empty line
3233 case '\r':
3234 case 0:
3235 break;
3236
3237 default:
3238 SCH_PARSE_ERROR( "undefined DRAW entry", aReader, line );
3239 }
3240
3241 line = aReader.ReadLine();
3242 }
3243
3244 SCH_PARSE_ERROR( "File ended prematurely loading symbol draw element.", aReader, line );
3245 }
3246
3247
parseFillMode(LINE_READER & aReader,const char * aLine,const char ** aOutput)3248 FILL_T SCH_LEGACY_PLUGIN_CACHE::parseFillMode( LINE_READER& aReader, const char* aLine,
3249 const char** aOutput )
3250 {
3251 switch ( parseChar( aReader, aLine, aOutput ) )
3252 {
3253 case 'F': return FILL_T::FILLED_SHAPE;
3254 case 'f': return FILL_T::FILLED_WITH_BG_BODYCOLOR;
3255 case 'N': return FILL_T::NO_FILL;
3256 default: SCH_PARSE_ERROR( "invalid fill type, expected f, F, or N", aReader, aLine );
3257 }
3258
3259 // This will never be reached but quiets the compiler warnings
3260 return FILL_T::NO_FILL;
3261 }
3262
3263
loadArc(std::unique_ptr<LIB_SYMBOL> & aSymbol,LINE_READER & aReader)3264 LIB_SHAPE* SCH_LEGACY_PLUGIN_CACHE::loadArc( std::unique_ptr<LIB_SYMBOL>& aSymbol,
3265 LINE_READER& aReader )
3266 {
3267 const char* line = aReader.Line();
3268
3269 wxCHECK_MSG( strCompare( "A", line, &line ), nullptr, "Invalid arc definition" );
3270
3271 LIB_SHAPE* arc = new LIB_SHAPE( aSymbol.get(), SHAPE_T::ARC );
3272
3273 wxPoint center;
3274
3275 center.x = Mils2Iu( parseInt( aReader, line, &line ) );
3276 center.y = Mils2Iu( parseInt( aReader, line, &line ) );
3277
3278 arc->SetPosition( center );
3279
3280 int radius = Mils2Iu( parseInt( aReader, line, &line ) );
3281 int angle1 = parseInt( aReader, line, &line );
3282 int angle2 = parseInt( aReader, line, &line );
3283
3284 NORMALIZE_ANGLE_POS( angle1 );
3285 NORMALIZE_ANGLE_POS( angle2 );
3286
3287 arc->SetUnit( parseInt( aReader, line, &line ) );
3288 arc->SetConvert( parseInt( aReader, line, &line ) );
3289 arc->SetWidth( Mils2Iu( parseInt( aReader, line, &line ) ) );
3290
3291 // Old libraries (version <= 2.2) do not have always this FILL MODE param
3292 // when fill mode is no fill (default mode).
3293 if( *line != 0 )
3294 arc->SetFillMode( parseFillMode( aReader, line, &line ) );
3295
3296 // Actual Coordinates of arc ends are read from file
3297 if( *line != 0 )
3298 {
3299 wxPoint arcStart, arcEnd;
3300
3301 arcStart.x = Mils2Iu( parseInt( aReader, line, &line ) );
3302 arcStart.y = Mils2Iu( parseInt( aReader, line, &line ) );
3303 arcEnd.x = Mils2Iu( parseInt( aReader, line, &line ) );
3304 arcEnd.y = Mils2Iu( parseInt( aReader, line, &line ) );
3305
3306 arc->SetStart( arcStart );
3307 arc->SetEnd( arcEnd );
3308 }
3309 else
3310 {
3311 // Actual Coordinates of arc ends are not read from file
3312 // (old library), calculate them
3313 wxPoint arcStart( radius, 0 );
3314 wxPoint arcEnd( radius, 0 );
3315
3316 RotatePoint( &arcStart.x, &arcStart.y, -angle1 );
3317 arcStart += arc->GetCenter();
3318 arc->SetStart( arcStart );
3319 RotatePoint( &arcEnd.x, &arcEnd.y, -angle2 );
3320 arcEnd += arc->GetCenter();
3321 arc->SetEnd( arcEnd );
3322 }
3323
3324 /**
3325 * This accounts for an oddity in the old library format, where the symbol is overdefined.
3326 * The previous draw (based on wxwidgets) used start point and end point and always drew
3327 * counter-clockwise. The new GAL draw takes center, radius and start/end angles. All of
3328 * these points were stored in the file, so we need to mimic the swapping of start/end
3329 * points rather than using the stored angles in order to properly map edge cases.
3330 */
3331 if( !TRANSFORM().MapAngles( &angle1, &angle2 ) )
3332 {
3333 wxPoint temp = arc->GetStart();
3334 arc->SetStart( arc->GetEnd() );
3335 arc->SetEnd( temp );
3336 }
3337
3338 return arc;
3339 }
3340
3341
loadCircle(std::unique_ptr<LIB_SYMBOL> & aSymbol,LINE_READER & aReader)3342 LIB_SHAPE* SCH_LEGACY_PLUGIN_CACHE::loadCircle( std::unique_ptr<LIB_SYMBOL>& aSymbol,
3343 LINE_READER& aReader )
3344 {
3345 const char* line = aReader.Line();
3346
3347 wxCHECK_MSG( strCompare( "C", line, &line ), nullptr, "Invalid circle definition" );
3348
3349 LIB_SHAPE* circle = new LIB_SHAPE( aSymbol.get(), SHAPE_T::CIRCLE );
3350
3351 wxPoint center;
3352
3353 center.x = Mils2Iu( parseInt( aReader, line, &line ) );
3354 center.y = Mils2Iu( parseInt( aReader, line, &line ) );
3355
3356 int radius = Mils2Iu( parseInt( aReader, line, &line ) );
3357
3358 circle->SetStart( center );
3359 circle->SetEnd( wxPoint( center.x + radius, center.y ) );
3360 circle->SetUnit( parseInt( aReader, line, &line ) );
3361 circle->SetConvert( parseInt( aReader, line, &line ) );
3362 circle->SetWidth( Mils2Iu( parseInt( aReader, line, &line ) ) );
3363
3364 if( *line != 0 )
3365 circle->SetFillMode( parseFillMode( aReader, line, &line ) );
3366
3367 return circle;
3368 }
3369
3370
loadText(std::unique_ptr<LIB_SYMBOL> & aSymbol,LINE_READER & aReader,int aMajorVersion,int aMinorVersion)3371 LIB_TEXT* SCH_LEGACY_PLUGIN_CACHE::loadText( std::unique_ptr<LIB_SYMBOL>& aSymbol,
3372 LINE_READER& aReader,
3373 int aMajorVersion,
3374 int aMinorVersion )
3375 {
3376 const char* line = aReader.Line();
3377
3378 wxCHECK_MSG( strCompare( "T", line, &line ), nullptr, "Invalid LIB_TEXT definition" );
3379
3380 LIB_TEXT* text = new LIB_TEXT( aSymbol.get() );
3381
3382 text->SetTextAngle( (double) parseInt( aReader, line, &line ) );
3383
3384 wxPoint center;
3385
3386 center.x = Mils2Iu( parseInt( aReader, line, &line ) );
3387 center.y = Mils2Iu( parseInt( aReader, line, &line ) );
3388 text->SetPosition( center );
3389
3390 wxSize size;
3391
3392 size.x = size.y = Mils2Iu( parseInt( aReader, line, &line ) );
3393 text->SetTextSize( size );
3394 text->SetVisible( !parseInt( aReader, line, &line ) );
3395 text->SetUnit( parseInt( aReader, line, &line ) );
3396 text->SetConvert( parseInt( aReader, line, &line ) );
3397
3398 wxString str;
3399
3400 // If quoted string loading fails, load as not quoted string.
3401 if( *line == '"' )
3402 {
3403 parseQuotedString( str, aReader, line, &line );
3404
3405 str = ConvertToNewOverbarNotation( str );
3406 }
3407 else
3408 {
3409 parseUnquotedString( str, aReader, line, &line );
3410
3411 // In old libs, "spaces" are replaced by '~' in unquoted strings:
3412 str.Replace( "~", " " );
3413 }
3414
3415 if( !str.IsEmpty() )
3416 {
3417 // convert two apostrophes back to double quote
3418 str.Replace( "''", "\"" );
3419 }
3420
3421 text->SetText( str );
3422
3423 // Here things are murky and not well defined. At some point it appears the format
3424 // was changed to add text properties. However rather than add the token to the end of
3425 // the text definition, it was added after the string and no mention if the file
3426 // verion was bumped or not so this code make break on very old symbol libraries.
3427 //
3428 // Update: apparently even in the latest version this can be different so added a test
3429 // for end of line before checking for the text properties.
3430 if( LIB_VERSION( aMajorVersion, aMinorVersion ) > 0
3431 && LIB_VERSION( aMajorVersion, aMinorVersion ) > LIB_VERSION( 2, 0 ) && !is_eol( *line ) )
3432 {
3433 if( strCompare( "Italic", line, &line ) )
3434 text->SetItalic( true );
3435 else if( !strCompare( "Normal", line, &line ) )
3436 SCH_PARSE_ERROR( "invalid text stype, expected 'Normal' or 'Italic'", aReader, line );
3437
3438 if( parseInt( aReader, line, &line ) > 0 )
3439 text->SetBold( true );
3440
3441 // Some old libaries version > 2.0 do not have these options for text justification:
3442 if( !is_eol( *line ) )
3443 {
3444 switch( parseChar( aReader, line, &line ) )
3445 {
3446 case 'L': text->SetHorizJustify( GR_TEXT_HJUSTIFY_LEFT ); break;
3447 case 'C': text->SetHorizJustify( GR_TEXT_HJUSTIFY_CENTER ); break;
3448 case 'R': text->SetHorizJustify( GR_TEXT_HJUSTIFY_RIGHT ); break;
3449 default: SCH_PARSE_ERROR( "invalid horizontal text justication; expected L, C, or R",
3450 aReader, line );
3451 }
3452
3453 switch( parseChar( aReader, line, &line ) )
3454 {
3455 case 'T': text->SetVertJustify( GR_TEXT_VJUSTIFY_TOP ); break;
3456 case 'C': text->SetVertJustify( GR_TEXT_VJUSTIFY_CENTER ); break;
3457 case 'B': text->SetVertJustify( GR_TEXT_VJUSTIFY_BOTTOM ); break;
3458 default: SCH_PARSE_ERROR( "invalid vertical text justication; expected T, C, or B",
3459 aReader, line );
3460 }
3461 }
3462 }
3463
3464 return text;
3465 }
3466
3467
loadRect(std::unique_ptr<LIB_SYMBOL> & aSymbol,LINE_READER & aReader)3468 LIB_SHAPE* SCH_LEGACY_PLUGIN_CACHE::loadRect( std::unique_ptr<LIB_SYMBOL>& aSymbol,
3469 LINE_READER& aReader )
3470 {
3471 const char* line = aReader.Line();
3472
3473 wxCHECK_MSG( strCompare( "S", line, &line ), nullptr, "Invalid rectangle definition" );
3474
3475 LIB_SHAPE* rectangle = new LIB_SHAPE( aSymbol.get(), SHAPE_T::RECT );
3476
3477 wxPoint pos;
3478
3479 pos.x = Mils2Iu( parseInt( aReader, line, &line ) );
3480 pos.y = Mils2Iu( parseInt( aReader, line, &line ) );
3481 rectangle->SetPosition( pos );
3482
3483 wxPoint end;
3484
3485 end.x = Mils2Iu( parseInt( aReader, line, &line ) );
3486 end.y = Mils2Iu( parseInt( aReader, line, &line ) );
3487 rectangle->SetEnd( end );
3488
3489 rectangle->SetUnit( parseInt( aReader, line, &line ) );
3490 rectangle->SetConvert( parseInt( aReader, line, &line ) );
3491 rectangle->SetWidth( Mils2Iu( parseInt( aReader, line, &line ) ) );
3492
3493 if( *line != 0 )
3494 rectangle->SetFillMode( parseFillMode( aReader, line, &line ) );
3495
3496 return rectangle;
3497 }
3498
3499
loadPin(std::unique_ptr<LIB_SYMBOL> & aSymbol,LINE_READER & aReader)3500 LIB_PIN* SCH_LEGACY_PLUGIN_CACHE::loadPin( std::unique_ptr<LIB_SYMBOL>& aSymbol,
3501 LINE_READER& aReader )
3502 {
3503 const char* line = aReader.Line();
3504
3505 wxCHECK_MSG( strCompare( "X", line, &line ), nullptr, "Invalid LIB_PIN definition" );
3506
3507 wxString name;
3508 wxString number;
3509
3510 size_t pos = 2; // "X" plus ' ' space character.
3511 wxString tmp;
3512 wxString utf8Line = wxString::FromUTF8( line );
3513 wxStringTokenizer tokens( utf8Line, " \r\n\t" );
3514
3515 if( tokens.CountTokens() < 11 )
3516 SCH_PARSE_ERROR( "invalid pin definition", aReader, line );
3517
3518 tmp = tokens.GetNextToken();
3519 name = tmp;
3520 pos += tmp.size() + 1;
3521
3522 tmp = tokens.GetNextToken();
3523 number = tmp ;
3524 pos += tmp.size() + 1;
3525
3526 long num;
3527 wxPoint position;
3528
3529 tmp = tokens.GetNextToken();
3530
3531 if( !tmp.ToLong( &num ) )
3532 THROW_PARSE_ERROR( "invalid pin X coordinate", aReader.GetSource(), aReader.Line(),
3533 aReader.LineNumber(), pos );
3534
3535 pos += tmp.size() + 1;
3536 position.x = Mils2Iu( (int) num );
3537
3538 tmp = tokens.GetNextToken();
3539
3540 if( !tmp.ToLong( &num ) )
3541 THROW_PARSE_ERROR( "invalid pin Y coordinate", aReader.GetSource(), aReader.Line(),
3542 aReader.LineNumber(), pos );
3543
3544 pos += tmp.size() + 1;
3545 position.y = Mils2Iu( (int) num );
3546
3547 tmp = tokens.GetNextToken();
3548
3549 if( !tmp.ToLong( &num ) )
3550 THROW_PARSE_ERROR( "invalid pin length", aReader.GetSource(), aReader.Line(),
3551 aReader.LineNumber(), pos );
3552
3553 pos += tmp.size() + 1;
3554 int length = Mils2Iu( (int) num );
3555
3556
3557 tmp = tokens.GetNextToken();
3558
3559 if( tmp.size() > 1 )
3560 THROW_PARSE_ERROR( "invalid pin orientation", aReader.GetSource(), aReader.Line(),
3561 aReader.LineNumber(), pos );
3562
3563 pos += tmp.size() + 1;
3564 int orientation = tmp[0];
3565
3566 tmp = tokens.GetNextToken();
3567
3568 if( !tmp.ToLong( &num ) )
3569 THROW_PARSE_ERROR( "invalid pin number text size", aReader.GetSource(), aReader.Line(),
3570 aReader.LineNumber(), pos );
3571
3572 pos += tmp.size() + 1;
3573 int numberTextSize = Mils2Iu( (int) num );
3574
3575 tmp = tokens.GetNextToken();
3576
3577 if( !tmp.ToLong( &num ) )
3578 THROW_PARSE_ERROR( "invalid pin name text size", aReader.GetSource(), aReader.Line(),
3579 aReader.LineNumber(), pos );
3580
3581 pos += tmp.size() + 1;
3582 int nameTextSize = Mils2Iu( (int) num );
3583
3584 tmp = tokens.GetNextToken();
3585
3586 if( !tmp.ToLong( &num ) )
3587 THROW_PARSE_ERROR( "invalid pin unit", aReader.GetSource(), aReader.Line(),
3588 aReader.LineNumber(), pos );
3589
3590 pos += tmp.size() + 1;
3591 int unit = (int) num;
3592
3593 tmp = tokens.GetNextToken();
3594
3595 if( !tmp.ToLong( &num ) )
3596 THROW_PARSE_ERROR( "invalid pin alternate body type", aReader.GetSource(), aReader.Line(),
3597 aReader.LineNumber(), pos );
3598
3599 pos += tmp.size() + 1;
3600 int convert = (int) num;
3601
3602 tmp = tokens.GetNextToken();
3603
3604 if( tmp.size() != 1 )
3605 THROW_PARSE_ERROR( "invalid pin type", aReader.GetSource(), aReader.Line(),
3606 aReader.LineNumber(), pos );
3607
3608 pos += tmp.size() + 1;
3609 char type = tmp[0];
3610 ELECTRICAL_PINTYPE pinType;
3611
3612 switch( type )
3613 {
3614 case 'I': pinType = ELECTRICAL_PINTYPE::PT_INPUT; break;
3615 case 'O': pinType = ELECTRICAL_PINTYPE::PT_OUTPUT; break;
3616 case 'B': pinType = ELECTRICAL_PINTYPE::PT_BIDI; break;
3617 case 'T': pinType = ELECTRICAL_PINTYPE::PT_TRISTATE; break;
3618 case 'P': pinType = ELECTRICAL_PINTYPE::PT_PASSIVE; break;
3619 case 'U': pinType = ELECTRICAL_PINTYPE::PT_UNSPECIFIED; break;
3620 case 'W': pinType = ELECTRICAL_PINTYPE::PT_POWER_IN; break;
3621 case 'w': pinType = ELECTRICAL_PINTYPE::PT_POWER_OUT; break;
3622 case 'C': pinType = ELECTRICAL_PINTYPE::PT_OPENCOLLECTOR; break;
3623 case 'E': pinType = ELECTRICAL_PINTYPE::PT_OPENEMITTER; break;
3624 case 'N': pinType = ELECTRICAL_PINTYPE::PT_NC; break;
3625 default:
3626 THROW_PARSE_ERROR( "unknown pin type", aReader.GetSource(), aReader.Line(),
3627 aReader.LineNumber(), pos );
3628 }
3629
3630
3631 LIB_PIN* pin = new LIB_PIN( aSymbol.get(),
3632 ConvertToNewOverbarNotation( name ),
3633 ConvertToNewOverbarNotation( number ),
3634 orientation,
3635 pinType,
3636 length,
3637 nameTextSize,
3638 numberTextSize,
3639 convert,
3640 position,
3641 unit );
3642
3643 // Optional
3644 if( tokens.HasMoreTokens() ) /* Special Symbol defined */
3645 {
3646 tmp = tokens.GetNextToken();
3647
3648 enum
3649 {
3650 INVERTED = 1 << 0,
3651 CLOCK = 1 << 1,
3652 LOWLEVEL_IN = 1 << 2,
3653 LOWLEVEL_OUT = 1 << 3,
3654 FALLING_EDGE = 1 << 4,
3655 NONLOGIC = 1 << 5
3656 };
3657
3658 int flags = 0;
3659
3660 for( int j = tmp.size(); j > 0; )
3661 {
3662 switch( tmp[--j].GetValue() )
3663 {
3664 case '~': break;
3665 case 'N': pin->SetVisible( false ); break;
3666 case 'I': flags |= INVERTED; break;
3667 case 'C': flags |= CLOCK; break;
3668 case 'L': flags |= LOWLEVEL_IN; break;
3669 case 'V': flags |= LOWLEVEL_OUT; break;
3670 case 'F': flags |= FALLING_EDGE; break;
3671 case 'X': flags |= NONLOGIC; break;
3672 default: THROW_PARSE_ERROR( "invalid pin attribut", aReader.GetSource(),
3673 aReader.Line(), aReader.LineNumber(), pos );
3674 }
3675
3676 pos += 1;
3677 }
3678
3679 switch( flags )
3680 {
3681 case 0: pin->SetShape( GRAPHIC_PINSHAPE::LINE ); break;
3682 case INVERTED: pin->SetShape( GRAPHIC_PINSHAPE::INVERTED ); break;
3683 case CLOCK: pin->SetShape( GRAPHIC_PINSHAPE::CLOCK ); break;
3684 case INVERTED | CLOCK: pin->SetShape( GRAPHIC_PINSHAPE::INVERTED_CLOCK ); break;
3685 case LOWLEVEL_IN: pin->SetShape( GRAPHIC_PINSHAPE::INPUT_LOW ); break;
3686 case LOWLEVEL_IN | CLOCK: pin->SetShape( GRAPHIC_PINSHAPE::CLOCK_LOW ); break;
3687 case LOWLEVEL_OUT: pin->SetShape( GRAPHIC_PINSHAPE::OUTPUT_LOW ); break;
3688 case FALLING_EDGE: pin->SetShape( GRAPHIC_PINSHAPE::FALLING_EDGE_CLOCK ); break;
3689 case NONLOGIC: pin->SetShape( GRAPHIC_PINSHAPE::NONLOGIC ); break;
3690 default:
3691 SCH_PARSE_ERROR( "pin attributes do not define a valid pin shape", aReader, line );
3692 }
3693 }
3694
3695 return pin;
3696 }
3697
3698
loadPolyLine(std::unique_ptr<LIB_SYMBOL> & aSymbol,LINE_READER & aReader)3699 LIB_SHAPE* SCH_LEGACY_PLUGIN_CACHE::loadPolyLine( std::unique_ptr<LIB_SYMBOL>& aSymbol,
3700 LINE_READER& aReader )
3701 {
3702 const char* line = aReader.Line();
3703
3704 wxCHECK_MSG( strCompare( "P", line, &line ), nullptr, "Invalid poly definition" );
3705
3706 LIB_SHAPE* polyLine = new LIB_SHAPE( aSymbol.get(), SHAPE_T::POLY );
3707
3708 int points = parseInt( aReader, line, &line );
3709 polyLine->SetUnit( parseInt( aReader, line, &line ) );
3710 polyLine->SetConvert( parseInt( aReader, line, &line ) );
3711 polyLine->SetWidth( Mils2Iu( parseInt( aReader, line, &line ) ) );
3712
3713 wxPoint pt;
3714
3715 for( int i = 0; i < points; i++ )
3716 {
3717 pt.x = Mils2Iu( parseInt( aReader, line, &line ) );
3718 pt.y = Mils2Iu( parseInt( aReader, line, &line ) );
3719 polyLine->AddPoint( pt );
3720 }
3721
3722 if( *line != 0 )
3723 polyLine->SetFillMode( parseFillMode( aReader, line, &line ) );
3724
3725 return polyLine;
3726 }
3727
3728
loadBezier(std::unique_ptr<LIB_SYMBOL> & aSymbol,LINE_READER & aReader)3729 LIB_SHAPE* SCH_LEGACY_PLUGIN_CACHE::loadBezier( std::unique_ptr<LIB_SYMBOL>& aSymbol,
3730 LINE_READER& aReader )
3731 {
3732 const char* line = aReader.Line();
3733
3734 wxCHECK_MSG( strCompare( "B", line, &line ), nullptr, "Invalid Bezier definition" );
3735
3736 int points = parseInt( aReader, line, &line );
3737
3738 wxCHECK_MSG( points == 4, NULL, "Invalid Bezier curve definition" );
3739
3740 LIB_SHAPE* bezier = new LIB_SHAPE( aSymbol.get(), SHAPE_T::BEZIER );
3741
3742 bezier->SetUnit( parseInt( aReader, line, &line ) );
3743 bezier->SetConvert( parseInt( aReader, line, &line ) );
3744 bezier->SetWidth( Mils2Iu( parseInt( aReader, line, &line ) ) );
3745
3746 bezier->SetStart( wxPoint( Mils2Iu( parseInt( aReader, line, &line ) ),
3747 Mils2Iu( parseInt( aReader, line, &line ) ) ) );
3748
3749 bezier->SetBezierC1( wxPoint( Mils2Iu( parseInt( aReader, line, &line ) ),
3750 Mils2Iu( parseInt( aReader, line, &line ) ) ) );
3751
3752 bezier->SetBezierC2( wxPoint( Mils2Iu( parseInt( aReader, line, &line ) ),
3753 Mils2Iu( parseInt( aReader, line, &line ) ) ) );
3754
3755 bezier->SetEnd( wxPoint( Mils2Iu( parseInt( aReader, line, &line ) ),
3756 Mils2Iu( parseInt( aReader, line, &line ) ) ) );
3757
3758 bezier->RebuildBezierToSegmentsPointsList( bezier->GetWidth() );
3759
3760 if( *line != 0 )
3761 bezier->SetFillMode( parseFillMode( aReader, line, &line ) );
3762
3763 return bezier;
3764 }
3765
3766
loadFootprintFilters(std::unique_ptr<LIB_SYMBOL> & aSymbol,LINE_READER & aReader)3767 void SCH_LEGACY_PLUGIN_CACHE::loadFootprintFilters( std::unique_ptr<LIB_SYMBOL>& aSymbol,
3768 LINE_READER& aReader )
3769 {
3770 const char* line = aReader.Line();
3771
3772 wxCHECK_RET( strCompare( "$FPLIST", line, &line ), "Invalid footprint filter list" );
3773
3774 line = aReader.ReadLine();
3775
3776 wxArrayString footprintFilters;
3777
3778 while( line )
3779 {
3780 if( strCompare( "$ENDFPLIST", line, &line ) )
3781 {
3782 aSymbol->SetFPFilters( footprintFilters );
3783 return;
3784 }
3785
3786 wxString footprint;
3787
3788 parseUnquotedString( footprint, aReader, line, &line );
3789 footprintFilters.Add( footprint );
3790 line = aReader.ReadLine();
3791 }
3792
3793 SCH_PARSE_ERROR( "File ended prematurely while loading footprint filters.", aReader, line );
3794 }
3795
3796
Save(bool aSaveDocFile)3797 void SCH_LEGACY_PLUGIN_CACHE::Save( bool aSaveDocFile )
3798 {
3799 if( !m_isModified )
3800 return;
3801
3802 // Write through symlinks, don't replace them
3803 wxFileName fn = GetRealFile();
3804
3805 auto formatter = std::make_unique<FILE_OUTPUTFORMATTER>( fn.GetFullPath() );
3806 formatter->Print( 0, "%s %d.%d\n", LIBFILE_IDENT, LIB_VERSION_MAJOR, LIB_VERSION_MINOR );
3807 formatter->Print( 0, "#encoding utf-8\n");
3808
3809 for( LIB_SYMBOL_MAP::iterator it = m_symbols.begin(); it != m_symbols.end(); it++ )
3810 {
3811 if( !it->second->IsRoot() )
3812 continue;
3813
3814 SaveSymbol( it->second, *formatter.get(), &m_symbols );
3815 }
3816
3817 formatter->Print( 0, "#\n#End Library\n" );
3818 formatter.reset();
3819
3820 m_fileModTime = fn.GetModificationTime();
3821 m_isModified = false;
3822
3823 if( aSaveDocFile )
3824 saveDocFile();
3825 }
3826
3827
SaveSymbol(LIB_SYMBOL * aSymbol,OUTPUTFORMATTER & aFormatter,LIB_SYMBOL_MAP * aMap)3828 void SCH_LEGACY_PLUGIN_CACHE::SaveSymbol( LIB_SYMBOL* aSymbol, OUTPUTFORMATTER& aFormatter,
3829 LIB_SYMBOL_MAP* aMap )
3830 {
3831 /*
3832 * NB:
3833 * Some of the rescue code still uses the legacy format as an intermediary, so we have
3834 * to keep this code.
3835 */
3836
3837 wxCHECK_RET( aSymbol && aSymbol->IsRoot(), "Invalid LIB_SYMBOL pointer." );
3838
3839 // LIB_ALIAS objects are deprecated but we still need to gather up the derived symbols
3840 // and save their names for the old file format.
3841 wxArrayString aliasNames;
3842
3843 if( aMap )
3844 {
3845 for( auto entry : *aMap )
3846 {
3847 LIB_SYMBOL* symbol = entry.second;
3848
3849 if( symbol->IsAlias() && symbol->GetParent().lock() == aSymbol->SharedPtr() )
3850 aliasNames.Add( symbol->GetName() );
3851 }
3852 }
3853
3854 LIB_FIELD& value = aSymbol->GetValueField();
3855
3856 // First line: it s a comment (symbol name for readers)
3857 aFormatter.Print( 0, "#\n# %s\n#\n", TO_UTF8( value.GetText() ) );
3858
3859 // Save data
3860 aFormatter.Print( 0, "DEF" );
3861 aFormatter.Print( 0, " %s", TO_UTF8( value.GetText() ) );
3862
3863 LIB_FIELD& reference = aSymbol->GetReferenceField();
3864
3865 if( !reference.GetText().IsEmpty() )
3866 aFormatter.Print( 0, " %s", TO_UTF8( reference.GetText() ) );
3867 else
3868 aFormatter.Print( 0, " ~" );
3869
3870 aFormatter.Print( 0, " %d %d %c %c %d %c %c\n",
3871 0, Iu2Mils( aSymbol->GetPinNameOffset() ),
3872 aSymbol->ShowPinNumbers() ? 'Y' : 'N',
3873 aSymbol->ShowPinNames() ? 'Y' : 'N',
3874 aSymbol->GetUnitCount(), aSymbol->UnitsLocked() ? 'L' : 'F',
3875 aSymbol->IsPower() ? 'P' : 'N' );
3876
3877 timestamp_t dateModified = aSymbol->GetLastModDate();
3878
3879 if( dateModified != 0 )
3880 {
3881 int sec = dateModified & 63;
3882 int min = ( dateModified >> 6 ) & 63;
3883 int hour = ( dateModified >> 12 ) & 31;
3884 int day = ( dateModified >> 17 ) & 31;
3885 int mon = ( dateModified >> 22 ) & 15;
3886 int year = ( dateModified >> 26 ) + 1990;
3887
3888 aFormatter.Print( 0, "Ti %d/%d/%d %d:%d:%d\n", year, mon, day, hour, min, sec );
3889 }
3890
3891 std::vector<LIB_FIELD*> fields;
3892 aSymbol->GetFields( fields );
3893
3894 // Mandatory fields:
3895 // may have their own save policy so there is a separate loop for them.
3896 // Empty fields are saved, because the user may have set visibility,
3897 // size and orientation
3898 for( int i = 0; i < MANDATORY_FIELDS; ++i )
3899 saveField( fields[i], aFormatter );
3900
3901 // User defined fields:
3902 // may have their own save policy so there is a separate loop for them.
3903 int fieldId = MANDATORY_FIELDS; // really wish this would go away.
3904
3905 for( unsigned i = MANDATORY_FIELDS; i < fields.size(); ++i )
3906 {
3907 // There is no need to save empty fields, i.e. no reason to preserve field
3908 // names now that fields names come in dynamically through the template
3909 // fieldnames.
3910 if( !fields[i]->GetText().IsEmpty() )
3911 {
3912 fields[i]->SetId( fieldId++ );
3913 saveField( fields[i], aFormatter );
3914 }
3915 }
3916
3917 // Save the alias list: a line starting by "ALIAS".
3918 if( !aliasNames.IsEmpty() )
3919 {
3920 aFormatter.Print( 0, "ALIAS" );
3921
3922 for( unsigned i = 0; i < aliasNames.GetCount(); i++ )
3923 aFormatter.Print( 0, " %s", TO_UTF8( aliasNames[i] ) );
3924
3925 aFormatter.Print( 0, "\n" );
3926 }
3927
3928 wxArrayString footprints = aSymbol->GetFPFilters();
3929
3930 // Write the footprint filter list
3931 if( footprints.GetCount() != 0 )
3932 {
3933 aFormatter.Print( 0, "$FPLIST\n" );
3934
3935 for( unsigned i = 0; i < footprints.GetCount(); i++ )
3936 aFormatter.Print( 0, " %s\n", TO_UTF8( footprints[i] ) );
3937
3938 aFormatter.Print( 0, "$ENDFPLIST\n" );
3939 }
3940
3941 // Save graphics items (including pins)
3942 if( !aSymbol->GetDrawItems().empty() )
3943 {
3944 // Sort the draw items in order to editing a file editing by hand.
3945 aSymbol->GetDrawItems().sort();
3946
3947 aFormatter.Print( 0, "DRAW\n" );
3948
3949 for( LIB_ITEM& item : aSymbol->GetDrawItems() )
3950 {
3951 switch( item.Type() )
3952 {
3953 default:
3954 case LIB_FIELD_T: /* Fields have already been saved above. */ break;
3955 case LIB_PIN_T: savePin( (LIB_PIN* ) &item, aFormatter ); break;
3956 case LIB_TEXT_T: saveText( ( LIB_TEXT* ) &item, aFormatter ); break;
3957 case LIB_SHAPE_T:
3958 {
3959 LIB_SHAPE& shape = static_cast<LIB_SHAPE&>( item );
3960
3961 switch( shape.GetShape() )
3962 {
3963 case SHAPE_T::ARC: saveArc( &shape, aFormatter ); break;
3964 case SHAPE_T::BEZIER: saveBezier( &shape, aFormatter ); break;
3965 case SHAPE_T::CIRCLE: saveCircle( &shape, aFormatter ); break;
3966 case SHAPE_T::POLY: savePolyLine( &shape, aFormatter ); break;
3967 case SHAPE_T::RECT: saveRectangle( &shape, aFormatter ); break;
3968 default: break;
3969 }
3970 }
3971 }
3972 }
3973
3974 aFormatter.Print( 0, "ENDDRAW\n" );
3975 }
3976
3977 aFormatter.Print( 0, "ENDDEF\n" );
3978 }
3979
3980
saveArc(LIB_SHAPE * aArc,OUTPUTFORMATTER & aFormatter)3981 void SCH_LEGACY_PLUGIN_CACHE::saveArc( LIB_SHAPE* aArc, OUTPUTFORMATTER& aFormatter )
3982 {
3983 wxCHECK_RET( aArc && aArc->GetShape() == SHAPE_T::ARC, "Invalid ARC object." );
3984
3985 int x1;
3986 int x2;
3987
3988 aArc->CalcArcAngles( x1, x2 );
3989
3990 if( x1 > 1800 )
3991 x1 -= 3600;
3992
3993 if( x2 > 1800 )
3994 x2 -= 3600;
3995
3996 aFormatter.Print( 0, "A %d %d %d %d %d %d %d %d %c %d %d %d %d\n",
3997 Iu2Mils( aArc->GetPosition().x ),
3998 Iu2Mils( aArc->GetPosition().y ),
3999 Iu2Mils( aArc->GetRadius() ),
4000 x1,
4001 x2,
4002 aArc->GetUnit(),
4003 aArc->GetConvert(),
4004 Iu2Mils( aArc->GetWidth() ),
4005 fill_tab[ static_cast<int>( aArc->GetFillType() ) - 1 ],
4006 Iu2Mils( aArc->GetStart().x ),
4007 Iu2Mils( aArc->GetStart().y ),
4008 Iu2Mils( aArc->GetEnd().x ),
4009 Iu2Mils( aArc->GetEnd().y ) );
4010 }
4011
4012
saveBezier(LIB_SHAPE * aBezier,OUTPUTFORMATTER & aFormatter)4013 void SCH_LEGACY_PLUGIN_CACHE::saveBezier( LIB_SHAPE* aBezier, OUTPUTFORMATTER& aFormatter )
4014 {
4015 wxCHECK_RET( aBezier && aBezier->GetShape() == SHAPE_T::BEZIER, "Invalid BEZIER object." );
4016
4017 aFormatter.Print( 0, "B %u %d %d %d",
4018 (unsigned)aBezier->GetBezierPoints().size(),
4019 aBezier->GetUnit(),
4020 aBezier->GetConvert(),
4021 Iu2Mils( aBezier->GetWidth() ) );
4022
4023 for( const wxPoint& pt : aBezier->GetBezierPoints() )
4024 aFormatter.Print( 0, " %d %d", Iu2Mils( pt.x ), Iu2Mils( pt.y ) );
4025
4026 aFormatter.Print( 0, " %c\n", fill_tab[ static_cast<int>( aBezier->GetFillType() ) - 1 ] );
4027 }
4028
4029
saveCircle(LIB_SHAPE * aCircle,OUTPUTFORMATTER & aFormatter)4030 void SCH_LEGACY_PLUGIN_CACHE::saveCircle( LIB_SHAPE* aCircle, OUTPUTFORMATTER& aFormatter )
4031 {
4032 wxCHECK_RET( aCircle && aCircle->GetShape() == SHAPE_T::CIRCLE, "Invalid CIRCLE object." );
4033
4034 aFormatter.Print( 0, "C %d %d %d %d %d %d %c\n",
4035 Iu2Mils( aCircle->GetPosition().x ),
4036 Iu2Mils( aCircle->GetPosition().y ),
4037 Iu2Mils( aCircle->GetRadius() ),
4038 aCircle->GetUnit(),
4039 aCircle->GetConvert(),
4040 Iu2Mils( aCircle->GetWidth() ),
4041 fill_tab[ static_cast<int>( aCircle->GetFillType() ) - 1 ] );
4042 }
4043
4044
saveField(const LIB_FIELD * aField,OUTPUTFORMATTER & aFormatter)4045 void SCH_LEGACY_PLUGIN_CACHE::saveField( const LIB_FIELD* aField, OUTPUTFORMATTER& aFormatter )
4046 {
4047 wxCHECK_RET( aField && aField->Type() == LIB_FIELD_T, "Invalid LIB_FIELD object." );
4048
4049 int hjustify, vjustify;
4050 int id = aField->GetId();
4051 wxString text = aField->GetText();
4052
4053 hjustify = 'C';
4054
4055 if( aField->GetHorizJustify() == GR_TEXT_HJUSTIFY_LEFT )
4056 hjustify = 'L';
4057 else if( aField->GetHorizJustify() == GR_TEXT_HJUSTIFY_RIGHT )
4058 hjustify = 'R';
4059
4060 vjustify = 'C';
4061
4062 if( aField->GetVertJustify() == GR_TEXT_VJUSTIFY_BOTTOM )
4063 vjustify = 'B';
4064 else if( aField->GetVertJustify() == GR_TEXT_VJUSTIFY_TOP )
4065 vjustify = 'T';
4066
4067 aFormatter.Print( 0, "F%d %s %d %d %d %c %c %c %c%c%c",
4068 id,
4069 EscapedUTF8( text ).c_str(), // wraps in quotes
4070 Iu2Mils( aField->GetTextPos().x ),
4071 Iu2Mils( aField->GetTextPos().y ),
4072 Iu2Mils( aField->GetTextWidth() ),
4073 aField->GetTextAngle() == 0 ? 'H' : 'V',
4074 aField->IsVisible() ? 'V' : 'I',
4075 hjustify, vjustify,
4076 aField->IsItalic() ? 'I' : 'N',
4077 aField->IsBold() ? 'B' : 'N' );
4078
4079 /* Save field name, if necessary
4080 * Field name is saved only if it is not the default name.
4081 * Just because default name depends on the language and can change from
4082 * a country to another
4083 */
4084 wxString defName = TEMPLATE_FIELDNAME::GetDefaultFieldName( id );
4085
4086 if( id >= MANDATORY_FIELDS && !aField->m_name.IsEmpty() && aField->m_name != defName )
4087 aFormatter.Print( 0, " %s", EscapedUTF8( aField->m_name ).c_str() );
4088
4089 aFormatter.Print( 0, "\n" );
4090 }
4091
4092
savePin(const LIB_PIN * aPin,OUTPUTFORMATTER & aFormatter)4093 void SCH_LEGACY_PLUGIN_CACHE::savePin( const LIB_PIN* aPin, OUTPUTFORMATTER& aFormatter )
4094 {
4095 wxCHECK_RET( aPin && aPin->Type() == LIB_PIN_T, "Invalid LIB_PIN object." );
4096
4097 int Etype;
4098
4099 switch( aPin->GetType() )
4100 {
4101 default:
4102 case ELECTRICAL_PINTYPE::PT_INPUT: Etype = 'I'; break;
4103 case ELECTRICAL_PINTYPE::PT_OUTPUT: Etype = 'O'; break;
4104 case ELECTRICAL_PINTYPE::PT_BIDI: Etype = 'B'; break;
4105 case ELECTRICAL_PINTYPE::PT_TRISTATE: Etype = 'T'; break;
4106 case ELECTRICAL_PINTYPE::PT_PASSIVE: Etype = 'P'; break;
4107 case ELECTRICAL_PINTYPE::PT_UNSPECIFIED: Etype = 'U'; break;
4108 case ELECTRICAL_PINTYPE::PT_POWER_IN: Etype = 'W'; break;
4109 case ELECTRICAL_PINTYPE::PT_POWER_OUT: Etype = 'w'; break;
4110 case ELECTRICAL_PINTYPE::PT_OPENCOLLECTOR: Etype = 'C'; break;
4111 case ELECTRICAL_PINTYPE::PT_OPENEMITTER: Etype = 'E'; break;
4112 case ELECTRICAL_PINTYPE::PT_NC: Etype = 'N'; break;
4113 }
4114
4115 if( !aPin->GetName().IsEmpty() )
4116 aFormatter.Print( 0, "X %s", TO_UTF8( aPin->GetName() ) );
4117 else
4118 aFormatter.Print( 0, "X ~" );
4119
4120 aFormatter.Print( 0, " %s %d %d %d %c %d %d %d %d %c",
4121 aPin->GetNumber().IsEmpty() ? "~" : TO_UTF8( aPin->GetNumber() ),
4122 Iu2Mils( aPin->GetPosition().x ),
4123 Iu2Mils( aPin->GetPosition().y ),
4124 Iu2Mils( (int) aPin->GetLength() ),
4125 (int) aPin->GetOrientation(),
4126 Iu2Mils( aPin->GetNumberTextSize() ),
4127 Iu2Mils( aPin->GetNameTextSize() ),
4128 aPin->GetUnit(),
4129 aPin->GetConvert(),
4130 Etype );
4131
4132 if( aPin->GetShape() != GRAPHIC_PINSHAPE::LINE || !aPin->IsVisible() )
4133 aFormatter.Print( 0, " " );
4134
4135 if( !aPin->IsVisible() )
4136 aFormatter.Print( 0, "N" );
4137
4138 switch( aPin->GetShape() )
4139 {
4140 case GRAPHIC_PINSHAPE::LINE: break;
4141 case GRAPHIC_PINSHAPE::INVERTED: aFormatter.Print( 0, "I" ); break;
4142 case GRAPHIC_PINSHAPE::CLOCK: aFormatter.Print( 0, "C" ); break;
4143 case GRAPHIC_PINSHAPE::INVERTED_CLOCK: aFormatter.Print( 0, "IC" ); break;
4144 case GRAPHIC_PINSHAPE::INPUT_LOW: aFormatter.Print( 0, "L" ); break;
4145 case GRAPHIC_PINSHAPE::CLOCK_LOW: aFormatter.Print( 0, "CL" ); break;
4146 case GRAPHIC_PINSHAPE::OUTPUT_LOW: aFormatter.Print( 0, "V" ); break;
4147 case GRAPHIC_PINSHAPE::FALLING_EDGE_CLOCK: aFormatter.Print( 0, "F" ); break;
4148 case GRAPHIC_PINSHAPE::NONLOGIC: aFormatter.Print( 0, "X" ); break;
4149 default: wxFAIL_MSG( "Invalid pin shape" );
4150 }
4151
4152 aFormatter.Print( 0, "\n" );
4153
4154 const_cast<LIB_PIN*>( aPin )->ClearFlags( IS_CHANGED );
4155 }
4156
4157
savePolyLine(LIB_SHAPE * aPolyLine,OUTPUTFORMATTER & aFormatter)4158 void SCH_LEGACY_PLUGIN_CACHE::savePolyLine( LIB_SHAPE* aPolyLine, OUTPUTFORMATTER& aFormatter )
4159 {
4160 wxCHECK_RET( aPolyLine && aPolyLine->GetShape() == SHAPE_T::POLY, "Invalid POLY object." );
4161
4162 aFormatter.Print( 0, "P %d %d %d %d",
4163 (int) aPolyLine->GetPolyShape().Outline( 0 ).GetPointCount(),
4164 aPolyLine->GetUnit(),
4165 aPolyLine->GetConvert(),
4166 Iu2Mils( aPolyLine->GetWidth() ) );
4167
4168 for( const VECTOR2I& pt : aPolyLine->GetPolyShape().Outline( 0 ).CPoints() )
4169 aFormatter.Print( 0, " %d %d", Iu2Mils( pt.x ), Iu2Mils( pt.y ) );
4170
4171 aFormatter.Print( 0, " %c\n", fill_tab[ static_cast<int>( aPolyLine->GetFillType() ) - 1 ] );
4172 }
4173
4174
saveRectangle(LIB_SHAPE * aRectangle,OUTPUTFORMATTER & aFormatter)4175 void SCH_LEGACY_PLUGIN_CACHE::saveRectangle( LIB_SHAPE* aRectangle, OUTPUTFORMATTER& aFormatter )
4176 {
4177 wxCHECK_RET( aRectangle && aRectangle->GetShape() == SHAPE_T::RECT, "Invalid RECT object." );
4178
4179 aFormatter.Print( 0, "S %d %d %d %d %d %d %d %c\n",
4180 Iu2Mils( aRectangle->GetPosition().x ),
4181 Iu2Mils( aRectangle->GetPosition().y ),
4182 Iu2Mils( aRectangle->GetEnd().x ),
4183 Iu2Mils( aRectangle->GetEnd().y ),
4184 aRectangle->GetUnit(),
4185 aRectangle->GetConvert(),
4186 Iu2Mils( aRectangle->GetWidth() ),
4187 fill_tab[ static_cast<int>( aRectangle->GetFillType() ) - 1 ] );
4188 }
4189
4190
saveText(const LIB_TEXT * aText,OUTPUTFORMATTER & aFormatter)4191 void SCH_LEGACY_PLUGIN_CACHE::saveText( const LIB_TEXT* aText, OUTPUTFORMATTER& aFormatter )
4192 {
4193 wxCHECK_RET( aText && aText->Type() == LIB_TEXT_T, "Invalid LIB_TEXT object." );
4194
4195 wxString text = aText->GetText();
4196
4197 if( text.Contains( wxT( " " ) ) || text.Contains( wxT( "~" ) ) || text.Contains( wxT( "\"" ) ) )
4198 {
4199 // convert double quote to similar-looking two apostrophes
4200 text.Replace( wxT( "\"" ), wxT( "''" ) );
4201 text = wxT( "\"" ) + text + wxT( "\"" );
4202 }
4203
4204 aFormatter.Print( 0, "T %g %d %d %d %d %d %d %s", aText->GetTextAngle(),
4205 Iu2Mils( aText->GetTextPos().x ), Iu2Mils( aText->GetTextPos().y ),
4206 Iu2Mils( aText->GetTextWidth() ), !aText->IsVisible(),
4207 aText->GetUnit(), aText->GetConvert(), TO_UTF8( text ) );
4208
4209 aFormatter.Print( 0, " %s %d", aText->IsItalic() ? "Italic" : "Normal", aText->IsBold() );
4210
4211 char hjustify = 'C';
4212
4213 if( aText->GetHorizJustify() == GR_TEXT_HJUSTIFY_LEFT )
4214 hjustify = 'L';
4215 else if( aText->GetHorizJustify() == GR_TEXT_HJUSTIFY_RIGHT )
4216 hjustify = 'R';
4217
4218 char vjustify = 'C';
4219
4220 if( aText->GetVertJustify() == GR_TEXT_VJUSTIFY_BOTTOM )
4221 vjustify = 'B';
4222 else if( aText->GetVertJustify() == GR_TEXT_VJUSTIFY_TOP )
4223 vjustify = 'T';
4224
4225 aFormatter.Print( 0, " %c %c\n", hjustify, vjustify );
4226 }
4227
4228
saveDocFile()4229 void SCH_LEGACY_PLUGIN_CACHE::saveDocFile()
4230 {
4231 /*
4232 * NB:
4233 * Some of the rescue code still uses the legacy format as an intermediary, so we have
4234 * to keep this code.
4235 */
4236
4237 wxFileName fileName = m_libFileName;
4238
4239 fileName.SetExt( DOC_EXT );
4240 FILE_OUTPUTFORMATTER formatter( fileName.GetFullPath() );
4241
4242 formatter.Print( 0, "%s\n", DOCFILE_IDENT );
4243
4244 for( LIB_SYMBOL_MAP::iterator it = m_symbols.begin(); it != m_symbols.end(); ++it )
4245 {
4246 wxString description = it->second->GetDescription();
4247 wxString keyWords = it->second->GetKeyWords();
4248 wxString docFileName = it->second->GetDatasheetField().GetText();
4249
4250 if( description.IsEmpty() && keyWords.IsEmpty() && docFileName.IsEmpty() )
4251 continue;
4252
4253 formatter.Print( 0, "#\n$CMP %s\n", TO_UTF8( it->second->GetName() ) );
4254
4255 if( !description.IsEmpty() )
4256 formatter.Print( 0, "D %s\n", TO_UTF8( description ) );
4257
4258 if( !keyWords.IsEmpty() )
4259 formatter.Print( 0, "K %s\n", TO_UTF8( keyWords ) );
4260
4261 if( !docFileName.IsEmpty() )
4262 formatter.Print( 0, "F %s\n", TO_UTF8( docFileName ) );
4263
4264 formatter.Print( 0, "$ENDCMP\n" );
4265 }
4266
4267 formatter.Print( 0, "#\n#End Doc Library\n" );
4268 }
4269
4270
DeleteSymbol(const wxString & aSymbolName)4271 void SCH_LEGACY_PLUGIN_CACHE::DeleteSymbol( const wxString& aSymbolName )
4272 {
4273 LIB_SYMBOL_MAP::iterator it = m_symbols.find( aSymbolName );
4274
4275 if( it == m_symbols.end() )
4276 THROW_IO_ERROR( wxString::Format( _( "library %s does not contain a symbol named %s" ),
4277 m_libFileName.GetFullName(), aSymbolName ) );
4278
4279 LIB_SYMBOL* symbol = it->second;
4280
4281 if( symbol->IsRoot() )
4282 {
4283 LIB_SYMBOL* rootSymbol = symbol;
4284
4285 // Remove the root symbol and all its children.
4286 m_symbols.erase( it );
4287
4288 LIB_SYMBOL_MAP::iterator it1 = m_symbols.begin();
4289
4290 while( it1 != m_symbols.end() )
4291 {
4292 if( it1->second->IsAlias()
4293 && it1->second->GetParent().lock() == rootSymbol->SharedPtr() )
4294 {
4295 delete it1->second;
4296 it1 = m_symbols.erase( it1 );
4297 }
4298 else
4299 {
4300 it1++;
4301 }
4302 }
4303
4304 delete rootSymbol;
4305 }
4306 else
4307 {
4308 // Just remove the alias.
4309 m_symbols.erase( it );
4310 delete symbol;
4311 }
4312
4313 SCH_LEGACY_PLUGIN_CACHE::IncrementModifyHash();
4314 m_isModified = true;
4315 }
4316
4317
cacheLib(const wxString & aLibraryFileName,const PROPERTIES * aProperties)4318 void SCH_LEGACY_PLUGIN::cacheLib( const wxString& aLibraryFileName, const PROPERTIES* aProperties )
4319 {
4320 if( !m_cache || !m_cache->IsFile( aLibraryFileName ) || m_cache->IsFileChanged() )
4321 {
4322 // a spectacular episode in memory management:
4323 delete m_cache;
4324 m_cache = new SCH_LEGACY_PLUGIN_CACHE( aLibraryFileName );
4325
4326 // Because m_cache is rebuilt, increment SYMBOL_LIBS::s_modify_generation
4327 // to modify the hash value that indicate symbol to symbol links
4328 // must be updated.
4329 SYMBOL_LIBS::IncrementModifyGeneration();
4330
4331 if( !isBuffering( aProperties ) )
4332 m_cache->Load();
4333 }
4334 }
4335
4336
writeDocFile(const PROPERTIES * aProperties)4337 bool SCH_LEGACY_PLUGIN::writeDocFile( const PROPERTIES* aProperties )
4338 {
4339 std::string propName( SCH_LEGACY_PLUGIN::PropNoDocFile );
4340
4341 if( aProperties && aProperties->find( propName ) != aProperties->end() )
4342 return false;
4343
4344 return true;
4345 }
4346
4347
isBuffering(const PROPERTIES * aProperties)4348 bool SCH_LEGACY_PLUGIN::isBuffering( const PROPERTIES* aProperties )
4349 {
4350 return ( aProperties && aProperties->Exists( SCH_LEGACY_PLUGIN::PropBuffering ) );
4351 }
4352
4353
GetModifyHash() const4354 int SCH_LEGACY_PLUGIN::GetModifyHash() const
4355 {
4356 if( m_cache )
4357 return SCH_LEGACY_PLUGIN_CACHE::GetModifyHash();
4358
4359 // If the cache hasn't been loaded, it hasn't been modified.
4360 return 0;
4361 }
4362
4363
EnumerateSymbolLib(wxArrayString & aSymbolNameList,const wxString & aLibraryPath,const PROPERTIES * aProperties)4364 void SCH_LEGACY_PLUGIN::EnumerateSymbolLib( wxArrayString& aSymbolNameList,
4365 const wxString& aLibraryPath,
4366 const PROPERTIES* aProperties )
4367 {
4368 LOCALE_IO toggle; // toggles on, then off, the C locale.
4369
4370 bool powerSymbolsOnly = ( aProperties &&
4371 aProperties->find( SYMBOL_LIB_TABLE::PropPowerSymsOnly ) != aProperties->end() );
4372
4373 cacheLib( aLibraryPath, aProperties );
4374
4375 const LIB_SYMBOL_MAP& symbols = m_cache->m_symbols;
4376
4377 for( LIB_SYMBOL_MAP::const_iterator it = symbols.begin(); it != symbols.end(); ++it )
4378 {
4379 if( !powerSymbolsOnly || it->second->IsPower() )
4380 aSymbolNameList.Add( it->first );
4381 }
4382 }
4383
4384
EnumerateSymbolLib(std::vector<LIB_SYMBOL * > & aSymbolList,const wxString & aLibraryPath,const PROPERTIES * aProperties)4385 void SCH_LEGACY_PLUGIN::EnumerateSymbolLib( std::vector<LIB_SYMBOL*>& aSymbolList,
4386 const wxString& aLibraryPath,
4387 const PROPERTIES* aProperties )
4388 {
4389 LOCALE_IO toggle; // toggles on, then off, the C locale.
4390
4391 bool powerSymbolsOnly = ( aProperties &&
4392 aProperties->find( SYMBOL_LIB_TABLE::PropPowerSymsOnly ) != aProperties->end() );
4393
4394 cacheLib( aLibraryPath, aProperties );
4395
4396 const LIB_SYMBOL_MAP& symbols = m_cache->m_symbols;
4397
4398 for( LIB_SYMBOL_MAP::const_iterator it = symbols.begin(); it != symbols.end(); ++it )
4399 {
4400 if( !powerSymbolsOnly || it->second->IsPower() )
4401 aSymbolList.push_back( it->second );
4402 }
4403 }
4404
4405
LoadSymbol(const wxString & aLibraryPath,const wxString & aSymbolName,const PROPERTIES * aProperties)4406 LIB_SYMBOL* SCH_LEGACY_PLUGIN::LoadSymbol( const wxString& aLibraryPath,
4407 const wxString& aSymbolName,
4408 const PROPERTIES* aProperties )
4409 {
4410 LOCALE_IO toggle; // toggles on, then off, the C locale.
4411
4412 cacheLib( aLibraryPath, aProperties );
4413
4414 LIB_SYMBOL_MAP::const_iterator it = m_cache->m_symbols.find( aSymbolName );
4415
4416 if( it == m_cache->m_symbols.end() )
4417 return nullptr;
4418
4419 return it->second;
4420 }
4421
4422
SaveSymbol(const wxString & aLibraryPath,const LIB_SYMBOL * aSymbol,const PROPERTIES * aProperties)4423 void SCH_LEGACY_PLUGIN::SaveSymbol( const wxString& aLibraryPath, const LIB_SYMBOL* aSymbol,
4424 const PROPERTIES* aProperties )
4425 {
4426 LOCALE_IO toggle; // toggles on, then off, the C locale.
4427
4428 cacheLib( aLibraryPath, aProperties );
4429
4430 m_cache->AddSymbol( aSymbol );
4431
4432 if( !isBuffering( aProperties ) )
4433 m_cache->Save( writeDocFile( aProperties ) );
4434 }
4435
4436
DeleteSymbol(const wxString & aLibraryPath,const wxString & aSymbolName,const PROPERTIES * aProperties)4437 void SCH_LEGACY_PLUGIN::DeleteSymbol( const wxString& aLibraryPath, const wxString& aSymbolName,
4438 const PROPERTIES* aProperties )
4439 {
4440 LOCALE_IO toggle; // toggles on, then off, the C locale.
4441
4442 cacheLib( aLibraryPath, aProperties );
4443
4444 m_cache->DeleteSymbol( aSymbolName );
4445
4446 if( !isBuffering( aProperties ) )
4447 m_cache->Save( writeDocFile( aProperties ) );
4448 }
4449
4450
CreateSymbolLib(const wxString & aLibraryPath,const PROPERTIES * aProperties)4451 void SCH_LEGACY_PLUGIN::CreateSymbolLib( const wxString& aLibraryPath,
4452 const PROPERTIES* aProperties )
4453 {
4454 if( wxFileExists( aLibraryPath ) )
4455 {
4456 THROW_IO_ERROR( wxString::Format( _( "Symbol library '%s' already exists." ),
4457 aLibraryPath.GetData() ) );
4458 }
4459
4460 LOCALE_IO toggle;
4461
4462 delete m_cache;
4463 m_cache = new SCH_LEGACY_PLUGIN_CACHE( aLibraryPath );
4464 m_cache->SetModified();
4465 m_cache->Save( writeDocFile( aProperties ) );
4466 m_cache->Load(); // update m_writable and m_mod_time
4467 }
4468
4469
DeleteSymbolLib(const wxString & aLibraryPath,const PROPERTIES * aProperties)4470 bool SCH_LEGACY_PLUGIN::DeleteSymbolLib( const wxString& aLibraryPath,
4471 const PROPERTIES* aProperties )
4472 {
4473 wxFileName fn = aLibraryPath;
4474
4475 if( !fn.FileExists() )
4476 return false;
4477
4478 // Some of the more elaborate wxRemoveFile() crap puts up its own wxLog dialog
4479 // we don't want that. we want bare metal portability with no UI here.
4480 if( wxRemove( aLibraryPath ) )
4481 {
4482 THROW_IO_ERROR( wxString::Format( _( "Symbol library '%s' cannot be deleted." ),
4483 aLibraryPath.GetData() ) );
4484 }
4485
4486 if( m_cache && m_cache->IsFile( aLibraryPath ) )
4487 {
4488 delete m_cache;
4489 m_cache = nullptr;
4490 }
4491
4492 return true;
4493 }
4494
4495
SaveLibrary(const wxString & aLibraryPath,const PROPERTIES * aProperties)4496 void SCH_LEGACY_PLUGIN::SaveLibrary( const wxString& aLibraryPath, const PROPERTIES* aProperties )
4497 {
4498 if( !m_cache )
4499 m_cache = new SCH_LEGACY_PLUGIN_CACHE( aLibraryPath );
4500
4501 wxString oldFileName = m_cache->GetFileName();
4502
4503 if( !m_cache->IsFile( aLibraryPath ) )
4504 {
4505 m_cache->SetFileName( aLibraryPath );
4506 }
4507
4508 // This is a forced save.
4509 m_cache->SetModified();
4510 m_cache->Save( writeDocFile( aProperties ) );
4511 m_cache->SetFileName( oldFileName );
4512 }
4513
4514
CheckHeader(const wxString & aFileName)4515 bool SCH_LEGACY_PLUGIN::CheckHeader( const wxString& aFileName )
4516 {
4517 // Open file and check first line
4518 wxTextFile tempFile;
4519
4520 tempFile.Open( aFileName );
4521 wxString firstline;
4522 // read the first line
4523 firstline = tempFile.GetFirstLine();
4524 tempFile.Close();
4525
4526 return firstline.StartsWith( "EESchema" );
4527 }
4528
4529
IsSymbolLibWritable(const wxString & aLibraryPath)4530 bool SCH_LEGACY_PLUGIN::IsSymbolLibWritable( const wxString& aLibraryPath )
4531 {
4532 // Writing legacy symbol libraries is deprecated.
4533 return false;
4534 }
4535
4536
ParsePart(LINE_READER & reader,int aMajorVersion,int aMinorVersion)4537 LIB_SYMBOL* SCH_LEGACY_PLUGIN::ParsePart( LINE_READER& reader, int aMajorVersion,
4538 int aMinorVersion )
4539 {
4540 return SCH_LEGACY_PLUGIN_CACHE::LoadPart( reader, aMajorVersion, aMinorVersion );
4541 }
4542
4543
FormatPart(LIB_SYMBOL * symbol,OUTPUTFORMATTER & formatter)4544 void SCH_LEGACY_PLUGIN::FormatPart( LIB_SYMBOL* symbol, OUTPUTFORMATTER & formatter )
4545 {
4546 SCH_LEGACY_PLUGIN_CACHE::SaveSymbol( symbol, formatter );
4547 }
4548
4549
4550
4551 const char* SCH_LEGACY_PLUGIN::PropBuffering = "buffering";
4552 const char* SCH_LEGACY_PLUGIN::PropNoDocFile = "no_doc_file";
4553