1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2015 Chris Pavlina <pavlina.chris@gmail.com>
5  * Copyright (C) 2015-2021 KiCad Developers, see change_log.txt for contributors.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, you may find one here:
19  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20  * or you may search the http://www.gnu.org website for the version 2 license,
21  * or you may write to the Free Software Foundation, Inc.,
22  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
23  */
24 
25 #include <sch_draw_panel.h>
26 #include <symbol_library.h>
27 #include <confirm.h>
28 #include <connection_graph.h>
29 #include <invoke_sch_dialog.h>
30 #include <kiway.h>
31 #include <symbol_viewer_frame.h>
32 #include <project_rescue.h>
33 #include <sch_symbol.h>
34 #include <sch_sheet.h>
35 #include <sch_edit_frame.h>
36 #include <schematic.h>
37 #include <symbol_lib_table.h>
38 #include <wildcards_and_files_ext.h>
39 
40 #include <cctype>
41 #include <map>
42 
43 
44 typedef std::pair<SCH_SYMBOL*, wxString> SYMBOL_NAME_PAIR;
45 
46 
47 // Helper sort function, used in getSymbols, to sort a symbol list by lib_id
sort_by_libid(const SCH_SYMBOL * ref,SCH_SYMBOL * cmp)48 static bool sort_by_libid( const SCH_SYMBOL* ref, SCH_SYMBOL* cmp )
49 {
50     return ref->GetLibId() < cmp->GetLibId();
51 }
52 
53 
54 /**
55  * Fill a vector with all of the project's symbols, to ease iterating over them.
56  *
57  * The list is sorted by #LIB_ID, therefore symbols using the same library
58  * symbol are grouped, allowing later faster calculations (one library search by group
59  * of symbols)
60  *
61  * @param aSymbols is a vector that will take the symbols.
62  */
getSymbols(SCHEMATIC * aSchematic,std::vector<SCH_SYMBOL * > & aSymbols)63 static void getSymbols( SCHEMATIC* aSchematic, std::vector<SCH_SYMBOL*>& aSymbols )
64 {
65     SCH_SCREENS screens( aSchematic->Root() );
66 
67     // Get the full list
68     for( SCH_SCREEN* screen = screens.GetFirst(); screen; screen = screens.GetNext() )
69     {
70         for( auto aItem : screen->Items().OfType( SCH_SYMBOL_T ) )
71             aSymbols.push_back( static_cast<SCH_SYMBOL*>( aItem ) );
72     }
73 
74     if( aSymbols.empty() )
75         return;
76 
77     // sort aSymbols by lib symbol. symbols will be grouped by same lib symbol.
78     std::sort( aSymbols.begin(), aSymbols.end(), sort_by_libid );
79 }
80 
81 
82 /**
83  * Search the libraries for the first symbol with a given name.
84  *
85  * @param aName - name to search for
86  * @param aLibs - the loaded SYMBOL_LIBS
87  * @param aCached - whether we are looking for the cached symbol
88  */
findSymbol(const wxString & aName,SYMBOL_LIBS * aLibs,bool aCached)89 static LIB_SYMBOL* findSymbol( const wxString& aName, SYMBOL_LIBS* aLibs, bool aCached )
90 {
91     LIB_SYMBOL *symbol = nullptr;
92     // wxString new_name = LIB_ID::FixIllegalChars( aName, false );
93 
94     for( SYMBOL_LIB& each_lib : *aLibs )
95     {
96         if( aCached && !each_lib.IsCache() )
97             continue;
98 
99         if( !aCached && each_lib.IsCache() )
100             continue;
101 
102         symbol = each_lib.FindSymbol( aName );
103 
104         if( symbol )
105             break;
106     }
107 
108     return symbol;
109 }
110 
111 
GetRescueLibraryFileName(SCHEMATIC * aSchematic)112 static wxFileName GetRescueLibraryFileName( SCHEMATIC* aSchematic )
113 {
114     wxFileName fn = aSchematic->GetFileName();
115     fn.SetName( fn.GetName() + wxT( "-rescue" ) );
116     fn.SetExt( LegacySymbolLibFileExtension );
117     return fn;
118 }
119 
120 
RESCUE_CASE_CANDIDATE(const wxString & aRequestedName,const wxString & aNewName,LIB_SYMBOL * aLibCandidate,int aUnit,int aConvert)121 RESCUE_CASE_CANDIDATE::RESCUE_CASE_CANDIDATE( const wxString& aRequestedName,
122                                               const wxString& aNewName,
123                                               LIB_SYMBOL* aLibCandidate,
124                                               int aUnit,
125                                               int aConvert )
126 {
127     m_requested_name = aRequestedName;
128     m_new_name = aNewName;
129     m_lib_candidate = aLibCandidate;
130     m_unit = aUnit;
131     m_convert = aConvert;
132 }
133 
134 
FindRescues(RESCUER & aRescuer,boost::ptr_vector<RESCUE_CANDIDATE> & aCandidates)135 void RESCUE_CASE_CANDIDATE::FindRescues( RESCUER& aRescuer,
136                                          boost::ptr_vector<RESCUE_CANDIDATE>& aCandidates )
137 {
138     typedef std::map<wxString, RESCUE_CASE_CANDIDATE> candidate_map_t;
139     candidate_map_t candidate_map;
140 
141     // Remember the list of symbols is sorted by symbol name.
142     // So a search in libraries is made only once by group
143     LIB_SYMBOL* case_sensitive_match = nullptr;
144     std::vector<LIB_SYMBOL*> case_insensitive_matches;
145 
146     wxString symbol_name;
147     wxString search_name;
148     wxString last_symbol_name;
149 
150     for( SCH_SYMBOL* eachSymbol : *( aRescuer.GetSymbols() ) )
151     {
152         symbol_name = eachSymbol->GetLibId().GetLibItemName();
153         search_name = LIB_ID::FixIllegalChars( symbol_name, false );
154 
155         if( last_symbol_name != symbol_name )
156         {
157             // A new symbol name is found (a new group starts here).
158             // Search the symbol names candidates only once for this group:
159             last_symbol_name = symbol_name;
160             case_insensitive_matches.clear();
161 
162             LIB_ID id( wxEmptyString, search_name );
163 
164             case_sensitive_match = aRescuer.GetPrj()->SchLibs()->FindLibSymbol( id );
165 
166             // If the case sensitive match failed, try a case insensitive match.
167             if( !case_sensitive_match )
168                 aRescuer.GetPrj()->SchLibs()->FindLibraryNearEntries( case_insensitive_matches,
169                                                                       search_name );
170         }
171 
172         if( case_sensitive_match || !( case_insensitive_matches.size() ) )
173             continue;
174 
175         RESCUE_CASE_CANDIDATE candidate( symbol_name, case_insensitive_matches[0]->GetName(),
176                                          case_insensitive_matches[0],
177                                          eachSymbol->GetUnit(),
178                                          eachSymbol->GetConvert() );
179 
180         candidate_map[symbol_name] = candidate;
181     }
182 
183     // Now, dump the map into aCandidates
184     for( const candidate_map_t::value_type& each_pair : candidate_map )
185     {
186         aCandidates.push_back( new RESCUE_CASE_CANDIDATE( each_pair.second ) );
187     }
188 }
189 
190 
GetActionDescription() const191 wxString RESCUE_CASE_CANDIDATE::GetActionDescription() const
192 {
193     wxString action;
194     action.Printf( _( "Rename %s to %s" ), m_requested_name, m_new_name );
195     return action;
196 }
197 
198 
PerformAction(RESCUER * aRescuer)199 bool RESCUE_CASE_CANDIDATE::PerformAction( RESCUER* aRescuer )
200 {
201     for( SCH_SYMBOL* eachSymbol : *aRescuer->GetSymbols() )
202     {
203         if( eachSymbol->GetLibId().GetLibItemName() != UTF8( m_requested_name ) )
204             continue;
205 
206         LIB_ID libId;
207 
208         libId.SetLibItemName( m_new_name );
209         eachSymbol->SetLibId( libId );
210         eachSymbol->ClearFlags();
211         aRescuer->LogRescue( eachSymbol, m_requested_name, m_new_name );
212     }
213 
214     return true;
215 }
216 
217 
RESCUE_CACHE_CANDIDATE(const wxString & aRequestedName,const wxString & aNewName,LIB_SYMBOL * aCacheCandidate,LIB_SYMBOL * aLibCandidate,int aUnit,int aConvert)218 RESCUE_CACHE_CANDIDATE::RESCUE_CACHE_CANDIDATE( const wxString& aRequestedName,
219                                                 const wxString& aNewName,
220                                                 LIB_SYMBOL* aCacheCandidate,
221                                                 LIB_SYMBOL* aLibCandidate,
222                                                 int aUnit,
223                                                 int aConvert )
224 {
225     m_requested_name = aRequestedName;
226     m_new_name = aNewName;
227     m_cache_candidate = aCacheCandidate;
228     m_lib_candidate = aLibCandidate;
229     m_unit = aUnit;
230     m_convert = aConvert;
231 }
232 
233 
RESCUE_CACHE_CANDIDATE()234 RESCUE_CACHE_CANDIDATE::RESCUE_CACHE_CANDIDATE()
235 {
236     m_cache_candidate = nullptr;
237     m_lib_candidate = nullptr;
238 }
239 
240 
FindRescues(RESCUER & aRescuer,boost::ptr_vector<RESCUE_CANDIDATE> & aCandidates)241 void RESCUE_CACHE_CANDIDATE::FindRescues( RESCUER& aRescuer,
242                                           boost::ptr_vector<RESCUE_CANDIDATE>& aCandidates )
243 {
244     typedef std::map<wxString, RESCUE_CACHE_CANDIDATE> candidate_map_t;
245     candidate_map_t candidate_map;
246 
247     // Remember the list of symbols is sorted by symbol name.
248     // So a search in libraries is made only once by group
249     LIB_SYMBOL* cache_match = nullptr;
250     LIB_SYMBOL* lib_match = nullptr;
251     wxString symbol_name;
252     wxString search_name;
253     wxString old_symbol_name;
254 
255     for( SCH_SYMBOL* eachSymbol : *( aRescuer.GetSymbols() ) )
256     {
257         symbol_name = eachSymbol->GetLibId().GetLibItemName();
258         search_name = LIB_ID::FixIllegalChars( symbol_name, false );
259 
260         if( old_symbol_name != symbol_name )
261         {
262             // A new symbol name is found (a new group starts here).
263             // Search the symbol names candidates only once for this group:
264             old_symbol_name = symbol_name;
265             cache_match = findSymbol( search_name, aRescuer.GetPrj()->SchLibs(), true );
266             lib_match = findSymbol( search_name, aRescuer.GetPrj()->SchLibs(), false );
267 
268             if( !cache_match && !lib_match )
269                 continue;
270 
271             // Test whether there is a conflict or if the symbol can only be found in the cache
272             // and the symbol name does not have any illegal characters.
273             if( cache_match && lib_match &&
274                 !cache_match->PinsConflictWith( *lib_match, true, true, true, true, false ) )
275                 continue;
276 
277             if( !cache_match && lib_match )
278                 continue;
279 
280             // Check if the symbol has already been rescued.
281             RESCUE_CACHE_CANDIDATE candidate( symbol_name, search_name, cache_match, lib_match,
282                                               eachSymbol->GetUnit(),
283                                               eachSymbol->GetConvert() );
284 
285             candidate_map[symbol_name] = candidate;
286         }
287     }
288 
289     // Now, dump the map into aCandidates
290     for( const candidate_map_t::value_type& each_pair : candidate_map )
291     {
292         aCandidates.push_back( new RESCUE_CACHE_CANDIDATE( each_pair.second ) );
293     }
294 }
295 
296 
GetActionDescription() const297 wxString RESCUE_CACHE_CANDIDATE::GetActionDescription() const
298 {
299     wxString action;
300 
301     if( !m_cache_candidate && !m_lib_candidate )
302         action.Printf( _( "Cannot rescue symbol %s which is not available in any library or "
303                           "the cache." ), m_requested_name );
304     else if( m_cache_candidate && !m_lib_candidate )
305         action.Printf( _( "Rescue symbol %s found only in cache library to %s." ),
306                        m_requested_name, m_new_name );
307     else
308         action.Printf( _( "Rescue modified symbol %s to %s" ),
309                        m_requested_name, m_new_name );
310 
311     return action;
312 }
313 
314 
PerformAction(RESCUER * aRescuer)315 bool RESCUE_CACHE_CANDIDATE::PerformAction( RESCUER* aRescuer )
316 {
317     LIB_SYMBOL* tmp = ( m_cache_candidate ) ? m_cache_candidate : m_lib_candidate;
318 
319     wxCHECK_MSG( tmp, false, "Both cache and library symbols undefined." );
320 
321     std::unique_ptr<LIB_SYMBOL> new_symbol = tmp->Flatten();
322     new_symbol->SetName( m_new_name );
323     aRescuer->AddSymbol( new_symbol.get() );
324 
325     for( SCH_SYMBOL* eachSymbol : *aRescuer->GetSymbols() )
326     {
327         if( eachSymbol->GetLibId().GetLibItemName() != UTF8( m_requested_name ) )
328             continue;
329 
330         LIB_ID libId;
331 
332         libId.SetLibItemName( m_new_name );
333         eachSymbol->SetLibId( libId );
334         eachSymbol->ClearFlags();
335         aRescuer->LogRescue( eachSymbol, m_requested_name, m_new_name );
336     }
337 
338     return true;
339 }
340 
341 
RESCUE_SYMBOL_LIB_TABLE_CANDIDATE(const LIB_ID & aRequestedId,const LIB_ID & aNewId,LIB_SYMBOL * aCacheCandidate,LIB_SYMBOL * aLibCandidate,int aUnit,int aConvert)342 RESCUE_SYMBOL_LIB_TABLE_CANDIDATE::RESCUE_SYMBOL_LIB_TABLE_CANDIDATE(
343     const LIB_ID& aRequestedId,
344     const LIB_ID& aNewId,
345     LIB_SYMBOL* aCacheCandidate,
346     LIB_SYMBOL* aLibCandidate,
347     int aUnit,
348     int aConvert ) : RESCUE_CANDIDATE()
349 {
350     m_requested_id = aRequestedId;
351     m_requested_name = aRequestedId.Format();
352     m_new_id = aNewId;
353     m_lib_candidate = aLibCandidate;
354     m_cache_candidate = aCacheCandidate;
355     m_unit = aUnit;
356     m_convert = aConvert;
357 }
358 
359 
RESCUE_SYMBOL_LIB_TABLE_CANDIDATE()360 RESCUE_SYMBOL_LIB_TABLE_CANDIDATE::RESCUE_SYMBOL_LIB_TABLE_CANDIDATE()
361 {
362     m_cache_candidate = nullptr;
363     m_lib_candidate = nullptr;
364 }
365 
366 
FindRescues(RESCUER & aRescuer,boost::ptr_vector<RESCUE_CANDIDATE> & aCandidates)367 void RESCUE_SYMBOL_LIB_TABLE_CANDIDATE::FindRescues(
368     RESCUER& aRescuer,
369     boost::ptr_vector<RESCUE_CANDIDATE>& aCandidates )
370 {
371     typedef std::map<LIB_ID, RESCUE_SYMBOL_LIB_TABLE_CANDIDATE> candidate_map_t;
372 
373     candidate_map_t candidate_map;
374 
375     // Remember the list of symbols is sorted by LIB_ID.
376     // So a search in libraries is made only once by group
377     LIB_SYMBOL* cache_match = nullptr;
378     LIB_SYMBOL* lib_match = nullptr;
379     LIB_ID old_symbol_id;
380 
381     for( SCH_SYMBOL* eachSymbol : *( aRescuer.GetSymbols() ) )
382     {
383         const LIB_ID& symbol_id = eachSymbol->GetLibId();
384 
385         if( old_symbol_id != symbol_id )
386         {
387             // A new symbol name is found (a new group starts here).
388             // Search the symbol names candidates only once for this group:
389             old_symbol_id = symbol_id;
390 
391             // Get the library symbol from the cache library.  It will be a flattened
392             // symbol by default (no inheritance).
393             cache_match = findSymbol( symbol_id.Format().wx_str(), aRescuer.GetPrj()->SchLibs(),
394                                       true );
395 
396             // Get the library symbol from the symbol library table.
397             lib_match = SchGetLibSymbol( symbol_id, aRescuer.GetPrj()->SchSymbolLibTable() );
398 
399             if( !cache_match && !lib_match )
400                 continue;
401 
402             LIB_SYMBOL_SPTR lib_match_parent;
403 
404             // If it's a derive symbol, use the parent symbol to perform the pin test.
405             if( lib_match && lib_match->IsAlias() )
406             {
407                 lib_match_parent = lib_match->GetParent().lock();
408 
409                 if( !lib_match_parent )
410                     lib_match = nullptr;
411                 else
412                     lib_match = lib_match_parent.get();
413             }
414 
415             // Test whether there is a conflict or if the symbol can only be found in the cache.
416             if( LIB_ID::HasIllegalChars( symbol_id.GetLibItemName() ) == -1 )
417             {
418                 if( cache_match && lib_match &&
419                     !cache_match->PinsConflictWith( *lib_match, true, true, true, true, false ) )
420                 {
421                     continue;
422                 }
423 
424                 if( !cache_match && lib_match )
425                     continue;
426             }
427 
428             // Fix illegal LIB_ID name characters.
429             wxString new_name = LIB_ID::FixIllegalChars( symbol_id.GetLibItemName(), false );
430 
431             // Differentiate symbol name in the rescue library by appending the symbol library
432             // table nickname to the symbol name to prevent name clashes in the rescue library.
433             wxString libNickname = GetRescueLibraryFileName( aRescuer.Schematic() ).GetName();
434 
435             // Spaces in the file name will break the symbol name because they are not
436             // quoted in the symbol library file format.
437             libNickname.Replace( " ", "-" );
438             LIB_ID new_id( libNickname, new_name + "-" + symbol_id.GetLibNickname().wx_str() );
439 
440             RESCUE_SYMBOL_LIB_TABLE_CANDIDATE candidate( symbol_id, new_id, cache_match, lib_match,
441                                                          eachSymbol->GetUnit(),
442                                                          eachSymbol->GetConvert() );
443 
444             candidate_map[symbol_id] = candidate;
445         }
446     }
447 
448     // Now, dump the map into aCandidates
449     for( const candidate_map_t::value_type& each_pair : candidate_map )
450     {
451         aCandidates.push_back( new RESCUE_SYMBOL_LIB_TABLE_CANDIDATE( each_pair.second ) );
452     }
453 }
454 
455 
GetActionDescription() const456 wxString RESCUE_SYMBOL_LIB_TABLE_CANDIDATE::GetActionDescription() const
457 {
458     wxString action;
459 
460     if( !m_cache_candidate && !m_lib_candidate )
461     {
462         action.Printf( _( "Cannot rescue symbol %s which is not available in any library or "
463                           "the cache." ),
464                        m_requested_id.GetLibItemName().wx_str() );
465     }
466     else if( m_cache_candidate && !m_lib_candidate )
467     {
468         action.Printf( _( "Rescue symbol %s found only in cache library to %s." ),
469                        m_requested_id.Format().wx_str(),
470                        m_new_id.Format().wx_str() );
471     }
472     else
473     {
474         action.Printf( _( "Rescue modified symbol %s to %s" ),
475                        m_requested_id.Format().wx_str(),
476                        m_new_id.Format().wx_str() );
477     }
478 
479     return action;
480 }
481 
482 
PerformAction(RESCUER * aRescuer)483 bool RESCUE_SYMBOL_LIB_TABLE_CANDIDATE::PerformAction( RESCUER* aRescuer )
484 {
485     LIB_SYMBOL* tmp = ( m_cache_candidate ) ? m_cache_candidate : m_lib_candidate;
486 
487     wxCHECK_MSG( tmp, false, "Both cache and library symbols undefined." );
488 
489     std::unique_ptr<LIB_SYMBOL> new_symbol = tmp->Flatten();
490     new_symbol->SetLibId( m_new_id );
491     new_symbol->SetName( m_new_id.GetLibItemName() );
492     aRescuer->AddSymbol( new_symbol.get() );
493 
494     for( SCH_SYMBOL* eachSymbol : *aRescuer->GetSymbols() )
495     {
496         if( eachSymbol->GetLibId() != m_requested_id )
497             continue;
498 
499         eachSymbol->SetLibId( m_new_id );
500         eachSymbol->ClearFlags();
501         aRescuer->LogRescue( eachSymbol, m_requested_id.Format(), m_new_id.Format() );
502     }
503 
504     return true;
505 }
506 
507 
RESCUER(PROJECT & aProject,SCHEMATIC * aSchematic,SCH_SHEET_PATH * aCurrentSheet,EDA_DRAW_PANEL_GAL::GAL_TYPE aGalBackEndType)508 RESCUER::RESCUER( PROJECT& aProject, SCHEMATIC* aSchematic, SCH_SHEET_PATH* aCurrentSheet,
509                   EDA_DRAW_PANEL_GAL::GAL_TYPE aGalBackEndType )
510 {
511     m_schematic = aSchematic ? aSchematic : aCurrentSheet->LastScreen()->Schematic();
512 
513     wxASSERT( m_schematic );
514 
515     if( m_schematic )
516         getSymbols( m_schematic, m_symbols );
517 
518     m_prj = &aProject;
519     m_currentSheet = aCurrentSheet;
520     m_galBackEndType = aGalBackEndType;
521 }
522 
523 
LogRescue(SCH_SYMBOL * aSymbol,const wxString & aOldName,const wxString & aNewName)524 void RESCUER::LogRescue( SCH_SYMBOL* aSymbol, const wxString &aOldName,
525                          const wxString &aNewName )
526 {
527     RESCUE_LOG logitem;
528     logitem.symbol = aSymbol;
529     logitem.old_name = aOldName;
530     logitem.new_name = aNewName;
531     m_rescue_log.push_back( logitem );
532 }
533 
534 
DoRescues()535 bool RESCUER::DoRescues()
536 {
537     for( RESCUE_CANDIDATE* each_candidate : m_chosen_candidates )
538     {
539         if( ! each_candidate->PerformAction( this ) )
540             return false;
541     }
542 
543     return true;
544 }
545 
546 
UndoRescues()547 void RESCUER::UndoRescues()
548 {
549     for( RESCUE_LOG& each_logitem : m_rescue_log )
550     {
551         LIB_ID libId;
552 
553         libId.SetLibItemName( each_logitem.old_name );
554         each_logitem.symbol->SetLibId( libId );
555         each_logitem.symbol->ClearFlags();
556     }
557 }
558 
559 
RescueProject(wxWindow * aParent,RESCUER & aRescuer,bool aRunningOnDemand)560 bool RESCUER::RescueProject( wxWindow* aParent, RESCUER& aRescuer, bool aRunningOnDemand )
561 {
562     aRescuer.FindCandidates();
563 
564     if( !aRescuer.GetCandidateCount() )
565     {
566         if( aRunningOnDemand )
567         {
568             wxMessageDialog dlg( aParent, _( "This project has nothing to rescue." ),
569                                  _( "Project Rescue Helper" ) );
570             dlg.ShowModal();
571         }
572 
573         return true;
574     }
575 
576     aRescuer.RemoveDuplicates();
577     aRescuer.InvokeDialog( aParent, !aRunningOnDemand );
578 
579     // If no symbols were rescued, let the user know what's going on. He might
580     // have clicked cancel by mistake, and should have some indication of that.
581     if( !aRescuer.GetChosenCandidateCount() )
582     {
583         wxMessageDialog dlg( aParent, _( "No symbols were rescued." ),
584                              _( "Project Rescue Helper" ) );
585         dlg.ShowModal();
586 
587         // Set the modified flag even on Cancel. Many users seem to instinctively want to Save at
588         // this point, due to the reloading of the symbols, so we'll make the save button active.
589         return true;
590     }
591 
592     aRescuer.OpenRescueLibrary();
593 
594     if( !aRescuer.DoRescues() )
595     {
596         aRescuer.UndoRescues();
597         return false;
598     }
599 
600     aRescuer.WriteRescueLibrary( aParent );
601 
602     return true;
603 }
604 
605 
RemoveDuplicates()606 void RESCUER::RemoveDuplicates()
607 {
608     std::vector<wxString> names_seen;
609 
610     for( boost::ptr_vector<RESCUE_CANDIDATE>::iterator it = m_all_candidates.begin();
611          it != m_all_candidates.end(); )
612     {
613         bool seen_already = false;
614 
615         for( wxString& name_seen : names_seen )
616         {
617             if( name_seen == it->GetRequestedName() )
618             {
619                 seen_already = true;
620                 break;
621             }
622         }
623 
624         if( seen_already )
625         {
626             it = m_all_candidates.erase( it );
627         }
628         else
629         {
630             names_seen.push_back( it->GetRequestedName() );
631             ++it;
632         }
633     }
634 }
635 
636 
FindCandidates()637 void LEGACY_RESCUER::FindCandidates()
638 {
639     RESCUE_CASE_CANDIDATE::FindRescues( *this, m_all_candidates );
640     RESCUE_CACHE_CANDIDATE::FindRescues( *this, m_all_candidates );
641 }
642 
643 
InvokeDialog(wxWindow * aParent,bool aAskShowAgain)644 void LEGACY_RESCUER::InvokeDialog( wxWindow* aParent, bool aAskShowAgain )
645 {
646     InvokeDialogRescueEach( aParent, static_cast< RESCUER& >( *this ), m_currentSheet,
647                             m_galBackEndType, aAskShowAgain );
648 }
649 
650 
OpenRescueLibrary()651 void LEGACY_RESCUER::OpenRescueLibrary()
652 {
653     wxFileName fn = GetRescueLibraryFileName( m_schematic );
654 
655     std::unique_ptr<SYMBOL_LIB> rescue_lib = std::make_unique<SYMBOL_LIB>( SCH_LIB_TYPE::LT_EESCHEMA,
656                                                                        fn.GetFullPath() );
657 
658     m_rescue_lib = std::move( rescue_lib );
659     m_rescue_lib->EnableBuffering();
660 
661     // If a rescue library already exists copy the contents of that library so we do not
662     // lose an previous rescues.
663     SYMBOL_LIB* rescueLib = m_prj->SchLibs()->FindLibrary( fn.GetName() );
664 
665     if( rescueLib )
666     {
667         // For items in the rescue library, aliases are the root symbol.
668         std::vector< LIB_SYMBOL* > symbols;
669 
670         rescueLib->GetSymbols( symbols );
671 
672         for( auto symbol : symbols )
673         {
674             // The LIB_SYMBOL copy constructor flattens derived symbols (formerly known as aliases).
675             m_rescue_lib->AddSymbol( new LIB_SYMBOL( *symbol, m_rescue_lib.get() ) );
676         }
677     }
678 }
679 
680 
WriteRescueLibrary(wxWindow * aParent)681 bool LEGACY_RESCUER::WriteRescueLibrary( wxWindow *aParent )
682 {
683     try
684     {
685         m_rescue_lib->Save( false );
686     }
687     catch( ... /* IO_ERROR ioe */ )
688     {
689         wxString msg;
690 
691         msg.Printf( _( "Failed to create symbol library file '%s'." ),
692                     m_rescue_lib->GetFullFileName() );
693         DisplayError( aParent, msg );
694         return false;
695     }
696 
697     wxArrayString libNames;
698     wxString libPaths;
699 
700     wxString libName = m_rescue_lib->GetName();
701     SYMBOL_LIBS *libs = dynamic_cast<SYMBOL_LIBS*>( m_prj->GetElem( PROJECT::ELEM_SCH_SYMBOL_LIBS ) );
702 
703     if( !libs )
704     {
705         libs = new SYMBOL_LIBS();
706         m_prj->SetElem( PROJECT::ELEM_SCH_SYMBOL_LIBS, libs );
707     }
708 
709     try
710     {
711         SYMBOL_LIBS::LibNamesAndPaths( m_prj, false, &libPaths, &libNames );
712 
713         // Make sure the library is not already in the list
714         while( libNames.Index( libName ) != wxNOT_FOUND )
715             libNames.Remove( libName );
716 
717         // Add the library to the top of the list and save.
718         libNames.Insert( libName, 0 );
719         SYMBOL_LIBS::LibNamesAndPaths( m_prj, true, &libPaths, &libNames );
720     }
721     catch( const IO_ERROR& )
722     {
723         // Could not get or save the current libraries.
724         return false;
725     }
726 
727     // Save the old libraries in case there is a problem after clear(). We'll
728     // put them back in.
729     boost::ptr_vector<SYMBOL_LIB> libsSave;
730     libsSave.transfer( libsSave.end(), libs->begin(), libs->end(), *libs );
731 
732     m_prj->SetElem( PROJECT::ELEM_SCH_SYMBOL_LIBS, nullptr );
733 
734     libs = new SYMBOL_LIBS();
735 
736     try
737     {
738         libs->LoadAllLibraries( m_prj );
739     }
740     catch( const PARSE_ERROR& )
741     {
742         // Some libraries were not found. There's no point in showing the error,
743         // because it was already shown. Just don't do anything.
744     }
745     catch( const IO_ERROR& )
746     {
747         // Restore the old list
748         libs->clear();
749         libs->transfer( libs->end(), libsSave.begin(), libsSave.end(), libsSave );
750         return false;
751     }
752 
753     m_prj->SetElem( PROJECT::ELEM_SCH_SYMBOL_LIBS, libs );
754 
755     // Update the schematic symbol library links since the library list has changed.
756     SCH_SCREENS schematic( m_schematic->Root() );
757     schematic.UpdateSymbolLinks();
758     return true;
759 }
760 
761 
AddSymbol(LIB_SYMBOL * aNewSymbol)762 void LEGACY_RESCUER::AddSymbol( LIB_SYMBOL* aNewSymbol )
763 {
764     wxCHECK_RET( aNewSymbol, "Invalid LIB_SYMBOL pointer." );
765 
766     aNewSymbol->SetLib( m_rescue_lib.get() );
767     m_rescue_lib->AddSymbol( aNewSymbol );
768 }
769 
770 
SYMBOL_LIB_TABLE_RESCUER(PROJECT & aProject,SCHEMATIC * aSchematic,SCH_SHEET_PATH * aCurrentSheet,EDA_DRAW_PANEL_GAL::GAL_TYPE aGalBackEndType)771 SYMBOL_LIB_TABLE_RESCUER::SYMBOL_LIB_TABLE_RESCUER( PROJECT& aProject, SCHEMATIC* aSchematic,
772                                                     SCH_SHEET_PATH* aCurrentSheet,
773                                                     EDA_DRAW_PANEL_GAL::GAL_TYPE aGalBackEndType ) :
774     RESCUER( aProject, aSchematic, aCurrentSheet, aGalBackEndType )
775 {
776     m_properties = std::make_unique<PROPERTIES>();
777 }
778 
779 
FindCandidates()780 void SYMBOL_LIB_TABLE_RESCUER::FindCandidates()
781 {
782     RESCUE_SYMBOL_LIB_TABLE_CANDIDATE::FindRescues( *this, m_all_candidates );
783 }
784 
785 
InvokeDialog(wxWindow * aParent,bool aAskShowAgain)786 void SYMBOL_LIB_TABLE_RESCUER::InvokeDialog( wxWindow* aParent, bool aAskShowAgain )
787 {
788     InvokeDialogRescueEach( aParent, static_cast< RESCUER& >( *this ), m_currentSheet,
789                             m_galBackEndType, aAskShowAgain );
790 }
791 
792 
OpenRescueLibrary()793 void SYMBOL_LIB_TABLE_RESCUER::OpenRescueLibrary()
794 {
795     m_pi.set( SCH_IO_MGR::FindPlugin( SCH_IO_MGR::SCH_LEGACY ) );
796     (*m_properties)[ SCH_LEGACY_PLUGIN::PropBuffering ] = "";
797 }
798 
799 
WriteRescueLibrary(wxWindow * aParent)800 bool SYMBOL_LIB_TABLE_RESCUER::WriteRescueLibrary( wxWindow *aParent )
801 {
802     wxString msg;
803     wxFileName fn = GetRescueLibraryFileName( m_schematic );
804 
805     // If the rescue library already exists in the symbol library table no need save it to add
806     // it to the table.
807     if( !m_prj->SchSymbolLibTable()->HasLibrary( fn.GetName() ) )
808     {
809         try
810         {
811             m_pi->SaveLibrary( fn.GetFullPath() );
812         }
813         catch( const IO_ERROR& ioe )
814         {
815             msg.Printf( _( "Failed to save rescue library %s." ), fn.GetFullPath() );
816             DisplayErrorMessage( aParent, msg, ioe.What() );
817             return false;
818         }
819 
820         wxString uri = "${KIPRJMOD}/" + fn.GetFullName();
821         wxString libNickname = fn.GetName();
822 
823         // Spaces in the file name will break the symbol name because they are not
824         // quoted in the symbol library file format.
825         libNickname.Replace( " ", "-" );
826 
827         SYMBOL_LIB_TABLE_ROW* row = new SYMBOL_LIB_TABLE_ROW( libNickname, uri,
828                                                               wxString( "Legacy" ) );
829         m_prj->SchSymbolLibTable()->InsertRow( row );
830 
831         fn = wxFileName( m_prj->GetProjectPath(), SYMBOL_LIB_TABLE::GetSymbolLibTableFileName() );
832 
833         try
834         {
835             m_prj->SchSymbolLibTable()->Save( fn.GetFullPath() );
836         }
837         catch( const IO_ERROR& ioe )
838         {
839             msg.Printf( _( "Error occurred saving project specific symbol library table." ) );
840             DisplayErrorMessage( aParent, msg, ioe.What() );
841             return false;
842         }
843     }
844 
845     // Relaod the symbol library table.
846     m_prj->SetElem( PROJECT::ELEM_SYMBOL_LIB_TABLE, nullptr );
847 
848     // This can only happen if the symbol library table file was corrupted on write.
849     if( !m_prj->SchSymbolLibTable() )
850         return false;
851 
852     // Update the schematic symbol library links since the library list has changed.
853     SCH_SCREENS schematic( m_schematic->Root() );
854     schematic.UpdateSymbolLinks();
855     return true;
856 }
857 
858 
AddSymbol(LIB_SYMBOL * aNewSymbol)859 void SYMBOL_LIB_TABLE_RESCUER::AddSymbol( LIB_SYMBOL* aNewSymbol )
860 {
861     wxCHECK_RET( aNewSymbol, "Invalid LIB_SYMBOL pointer." );
862 
863     wxFileName fn = GetRescueLibraryFileName( m_schematic );
864 
865     try
866     {
867         if( !m_prj->SchSymbolLibTable()->HasLibrary( fn.GetName() ) )
868             m_pi->SaveSymbol( fn.GetFullPath(), new LIB_SYMBOL( *aNewSymbol ), m_properties.get() );
869         else
870             m_prj->SchSymbolLibTable()->SaveSymbol( fn.GetName(), new LIB_SYMBOL( *aNewSymbol ) );
871     }
872     catch( ... /* IO_ERROR */ )
873     {
874     }
875 }
876