1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2017 Jean-Pierre Charras, jp.charras at wanadoo.fr
5  * Copyright (C) 2011 Wayne Stambaugh <stambaughw@gmail.com>
6  * Copyright (C) 1992-2021 KiCad Developers, see AUTHORS.txt for contributors.
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public License
10  * as published by the Free Software Foundation; either version 2
11  * of the License, or (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, you may find one here:
20  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
21  * or you may search the http://www.gnu.org website for the version 2 license,
22  * or you may write to the Free Software Foundation, Inc.,
23  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
24  */
25 
26 #include <sch_screen.h>
27 #include <sch_item.h>
28 #include <sch_marker.h>
29 #include <sch_reference_list.h>
30 #include <symbol_library.h>
31 #include <sch_sheet_path.h>
32 #include <sch_symbol.h>
33 #include <sch_sheet.h>
34 #include <schematic.h>
35 #include <template_fieldnames.h>
36 #include <trace_helpers.h>
37 
38 #include <boost/functional/hash.hpp>
39 #include <wx/filename.h>
40 #include <wx/log.h>
41 #include "erc_item.h"
42 
43 
44 /**
45  * A singleton item of this class is returned for a weak reference that no longer exists.
46  * Its sole purpose is to flag the item as having been deleted.
47  */
48 class DELETED_SHEET_ITEM : public SCH_ITEM
49 {
50 public:
DELETED_SHEET_ITEM()51     DELETED_SHEET_ITEM() :
52         SCH_ITEM( nullptr, NOT_USED )
53     {}
54 
GetSelectMenuText(EDA_UNITS aUnits) const55     wxString GetSelectMenuText( EDA_UNITS aUnits ) const override
56     {
57         return _( "(Deleted Item)" );
58     }
59 
GetClass() const60     wxString GetClass() const override
61     {
62         return wxT( "DELETED_SHEET_ITEM" );
63     }
64 
GetInstance()65     static DELETED_SHEET_ITEM* GetInstance()
66     {
67         static DELETED_SHEET_ITEM* item = nullptr;
68 
69         if( !item )
70             item = new DELETED_SHEET_ITEM();
71 
72         return item;
73     }
74 
75     // pure virtuals:
SetPosition(const wxPoint &)76     void SetPosition( const wxPoint& ) override {}
Print(const RENDER_SETTINGS * aSettings,const wxPoint & aOffset)77     void Print( const RENDER_SETTINGS* aSettings, const wxPoint& aOffset ) override {}
Move(const wxPoint & aMoveVector)78     void Move( const wxPoint& aMoveVector ) override {}
MirrorHorizontally(int aCenter)79     void MirrorHorizontally( int aCenter ) override {}
MirrorVertically(int aCenter)80     void MirrorVertically( int aCenter ) override {}
Rotate(const wxPoint & aCenter)81     void Rotate( const wxPoint& aCenter ) override {}
82 
83 #if defined(DEBUG)
Show(int,std::ostream &) const84     void Show( int , std::ostream&  ) const override {}
85 #endif
86 };
87 
88 
89 namespace std
90 {
operator ()(const SCH_SHEET_PATH & path) const91     size_t hash<SCH_SHEET_PATH>::operator()( const SCH_SHEET_PATH& path ) const
92     {
93         return path.GetCurrentHash();
94     }
95 }
96 
97 
SCH_SHEET_PATH()98 SCH_SHEET_PATH::SCH_SHEET_PATH()
99 {
100     m_virtualPageNumber = 0;
101     m_current_hash = 0;
102 }
103 
104 
SCH_SHEET_PATH(const SCH_SHEET_PATH & aOther)105 SCH_SHEET_PATH::SCH_SHEET_PATH( const SCH_SHEET_PATH& aOther )
106 {
107     initFromOther( aOther );
108 }
109 
110 
operator =(const SCH_SHEET_PATH & aOther)111 SCH_SHEET_PATH& SCH_SHEET_PATH::operator=( const SCH_SHEET_PATH& aOther )
112 {
113     initFromOther( aOther );
114     return *this;
115 }
116 
117 
initFromOther(const SCH_SHEET_PATH & aOther)118 void SCH_SHEET_PATH::initFromOther( const SCH_SHEET_PATH& aOther )
119 {
120     m_sheets            = aOther.m_sheets;
121     m_virtualPageNumber = aOther.m_virtualPageNumber;
122     m_current_hash      = aOther.m_current_hash;
123 
124     // Note: don't copy m_recursion_test_cache as it is slow and we want SCH_SHEET_PATHS to be
125     // very fast to construct for use in the connectivity algorithm.
126 }
127 
128 
IsFullPath() const129 bool SCH_SHEET_PATH::IsFullPath() const
130 {
131     return GetSheet( 0 ) && GetSheet( 0 )->IsRootSheet();
132 }
133 
134 
Rehash()135 void SCH_SHEET_PATH::Rehash()
136 {
137     m_current_hash = 0;
138 
139     for( auto sheet : m_sheets )
140         boost::hash_combine( m_current_hash, sheet->m_Uuid.Hash() );
141 }
142 
143 
Cmp(const SCH_SHEET_PATH & aSheetPathToTest) const144 int SCH_SHEET_PATH::Cmp( const SCH_SHEET_PATH& aSheetPathToTest ) const
145 {
146     if( size() > aSheetPathToTest.size() )
147         return 1;
148 
149     if( size() < aSheetPathToTest.size() )
150         return -1;
151 
152     //otherwise, same number of sheets.
153     for( unsigned i = 0; i < size(); i++ )
154     {
155         if( at( i )->m_Uuid < aSheetPathToTest.at( i )->m_Uuid )
156             return -1;
157 
158         if( at( i )->m_Uuid != aSheetPathToTest.at( i )->m_Uuid )
159             return 1;
160     }
161 
162     return 0;
163 }
164 
165 
ComparePageNumAndName(const SCH_SHEET_PATH & aSheetPathToTest) const166 int SCH_SHEET_PATH::ComparePageNumAndName( const SCH_SHEET_PATH& aSheetPathToTest ) const
167 {
168     wxString pageA = GetPageNumber();
169     wxString pageB = aSheetPathToTest.GetPageNumber();
170 
171     int pageNumComp = SCH_SHEET::ComparePageNum( pageA, pageB );
172 
173     if( pageNumComp == 0 )
174     {
175         wxString nameA = Last()->GetName();
176         wxString nameB = aSheetPathToTest.Last()->GetName();
177 
178         return nameA.Cmp( nameB );
179     }
180     else
181     {
182         return pageNumComp;
183     }
184 }
185 
186 
IsContainedWithin(const SCH_SHEET_PATH & aSheetPathToTest) const187 bool SCH_SHEET_PATH::IsContainedWithin( const SCH_SHEET_PATH& aSheetPathToTest ) const
188 {
189     if( aSheetPathToTest.size() > size() )
190         return false;
191 
192     for( size_t i = 0; i < aSheetPathToTest.size(); ++i )
193     {
194         if( at( i )->m_Uuid != aSheetPathToTest.at( i )->m_Uuid )
195             return false;
196     }
197 
198     return true;
199 }
200 
201 
Last() const202 SCH_SHEET* SCH_SHEET_PATH::Last() const
203 {
204     if( !empty() )
205         return m_sheets.back();
206 
207     return nullptr;
208 }
209 
210 
LastScreen()211 SCH_SCREEN* SCH_SHEET_PATH::LastScreen()
212 {
213     SCH_SHEET* lastSheet = Last();
214 
215     if( lastSheet )
216         return lastSheet->GetScreen();
217 
218     return nullptr;
219 }
220 
221 
LastScreen() const222 SCH_SCREEN* SCH_SHEET_PATH::LastScreen() const
223 {
224     SCH_SHEET* lastSheet = Last();
225 
226     if( lastSheet )
227         return lastSheet->GetScreen();
228 
229     return nullptr;
230 }
231 
232 
PathAsString() const233 wxString SCH_SHEET_PATH::PathAsString() const
234 {
235     wxString s;
236 
237     s = wxT( "/" );     // This is the root path
238 
239     // Start at 1 to avoid the root sheet, which does not need to be added to the path.
240     // Its timestamp changes anyway.
241     for( unsigned i = 1; i < size(); i++ )
242         s += at( i )->m_Uuid.AsString() + "/";
243 
244     return s;
245 }
246 
247 
Path() const248 KIID_PATH SCH_SHEET_PATH::Path() const
249 {
250     KIID_PATH path;
251 
252     for( const SCH_SHEET* sheet : m_sheets )
253         path.push_back( sheet->m_Uuid );
254 
255     return path;
256 }
257 
258 
PathWithoutRootUuid() const259 KIID_PATH SCH_SHEET_PATH::PathWithoutRootUuid() const
260 {
261     KIID_PATH path;
262 
263     for( size_t i = 1; i < size(); i++ )
264         path.push_back( at( i )->m_Uuid );
265 
266     return path;
267 }
268 
269 
PathHumanReadable(bool aUseShortRootName) const270 wxString SCH_SHEET_PATH::PathHumanReadable( bool aUseShortRootName ) const
271 {
272     wxString s;
273     wxString fileName;
274 
275     if( !empty() && at( 0 )->GetScreen() )
276         fileName = at( 0 )->GetScreen()->GetFileName();
277 
278     wxFileName  fn = fileName;
279 
280     if( aUseShortRootName )
281         s = wxT( "/" );  // Use only the short name in netlists
282     else
283         s = fn.GetName() + wxT( "/" );
284 
285     // Start at 1 since we've already processed the root sheet.
286     for( unsigned i = 1; i < size(); i++ )
287         s = s + at( i )->GetFields()[ SHEETNAME ].GetShownText() + wxT( "/" );
288 
289     return s;
290 }
291 
292 
UpdateAllScreenReferences()293 void SCH_SHEET_PATH::UpdateAllScreenReferences()
294 {
295     for( SCH_ITEM* item : LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
296     {
297         SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
298         symbol->GetField( REFERENCE_FIELD )->SetText( symbol->GetRef( this ) );
299         symbol->GetField( VALUE_FIELD )->SetText( symbol->GetValue( this, false ) );
300         symbol->GetField( FOOTPRINT_FIELD )->SetText( symbol->GetFootprint( this, false ) );
301         symbol->UpdateUnit( symbol->GetUnitSelection( this ) );
302     }
303 }
304 
305 
306 
GetSymbols(SCH_REFERENCE_LIST & aReferences,bool aIncludePowerSymbols,bool aForceIncludeOrphanSymbols) const307 void SCH_SHEET_PATH::GetSymbols( SCH_REFERENCE_LIST& aReferences, bool aIncludePowerSymbols,
308                                  bool aForceIncludeOrphanSymbols ) const
309 {
310     for( SCH_ITEM* item : LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
311     {
312         SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
313         AppendSymbol( aReferences, symbol, aIncludePowerSymbols, aForceIncludeOrphanSymbols );
314     }
315 }
316 
317 
AppendSymbol(SCH_REFERENCE_LIST & aReferences,SCH_SYMBOL * aSymbol,bool aIncludePowerSymbols,bool aForceIncludeOrphanSymbols) const318 void SCH_SHEET_PATH::AppendSymbol( SCH_REFERENCE_LIST& aReferences, SCH_SYMBOL* aSymbol,
319                                    bool aIncludePowerSymbols,
320                                    bool aForceIncludeOrphanSymbols ) const
321 {
322     // Skip pseudo-symbols, which have a reference starting with #.  This mainly
323     // affects power symbols.
324     if( aIncludePowerSymbols || aSymbol->GetRef( this )[0] != wxT( '#' ) )
325     {
326         LIB_SYMBOL* symbol = aSymbol->GetLibSymbolRef().get();
327 
328         if( symbol || aForceIncludeOrphanSymbols )
329         {
330             SCH_REFERENCE schReference( aSymbol, symbol, *this );
331 
332             schReference.SetSheetNumber( m_virtualPageNumber );
333             aReferences.AddItem( schReference );
334         }
335     }
336 }
337 
338 
GetMultiUnitSymbols(SCH_MULTI_UNIT_REFERENCE_MAP & aRefList,bool aIncludePowerSymbols) const339 void SCH_SHEET_PATH::GetMultiUnitSymbols( SCH_MULTI_UNIT_REFERENCE_MAP& aRefList,
340                                           bool aIncludePowerSymbols ) const
341 {
342     for( SCH_ITEM* item : LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
343     {
344         SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
345         AppendMultiUnitSymbol( aRefList, symbol, aIncludePowerSymbols );
346     }
347 }
348 
349 
AppendMultiUnitSymbol(SCH_MULTI_UNIT_REFERENCE_MAP & aRefList,SCH_SYMBOL * aSymbol,bool aIncludePowerSymbols) const350 void SCH_SHEET_PATH::AppendMultiUnitSymbol( SCH_MULTI_UNIT_REFERENCE_MAP& aRefList,
351                                             SCH_SYMBOL* aSymbol,
352                                             bool aIncludePowerSymbols ) const
353 {
354     // Skip pseudo-symbols, which have a reference starting with #.  This mainly
355     // affects power symbols.
356     if( !aIncludePowerSymbols && aSymbol->GetRef( this )[0] == wxT( '#' ) )
357         return;
358 
359     LIB_SYMBOL* symbol = aSymbol->GetLibSymbolRef().get();
360 
361     if( symbol && symbol->GetUnitCount() > 1 )
362     {
363         SCH_REFERENCE schReference = SCH_REFERENCE( aSymbol, symbol, *this );
364         schReference.SetSheetNumber( m_virtualPageNumber );
365         wxString reference_str = schReference.GetRef();
366 
367         // Never lock unassigned references
368         if( reference_str[reference_str.Len() - 1] == '?' )
369             return;
370 
371         aRefList[reference_str].AddItem( schReference );
372     }
373 }
374 
375 
operator ==(const SCH_SHEET_PATH & d1) const376 bool SCH_SHEET_PATH::operator==( const SCH_SHEET_PATH& d1 ) const
377 {
378     return m_current_hash == d1.GetCurrentHash();
379 }
380 
381 
TestForRecursion(const wxString & aSrcFileName,const wxString & aDestFileName)382 bool SCH_SHEET_PATH::TestForRecursion( const wxString& aSrcFileName, const wxString& aDestFileName )
383 {
384     auto pair = std::make_pair( aSrcFileName, aDestFileName );
385 
386     if( m_recursion_test_cache.count( pair ) )
387         return m_recursion_test_cache.at( pair );
388 
389     SCHEMATIC* sch = LastScreen()->Schematic();
390 
391     wxCHECK_MSG( sch, false, "No SCHEMATIC found in SCH_SHEET_PATH::TestForRecursion!" );
392 
393     wxFileName rootFn = sch->GetFileName();
394     wxFileName srcFn = aSrcFileName;
395     wxFileName destFn = aDestFileName;
396 
397     if( srcFn.IsRelative() )
398         srcFn.MakeAbsolute( rootFn.GetPath() );
399 
400     if( destFn.IsRelative() )
401         destFn.MakeAbsolute( rootFn.GetPath() );
402 
403 
404     // The source and destination sheet file names cannot be the same.
405     if( srcFn == destFn )
406     {
407         m_recursion_test_cache[pair] = true;
408         return true;
409     }
410 
411     /// @todo Store sheet file names with full path, either relative to project path
412     ///       or absolute path.  The current design always assumes subsheet files are
413     ///       located in the project folder which may or may not be desirable.
414     unsigned i = 0;
415 
416     while( i < size() )
417     {
418         wxFileName cmpFn = at( i )->GetFileName();
419 
420         if( cmpFn.IsRelative() )
421             cmpFn.MakeAbsolute( rootFn.GetPath() );
422 
423         // Test if the file name of the destination sheet is in anywhere in this sheet path.
424         if( cmpFn == destFn )
425             break;
426 
427         i++;
428     }
429 
430     // The destination sheet file name was not found in the sheet path or the destination
431     // sheet file name is the root sheet so no recursion is possible.
432     if( i >= size() || i == 0 )
433     {
434         m_recursion_test_cache[pair] = false;
435         return false;
436     }
437 
438     // Walk back up to the root sheet to see if the source file name is already a parent in
439     // the sheet path.  If so, recursion will occur.
440     do
441     {
442         i -= 1;
443 
444         wxFileName cmpFn = at( i )->GetFileName();
445 
446         if( cmpFn.IsRelative() )
447             cmpFn.MakeAbsolute( rootFn.GetPath() );
448 
449         if( cmpFn == srcFn )
450         {
451             m_recursion_test_cache[pair] = true;
452             return true;
453         }
454 
455     } while( i != 0 );
456 
457     // The source sheet file name is not a parent of the destination sheet file name.
458     m_recursion_test_cache[pair] = false;
459     return false;
460 }
461 
462 
GetPageNumber() const463 wxString SCH_SHEET_PATH::GetPageNumber() const
464 {
465     SCH_SHEET* sheet = Last();
466 
467     wxCHECK( sheet, wxEmptyString );
468 
469     return sheet->GetPageNumber( *this );
470 }
471 
472 
SetPageNumber(const wxString & aPageNumber)473 void SCH_SHEET_PATH::SetPageNumber( const wxString& aPageNumber )
474 {
475     SCH_SHEET* sheet = Last();
476 
477     wxCHECK( sheet, /* void */ );
478 
479     sheet->SetPageNumber( *this, aPageNumber );
480 }
481 
482 
MakeFilePathRelativeToParentSheet()483 void SCH_SHEET_PATH::MakeFilePathRelativeToParentSheet()
484 {
485     wxCHECK( m_sheets.size() > 1, /* void */  );
486 
487     wxFileName sheetFileName = Last()->GetFileName();
488 
489     // If the sheet file name is absolute, then the user requested is so don't make it relative.
490     if( sheetFileName.IsAbsolute() )
491         return;
492 
493     SCH_SCREEN* screen = LastScreen();
494     SCH_SCREEN* parentScreen = m_sheets[ m_sheets.size() - 2 ]->GetScreen();
495 
496     wxCHECK( screen && parentScreen, /* void */ );
497 
498     wxFileName fileName = screen->GetFileName();
499     wxFileName parentFileName = parentScreen->GetFileName();
500 
501     // SCH_SCREEN file names must be absolute.  If they are not, someone set them incorrectly
502     // on load or on creation.
503     wxCHECK( fileName.IsAbsolute() && parentFileName.IsAbsolute(), /* void */ );
504 
505     if( fileName.GetPath() == parentFileName.GetPath() )
506     {
507         Last()->SetFileName( fileName.GetFullName() );
508     }
509     else if( fileName.MakeRelativeTo( parentFileName.GetPath() ) )
510     {
511         Last()->SetFileName( fileName.GetFullPath() );
512     }
513     else
514     {
515         Last()->SetFileName( screen->GetFileName() );
516     }
517 
518     wxLogTrace( tracePathsAndFiles,
519                 wxT( "\n    File name: '%s'"
520                      "\n    parent file name '%s',"
521                      "\n    sheet '%s' file name '%s'." ),
522                 screen->GetFileName(), parentScreen->GetFileName(), PathHumanReadable(),
523                 Last()->GetFileName() );
524 }
525 
526 
SCH_SHEET_LIST(SCH_SHEET * aSheet,bool aCheckIntegrity)527 SCH_SHEET_LIST::SCH_SHEET_LIST( SCH_SHEET* aSheet, bool aCheckIntegrity )
528 {
529     if( aSheet != nullptr )
530     {
531         BuildSheetList( aSheet, aCheckIntegrity );
532 
533         if( aSheet->IsRootSheet() )
534             SortByPageNumbers();
535     }
536 }
537 
538 
BuildSheetList(SCH_SHEET * aSheet,bool aCheckIntegrity)539 void SCH_SHEET_LIST::BuildSheetList( SCH_SHEET* aSheet, bool aCheckIntegrity )
540 {
541     wxCHECK_RET( aSheet != nullptr, wxT( "Cannot build sheet list from undefined sheet." ) );
542 
543     std::vector<SCH_SHEET*> badSheets;
544 
545     m_currentSheetPath.push_back( aSheet );
546     m_currentSheetPath.SetVirtualPageNumber( static_cast<int>( size() ) + 1 );
547     push_back( m_currentSheetPath );
548 
549     if( m_currentSheetPath.LastScreen() )
550     {
551         wxString               parentFileName = aSheet->GetFileName();
552         std::vector<SCH_ITEM*> childSheets;
553         m_currentSheetPath.LastScreen()->GetSheets( &childSheets );
554 
555         for( SCH_ITEM* item : childSheets )
556         {
557             SCH_SHEET* sheet = static_cast<SCH_SHEET*>( item );
558 
559             if( aCheckIntegrity )
560             {
561                 if( !m_currentSheetPath.TestForRecursion( sheet->GetFileName(), parentFileName ) )
562                     BuildSheetList( sheet, true );
563                 else
564                     badSheets.push_back( sheet );
565             }
566             else
567             {
568                 BuildSheetList( sheet, false );
569             }
570         }
571     }
572 
573     if( aCheckIntegrity )
574     {
575         for( SCH_SHEET* sheet : badSheets )
576         {
577             m_currentSheetPath.LastScreen()->Remove( sheet );
578             m_currentSheetPath.LastScreen()->SetContentModified();
579         }
580     }
581 
582     m_currentSheetPath.pop_back();
583 }
584 
585 
SortByPageNumbers(bool aUpdateVirtualPageNums)586 void SCH_SHEET_LIST::SortByPageNumbers( bool aUpdateVirtualPageNums )
587 {
588     std::sort( begin(), end(),
589         []( SCH_SHEET_PATH a, SCH_SHEET_PATH b ) -> bool
590         {
591              return a.ComparePageNumAndName( b ) < 0;
592         } );
593 
594     if( aUpdateVirtualPageNums )
595     {
596         int virtualPageNum = 1;
597 
598         for( SCH_SHEET_PATH& sheet : *this )
599         {
600             sheet.SetVirtualPageNumber( virtualPageNum++ );
601         }
602     }
603 }
604 
605 
NameExists(const wxString & aSheetName) const606 bool SCH_SHEET_LIST::NameExists( const wxString& aSheetName ) const
607 {
608     for( const SCH_SHEET_PATH& sheet : *this )
609     {
610         if( sheet.Last()->GetName() == aSheetName )
611             return true;
612     }
613 
614     return false;
615 }
616 
617 
PageNumberExists(const wxString & aPageNumber) const618 bool SCH_SHEET_LIST::PageNumberExists( const wxString& aPageNumber ) const
619 {
620     for( const SCH_SHEET_PATH& sheet : *this )
621     {
622         if( sheet.GetPageNumber() == aPageNumber )
623             return true;
624     }
625 
626     return false;
627 }
628 
629 
IsModified() const630 bool SCH_SHEET_LIST::IsModified() const
631 {
632     for( const SCH_SHEET_PATH& sheet : *this )
633     {
634         if( sheet.LastScreen() && sheet.LastScreen()->IsContentModified() )
635             return true;
636     }
637 
638     return false;
639 }
640 
641 
ClearModifyStatus()642 void SCH_SHEET_LIST::ClearModifyStatus()
643 {
644     for( const SCH_SHEET_PATH& sheet : *this )
645     {
646         if( sheet.LastScreen() )
647             sheet.LastScreen()->SetContentModified( false );
648     }
649 }
650 
651 
GetItem(const KIID & aID,SCH_SHEET_PATH * aPathOut) const652 SCH_ITEM* SCH_SHEET_LIST::GetItem( const KIID& aID, SCH_SHEET_PATH* aPathOut ) const
653 {
654     for( const SCH_SHEET_PATH& sheet : *this )
655     {
656         SCH_SCREEN* screen = sheet.LastScreen();
657 
658         for( SCH_ITEM* aItem : screen->Items() )
659         {
660             if( aItem->m_Uuid == aID )
661             {
662                 if( aPathOut )
663                     *aPathOut = sheet;
664 
665                 return aItem;
666             }
667 
668             SCH_ITEM* childMatch = nullptr;
669 
670             aItem->RunOnChildren(
671                     [&]( SCH_ITEM* aChild )
672                     {
673                         if( aChild->m_Uuid == aID )
674                             childMatch = aChild;
675                     } );
676 
677             if( childMatch )
678             {
679                 if( aPathOut )
680                     *aPathOut = sheet;
681 
682                 return childMatch;
683             }
684         }
685     }
686 
687     // Not found; weak reference has been deleted.
688     return DELETED_SHEET_ITEM::GetInstance();
689 }
690 
691 
FillItemMap(std::map<KIID,EDA_ITEM * > & aMap)692 void SCH_SHEET_LIST::FillItemMap( std::map<KIID, EDA_ITEM*>& aMap )
693 {
694     for( const SCH_SHEET_PATH& sheet : *this )
695     {
696         SCH_SCREEN* screen = sheet.LastScreen();
697 
698         for( SCH_ITEM* aItem : screen->Items() )
699         {
700             aMap[ aItem->m_Uuid ] = aItem;
701 
702             aItem->RunOnChildren(
703                     [&]( SCH_ITEM* aChild )
704                     {
705                         aMap[ aChild->m_Uuid ] = aChild;
706                     } );
707         }
708     }
709 }
710 
711 
AnnotatePowerSymbols()712 void SCH_SHEET_LIST::AnnotatePowerSymbols()
713 {
714     // List of reference for power symbols
715     SCH_REFERENCE_LIST references;
716     SCH_REFERENCE_LIST additionalreferences; // Todo: add as a parameter to this function
717 
718     // Map of locked symbols (not used, but needed by Annotate()
719     SCH_MULTI_UNIT_REFERENCE_MAP lockedSymbols;
720 
721     // Build the list of power symbols:
722     for( SCH_SHEET_PATH& sheet : *this )
723     {
724         for( SCH_ITEM* item : sheet.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
725         {
726             SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
727             LIB_SYMBOL* libSymbol = symbol->GetLibSymbolRef().get();
728 
729             if( libSymbol && libSymbol->IsPower() )
730             {
731                 SCH_REFERENCE schReference( symbol, libSymbol, sheet );
732                 references.AddItem( schReference );
733             }
734         }
735     }
736 
737     // Find duplicate, and silently clear annotation of duplicate
738     std::map<wxString, int> ref_list;   // stores the existing references
739 
740     for( unsigned ii = 0; ii< references.GetCount(); ++ii )
741     {
742         wxString curr_ref = references[ii].GetRef();
743 
744         if( ref_list.find( curr_ref ) == ref_list.end() )
745         {
746             ref_list[curr_ref] = ii;
747             continue;
748         }
749 
750         // Possible duplicate, if the ref ends by a number:
751         if( curr_ref.Last() < '0' && curr_ref.Last() > '9' )
752             continue;   // not annotated
753 
754         // Duplicate: clear annotation by removing the number ending the ref
755         while( curr_ref.Last() >= '0' && curr_ref.Last() <= '9' )
756             curr_ref.RemoveLast();
757 
758         references[ii].SetRef( curr_ref );
759     }
760 
761     // Break full symbol reference into name (prefix) and number:
762     // example: IC1 become IC, and 1
763     references.SplitReferences();
764 
765     // Ensure all power symbols have the reference starting by '#'
766     // (Not sure this is really useful)
767     for( unsigned ii = 0; ii< references.GetCount(); ++ii )
768     {
769         if( references[ii].GetRef()[0] != '#' )
770         {
771             wxString new_ref = "#" + references[ii].GetRef();
772             references[ii].SetRef( new_ref );
773         }
774     }
775 
776     // Recalculate and update reference numbers in schematic
777     references.Annotate( false, 0, 100, lockedSymbols, additionalreferences );
778     references.UpdateAnnotation();
779 }
780 
781 
GetSymbols(SCH_REFERENCE_LIST & aReferences,bool aIncludePowerSymbols,bool aForceIncludeOrphanSymbols) const782 void SCH_SHEET_LIST::GetSymbols( SCH_REFERENCE_LIST& aReferences, bool aIncludePowerSymbols,
783                                  bool aForceIncludeOrphanSymbols ) const
784 {
785     for( const SCH_SHEET_PATH& sheet : *this )
786         sheet.GetSymbols( aReferences, aIncludePowerSymbols, aForceIncludeOrphanSymbols );
787 }
788 
789 
GetSymbolsWithinPath(SCH_REFERENCE_LIST & aReferences,const SCH_SHEET_PATH & aSheetPath,bool aIncludePowerSymbols,bool aForceIncludeOrphanSymbols) const790 void SCH_SHEET_LIST::GetSymbolsWithinPath( SCH_REFERENCE_LIST&   aReferences,
791                                            const SCH_SHEET_PATH& aSheetPath,
792                                            bool                  aIncludePowerSymbols,
793                                            bool                  aForceIncludeOrphanSymbols ) const
794 {
795     for( const SCH_SHEET_PATH& sheet : *this )
796     {
797         if( sheet.IsContainedWithin( aSheetPath ) )
798             sheet.GetSymbols( aReferences, aIncludePowerSymbols, aForceIncludeOrphanSymbols );
799     }
800 }
801 
802 
GetSheetsWithinPath(SCH_SHEET_PATHS & aSheets,const SCH_SHEET_PATH & aSheetPath) const803 void SCH_SHEET_LIST::GetSheetsWithinPath( SCH_SHEET_PATHS&      aSheets,
804                                           const SCH_SHEET_PATH& aSheetPath ) const
805 {
806     for( const SCH_SHEET_PATH& sheet : *this )
807     {
808         if( sheet.IsContainedWithin( aSheetPath ) )
809             aSheets.push_back( sheet );
810     }
811 }
812 
813 
GetMultiUnitSymbols(SCH_MULTI_UNIT_REFERENCE_MAP & aRefList,bool aIncludePowerSymbols) const814 void SCH_SHEET_LIST::GetMultiUnitSymbols( SCH_MULTI_UNIT_REFERENCE_MAP &aRefList,
815                                           bool aIncludePowerSymbols ) const
816 {
817     for( SCH_SHEET_PATHS::const_iterator it = begin(); it != end(); ++it )
818     {
819         SCH_MULTI_UNIT_REFERENCE_MAP tempMap;
820         ( *it ).GetMultiUnitSymbols( tempMap );
821 
822         for( SCH_MULTI_UNIT_REFERENCE_MAP::value_type& pair : tempMap )
823         {
824             // Merge this list into the main one
825             unsigned n_refs = pair.second.GetCount();
826 
827             for( unsigned thisRef = 0; thisRef < n_refs; ++thisRef )
828                 aRefList[pair.first].AddItem( pair.second[thisRef] );
829         }
830     }
831 }
832 
833 
TestForRecursion(const SCH_SHEET_LIST & aSrcSheetHierarchy,const wxString & aDestFileName)834 bool SCH_SHEET_LIST::TestForRecursion( const SCH_SHEET_LIST& aSrcSheetHierarchy,
835                                        const wxString& aDestFileName )
836 {
837     if( empty() )
838         return false;
839 
840     SCHEMATIC* sch = at( 0 ).LastScreen()->Schematic();
841 
842     wxCHECK_MSG( sch, false, "No SCHEMATIC found in SCH_SHEET_LIST::TestForRecursion!" );
843 
844     wxFileName rootFn = sch->GetFileName();
845     wxFileName destFn = aDestFileName;
846 
847     if( destFn.IsRelative() )
848         destFn.MakeAbsolute( rootFn.GetPath() );
849 
850     // Test each SCH_SHEET_PATH in this SCH_SHEET_LIST for potential recursion.
851     for( unsigned i = 0; i < size(); i++ )
852     {
853         // Test each SCH_SHEET_PATH in the source sheet.
854         for( unsigned j = 0; j < aSrcSheetHierarchy.size(); j++ )
855         {
856             const SCH_SHEET_PATH* sheetPath = &aSrcSheetHierarchy[j];
857 
858             for( unsigned k = 0; k < sheetPath->size(); k++ )
859             {
860                 if( at( i ).TestForRecursion( sheetPath->GetSheet( k )->GetFileName(),
861                                               aDestFileName ) )
862                 {
863                     return true;
864                 }
865             }
866         }
867     }
868 
869     // The source sheet file can safely be added to the destination sheet file.
870     return false;
871 }
872 
873 
FindSheetForScreen(const SCH_SCREEN * aScreen)874 SCH_SHEET_PATH* SCH_SHEET_LIST::FindSheetForScreen( const SCH_SCREEN* aScreen )
875 {
876     for( SCH_SHEET_PATH& sheetpath : *this )
877     {
878         if( sheetpath.LastScreen() == aScreen )
879             return &sheetpath;
880     }
881 
882     return nullptr;
883 }
884 
885 
FindAllSheetsForScreen(const SCH_SCREEN * aScreen) const886 SCH_SHEET_LIST SCH_SHEET_LIST::FindAllSheetsForScreen( const SCH_SCREEN* aScreen ) const
887 {
888     SCH_SHEET_LIST retval;
889 
890     for( const SCH_SHEET_PATH& sheetpath : *this )
891     {
892         if( sheetpath.LastScreen() == aScreen )
893             retval.push_back( sheetpath );
894     }
895 
896     return retval;
897 }
898 
899 
UpdateSymbolInstances(const std::vector<SYMBOL_INSTANCE_REFERENCE> & aSymbolInstances)900 void SCH_SHEET_LIST::UpdateSymbolInstances(
901                                 const std::vector<SYMBOL_INSTANCE_REFERENCE>& aSymbolInstances )
902 {
903     SCH_REFERENCE_LIST symbolInstances;
904 
905     GetSymbols( symbolInstances, true, true );
906 
907     std::map<KIID_PATH, wxString> pathNameCache;
908 
909     // Calculating the name of a path is somewhat expensive; on large designs with many symbols
910     // this can blow up to a serious amount of time when loading the schematic
911     auto getName = [&pathNameCache]( const KIID_PATH& aPath ) -> const wxString&
912                    {
913                        if( pathNameCache.count( aPath ) )
914                            return pathNameCache.at( aPath );
915 
916                        pathNameCache[aPath] = aPath.AsString();
917                        return pathNameCache[aPath];
918                    };
919 
920     for( size_t i = 0; i < symbolInstances.GetCount(); i++ )
921     {
922         // The instance paths are stored in the file sans root path so the comparison
923         // should not include the root path.
924         wxString path = symbolInstances[i].GetPath();
925 
926         auto it = std::find_if( aSymbolInstances.begin(), aSymbolInstances.end(),
927                                 [ path, &getName ]( const SYMBOL_INSTANCE_REFERENCE& r ) -> bool
928                                 {
929                                     return path == getName( r.m_Path );
930                                 } );
931 
932         if( it == aSymbolInstances.end() )
933         {
934             wxLogTrace( traceSchSheetPaths, "No symbol instance found for path '%s'", path );
935             continue;
936         }
937 
938         SCH_SYMBOL* symbol = symbolInstances[ i ].GetSymbol();
939 
940         wxCHECK2( symbol, continue );
941 
942         // Symbol instance paths are stored and looked up in memory with the root path so use
943         // the full path here.
944         symbol->AddHierarchicalReference( symbolInstances[i].GetSheetPath().Path(),
945                                           it->m_Reference, it->m_Unit, it->m_Value,
946                                           it->m_Footprint );
947         symbol->GetField( REFERENCE_FIELD )->SetText( it->m_Reference );
948     }
949 }
950 
951 
UpdateSheetInstances(const std::vector<SCH_SHEET_INSTANCE> & aSheetInstances)952 void SCH_SHEET_LIST::UpdateSheetInstances( const std::vector<SCH_SHEET_INSTANCE>& aSheetInstances )
953 {
954 
955     for( const SCH_SHEET_PATH& path : *this )
956     {
957         SCH_SHEET* sheet = path.Last();
958 
959         wxCHECK2( sheet, continue );
960 
961         auto it = std::find_if( aSheetInstances.begin(), aSheetInstances.end(),
962                                 [ path ]( const SCH_SHEET_INSTANCE& r ) -> bool
963                                 {
964                                     return path.PathWithoutRootUuid() == r.m_Path;
965                                 } );
966 
967         if( it == aSheetInstances.end() )
968         {
969             wxLogTrace( traceSchSheetPaths, "No sheet instance found for path '%s'",
970                         path.PathWithoutRootUuid().AsString() );
971             continue;
972         }
973 
974         wxLogTrace( traceSchSheetPaths, "Setting sheet '%s' instance '%s' page number '%s'",
975                     ( sheet->GetName().IsEmpty() ) ? wxT( "root" ) : sheet->GetName(),
976                     path.PathWithoutRootUuid().AsString(), it->m_PageNumber );
977         sheet->AddInstance( path );
978         sheet->SetPageNumber( path, it->m_PageNumber );
979     }
980 }
981 
982 
GetPaths() const983 std::vector<KIID_PATH> SCH_SHEET_LIST::GetPaths() const
984 {
985     std::vector<KIID_PATH> paths;
986 
987     for( const SCH_SHEET_PATH& sheetPath : *this )
988         paths.emplace_back( sheetPath.Path() );
989 
990     return paths;
991 }
992 
993 
GetSheetInstances() const994 std::vector<SCH_SHEET_INSTANCE> SCH_SHEET_LIST::GetSheetInstances() const
995 {
996     std::vector<SCH_SHEET_INSTANCE> retval;
997 
998     for( const SCH_SHEET_PATH& path : *this )
999     {
1000         const SCH_SHEET* sheet = path.Last();
1001 
1002         wxCHECK2( sheet, continue );
1003 
1004         SCH_SHEET_INSTANCE instance;
1005         instance.m_Path = path.PathWithoutRootUuid();
1006         instance.m_PageNumber = sheet->GetPageNumber( path );
1007 
1008         retval.push_back( instance );
1009     }
1010 
1011     return retval;
1012 }
1013 
1014 
AllSheetPageNumbersEmpty() const1015 bool SCH_SHEET_LIST::AllSheetPageNumbersEmpty() const
1016 {
1017     for( const SCH_SHEET_PATH& instance : *this )
1018     {
1019         const SCH_SHEET* sheet = instance.Last();
1020 
1021         wxCHECK2( sheet, continue );
1022 
1023         if( !sheet->GetPageNumber( instance ).IsEmpty() )
1024             return false;
1025     }
1026 
1027     return true;
1028 }
1029 
1030 
SetInitialPageNumbers()1031 void SCH_SHEET_LIST::SetInitialPageNumbers()
1032 {
1033     // Don't accidentally renumber existing sheets.
1034     wxCHECK( AllSheetPageNumbersEmpty(), /* void */ );
1035 
1036     wxString tmp;
1037     int pageNumber = 1;
1038 
1039     for( const SCH_SHEET_PATH& instance : *this )
1040     {
1041         SCH_SHEET* sheet = instance.Last();
1042 
1043         wxCHECK2( sheet, continue );
1044 
1045         sheet->AddInstance( instance );
1046         tmp.Printf( "%d", pageNumber );
1047         sheet->SetPageNumber( instance, tmp );
1048         pageNumber += 1;
1049     }
1050 }
1051