1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 1992-2021 KiCad Developers, see AUTHORS.txt for contributors.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, you may find one here:
18  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19  * or you may search the http://www.gnu.org website for the version 2 license,
20  * or you may write to the Free Software Foundation, Inc.,
21  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
22  */
23 
24 #include <wx/ffile.h>
25 #include <pgm_base.h>
26 #include <kiface_base.h>
27 #include <confirm.h>
28 #include <string_utils.h>
29 #include <macros.h>
30 #include <pcb_edit_frame.h>
31 #include <eda_list_dialog.h>
32 #include <filter_reader.h>
33 #include <fp_lib_table.h>
34 #include <validators.h>
35 #include <dialogs/dialog_text_entry.h>
36 #include <tool/tool_manager.h>
37 #include <tools/pcb_actions.h>
38 #include <tools/board_editor_control.h>
39 #include <board.h>
40 #include <footprint.h>
41 #include <board_commit.h>
42 #include <footprint_edit_frame.h>
43 #include <wildcards_and_files_ext.h>
44 #include <plugins/kicad/pcb_plugin.h>
45 #include <plugins/legacy/legacy_plugin.h>
46 #include <env_paths.h>
47 #include <paths.h>
48 #include <settings/settings_manager.h>
49 #include <footprint_editor_settings.h>
50 #include "footprint_viewer_frame.h"
51 #include <wx/choicdlg.h>
52 #include <wx/filedlg.h>
53 
54 
55 // unique, "file local" translations:
56 
57 
58 static const wxString INFO_LEGACY_LIB_WARN_EDIT(
59         _(  "Writing/modifying legacy libraries (.mod files) is not allowed\n"\
60             "Please save the current library to the new .pretty format\n"\
61             "and update your footprint lib table\n"\
62             "to save your footprint (a .kicad_mod file) in the .pretty library folder" ) );
63 
64 static const wxString INFO_LEGACY_LIB_WARN_DELETE(
65         _(  "Modifying legacy libraries (.mod files) is not allowed\n"\
66             "Please save the current library under the new .pretty format\n"\
67             "and update your footprint lib table\n"\
68             "before deleting a footprint" ) );
69 
70 
71 /**
72  * Prompt the user for a footprint file to open.
73  * @param aParent - parent window for the dialog
74  * @param aLastPath - last opened path
75  */
getFootprintFilenameFromUser(wxWindow * aParent,const wxString & aLastPath)76 static wxFileName getFootprintFilenameFromUser( wxWindow* aParent, const wxString& aLastPath )
77 {
78     static int lastFilterIndex = 0;     // To store the last choice during a session.
79     wxString wildCard;
80 
81     wildCard << KiCadFootprintLibFileWildcard() << wxChar( '|' )
82              << ModLegacyExportFileWildcard() << wxChar( '|' )
83              << GedaPcbFootprintLibFileWildcard() << wxChar( '|' )
84              << AllFilesWildcard();
85 
86     wxFileDialog dlg( aParent, _( "Import Footprint" ), aLastPath, wxEmptyString, wildCard,
87             wxFD_OPEN | wxFD_FILE_MUST_EXIST );
88 
89     dlg.SetFilterIndex( lastFilterIndex );
90 
91     if( dlg.ShowModal() == wxID_CANCEL )
92         return wxFileName();
93 
94     lastFilterIndex = dlg.GetFilterIndex();
95 
96     return wxFileName( dlg.GetPath() );
97 }
98 
99 
100 /**
101  * Read a file to detect the type.
102  * @param aFile - open file to be read. File pointer will be closed.
103  * @param aFileName - file name to be read
104  * @param aName - wxString to receive the footprint name iff type is LEGACY
105  */
detect_file_type(FILE * aFile,const wxFileName & aFileName,wxString * aName)106 static IO_MGR::PCB_FILE_T detect_file_type( FILE* aFile, const wxFileName& aFileName,
107                                             wxString* aName )
108 {
109     FILE_LINE_READER freader( aFile, aFileName.GetFullPath() );
110     WHITESPACE_FILTER_READER reader( freader );
111     IO_MGR::PCB_FILE_T file_type;
112 
113     wxASSERT( aName );
114 
115     reader.ReadLine();
116     char* line = reader.Line();
117 
118     if( !line )
119     {
120         return IO_MGR::FILE_TYPE_NONE;
121     }
122 
123     // first .kicad_mod file versions starts by "(module"
124     // recent .kicad_mod file versions starts by "(footprint"
125     if( strncasecmp( line, "(module", strlen( "(module" ) ) == 0
126         || strncasecmp( line, "(footprint", strlen( "(footprint" ) ) == 0 )
127     {
128         file_type = IO_MGR::KICAD_SEXP;
129         *aName = aFileName.GetName();
130     }
131     else if( !strncasecmp( line, FOOTPRINT_LIBRARY_HEADER, FOOTPRINT_LIBRARY_HEADER_CNT ) )
132     {
133         file_type = IO_MGR::LEGACY;
134 
135         while( reader.ReadLine() )
136         {
137             if( !strncasecmp( line, "$MODULE", strlen( "$MODULE" ) ) )
138             {
139                 *aName = FROM_UTF8( StrPurge( line + strlen( "$MODULE" ) ) );
140                 break;
141             }
142         }
143     }
144     else if( !strncasecmp( line, "Element", strlen( "Element" ) ) )
145     {
146         file_type = IO_MGR::GEDA_PCB;
147         *aName = aFileName.GetName();
148     }
149     else
150     {
151         file_type = IO_MGR::FILE_TYPE_NONE;
152     }
153 
154     return file_type;
155 }
156 
157 
158 /**
159  * Parse a footprint using a PLUGIN.
160  * @param aFileName - file name to parse
161  * @param aFileType - type of the file
162  * @param aName - name of the footprint
163  */
parse_footprint_with_plugin(const wxFileName & aFileName,IO_MGR::PCB_FILE_T aFileType,const wxString & aName)164 static FOOTPRINT* parse_footprint_with_plugin( const wxFileName& aFileName,
165                                                IO_MGR::PCB_FILE_T aFileType,
166                                                const wxString& aName )
167 {
168     wxString path;
169 
170     switch( aFileType )
171     {
172     case IO_MGR::GEDA_PCB: path = aFileName.GetPath();             break;
173     case IO_MGR::LEGACY:   path = aFileName.GetFullPath();         break;
174     default: wxFAIL_MSG( wxT( "unexpected IO_MGR::PCB_FILE_T" ) ); break;
175     }
176 
177     PLUGIN::RELEASER pi( IO_MGR::PluginFind( aFileType ) );
178 
179     return pi->FootprintLoad( path, aName );
180 }
181 
182 
183 /**
184  * Parse a KICAD footprint.
185  * @param aFileName - file name to parse
186  */
parse_footprint_kicad(const wxFileName & aFileName)187 static FOOTPRINT* parse_footprint_kicad( const wxFileName& aFileName )
188 {
189     wxString   fcontents;
190     PCB_PLUGIN pcb_io;
191     wxFFile    f( aFileName.GetFullPath() );
192 
193     if( !f.IsOpened() )
194         return nullptr;
195 
196     f.ReadAll( &fcontents );
197 
198     return dynamic_cast<FOOTPRINT*>( pcb_io.Parse( fcontents ) );
199 }
200 
201 
202 /**
203  * Try to load a footprint, returning nullptr if the file couldn't be accessed.
204  * @param aFileName - file name to load
205  * @param aFileType - type of the file to load
206  * @param aName - footprint name
207  */
try_load_footprint(const wxFileName & aFileName,IO_MGR::PCB_FILE_T aFileType,const wxString & aName)208 FOOTPRINT* try_load_footprint( const wxFileName& aFileName, IO_MGR::PCB_FILE_T aFileType,
209                                const wxString& aName )
210 {
211     FOOTPRINT* footprint;
212 
213     switch( aFileType )
214     {
215     case IO_MGR::GEDA_PCB:
216     case IO_MGR::LEGACY:
217         footprint = parse_footprint_with_plugin( aFileName, aFileType, aName );
218         break;
219 
220     case IO_MGR::KICAD_SEXP:
221         footprint = parse_footprint_kicad( aFileName );
222         break;
223 
224     default:
225         wxFAIL_MSG( wxT( "unexpected IO_MGR::PCB_FILE_T" ) );
226         footprint = nullptr;
227     }
228 
229     return footprint;
230 }
231 
232 
ImportFootprint(const wxString & aName)233 FOOTPRINT* FOOTPRINT_EDIT_FRAME::ImportFootprint( const wxString& aName )
234 {
235     wxString lastOpenedPathForLoading = m_mruPath;
236     FOOTPRINT_EDITOR_SETTINGS* cfg    = GetSettings();
237 
238     if( !cfg->m_LastImportExportPath.empty() )
239         lastOpenedPathForLoading = cfg->m_LastImportExportPath;
240 
241     wxFileName fn;
242 
243     if( aName != wxT("") )
244         fn = aName;
245     else
246         fn = getFootprintFilenameFromUser( this, lastOpenedPathForLoading );
247 
248     if( !fn.IsOk() )
249         return nullptr;
250 
251     FILE* fp = wxFopen( fn.GetFullPath(), wxT( "rt" ) );
252 
253     if( !fp )
254     {
255         wxString msg = wxString::Format( _( "File '%s' not found." ), fn.GetFullPath() );
256         DisplayError( this, msg );
257         return nullptr;
258     }
259 
260     cfg->m_LastImportExportPath = lastOpenedPathForLoading;
261 
262     wxString footprintName;
263     IO_MGR::PCB_FILE_T fileType = detect_file_type( fp, fn.GetFullPath(), &footprintName );
264 
265     if( fileType == IO_MGR::FILE_TYPE_NONE )
266     {
267         DisplayError( this, _( "Not a footprint file." ) );
268         return nullptr;
269     }
270 
271     FOOTPRINT* footprint = nullptr;
272 
273     try
274     {
275         footprint = try_load_footprint( fn, fileType, footprintName );
276 
277         if( !footprint )
278         {
279             wxString msg = wxString::Format( _( "Unable to load footprint '%s' from '%s'" ),
280                                              footprintName,
281                                              fn.GetFullPath() );
282             DisplayError( this, msg );
283             return nullptr;
284         }
285     }
286     catch( const IO_ERROR& ioe )
287     {
288         DisplayError( this, ioe.What() );
289 
290         // if the footprint is not loaded, exit.
291         // However, even if an error happens, it can be loaded, because in KICAD and GPCB format,
292         // a fp library is a set of separate files, and the error(s) are not necessary when
293         // reading the selected file
294 
295         if( !footprint )
296             return nullptr;
297     }
298 
299     footprint->SetFPID( LIB_ID( wxEmptyString, footprintName ) );
300 
301     // Insert footprint in list
302     AddFootprintToBoard( footprint );
303 
304     // Display info :
305     SetMsgPanel( footprint );
306     PlaceFootprint( footprint );
307 
308     footprint->SetPosition( wxPoint( 0, 0 ) );
309 
310     GetBoard()->BuildListOfNets();
311     UpdateView();
312 
313     return footprint;
314 }
315 
316 
ExportFootprint(FOOTPRINT * aFootprint)317 void FOOTPRINT_EDIT_FRAME::ExportFootprint( FOOTPRINT* aFootprint )
318 {
319     wxFileName fn;
320     FOOTPRINT_EDITOR_SETTINGS* cfg = GetSettings();
321 
322     if( !aFootprint )
323         return;
324 
325     fn.SetName( aFootprint->GetFPID().GetLibItemName() );
326 
327     wxString    wildcard = KiCadFootprintLibFileWildcard();
328 
329     fn.SetExt( KiCadFootprintFileExtension );
330 
331     if( !cfg->m_LastImportExportPath.empty() )
332         fn.SetPath( cfg->m_LastImportExportPath );
333     else
334         fn.SetPath( m_mruPath );
335 
336     wxFileDialog dlg( this, _( "Export Footprint" ), fn.GetPath(), fn.GetFullName(),
337                       wildcard, wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
338 
339     if( dlg.ShowModal() == wxID_CANCEL )
340         return;
341 
342     fn = dlg.GetPath();
343     cfg->m_LastImportExportPath = fn.GetPath();
344 
345     try
346     {
347         // Export as *.kicad_pcb format, using a strategy which is specifically chosen
348         // as an example on how it could also be used to send it to the system clipboard.
349 
350         PCB_PLUGIN  pcb_io(CTL_FOR_LIBRARY );
351 
352         /*  This footprint should *already* be "normalized" in a way such that
353             orientation is zero, etc., since it came from the Footprint Editor.
354 
355             aFootprint->SetParent( 0 );
356             aFootprint->SetOrientation( 0 );
357         */
358 
359         pcb_io.Format( aFootprint );
360 
361         FILE* fp = wxFopen( dlg.GetPath(), wxT( "wt" ) );
362 
363         if( fp == nullptr )
364         {
365             wxMessageBox( wxString::Format( _( "Insufficient permissions to write file '%s'." ),
366                                             dlg.GetPath() ) );
367             return;
368         }
369 
370         fprintf( fp, "%s", pcb_io.GetStringOutput( false ).c_str() );
371         fclose( fp );
372     }
373     catch( const IO_ERROR& ioe )
374     {
375         DisplayError( this, ioe.What() );
376         return;
377     }
378 
379     wxString msg = wxString::Format( _( "Footprint exported to file '%s'." ), dlg.GetPath() );
380     DisplayInfoMessage( this, msg );
381 }
382 
383 
CreateNewProjectLibrary(const wxString & aLibName,const wxString & aProposedName)384 wxString PCB_BASE_EDIT_FRAME::CreateNewProjectLibrary( const wxString& aLibName,
385                                                        const wxString& aProposedName )
386 {
387     return createNewLibrary( aLibName, aProposedName, Prj().PcbFootprintLibs() );
388 }
389 
390 
CreateNewLibrary(const wxString & aLibName,const wxString & aProposedName)391 wxString PCB_BASE_EDIT_FRAME::CreateNewLibrary( const wxString& aLibName,
392                                                 const wxString& aProposedName )
393 {
394     FP_LIB_TABLE* table  = selectLibTable();
395 
396     return createNewLibrary( aLibName, aProposedName, table );
397 }
398 
399 
createNewLibrary(const wxString & aLibName,const wxString & aProposedName,FP_LIB_TABLE * aTable)400 wxString PCB_BASE_EDIT_FRAME::createNewLibrary( const wxString& aLibName,
401                                                 const wxString& aProposedName,
402                                                 FP_LIB_TABLE* aTable )
403 {
404     // Kicad cannot write legacy format libraries, only .pretty new format because the legacy
405     // format cannot handle current features.
406     // The footprint library is actually a directory.
407 
408     if( aTable == nullptr )
409         return wxEmptyString;
410 
411     wxString   initialPath = aProposedName.IsEmpty() ? Prj().GetProjectPath() : aProposedName;
412     wxFileName fn;
413     bool       doAdd = false;
414     bool       isGlobal = ( aTable == &GFootprintTable );
415 
416     if( aLibName.IsEmpty() )
417     {
418         fn = initialPath;
419 
420         if( !LibraryFileBrowser( false, fn, KiCadFootprintLibPathWildcard(),
421                                  KiCadFootprintLibPathExtension, false, isGlobal,
422                                  PATHS::GetDefaultUserFootprintsPath() ) )
423         {
424             return wxEmptyString;
425         }
426 
427         doAdd = true;
428     }
429     else
430     {
431         fn = aLibName;
432 
433         if( !fn.IsAbsolute() )
434         {
435             fn.SetName( aLibName );
436             fn.MakeAbsolute( initialPath );
437         }
438 
439         // Enforce the .pretty extension:
440         fn.SetExt( KiCadFootprintLibPathExtension );
441     }
442 
443     // We can save fp libs only using IO_MGR::KICAD_SEXP format (.pretty libraries)
444     IO_MGR::PCB_FILE_T piType  = IO_MGR::KICAD_SEXP;
445     wxString           libPath = fn.GetFullPath();
446 
447     try
448     {
449         PLUGIN::RELEASER pi( IO_MGR::PluginFind( piType ) );
450 
451         bool writable = false;
452         bool exists   = false;
453 
454         try
455         {
456             writable = pi->IsFootprintLibWritable( libPath );
457             exists   = true;    // no exception was thrown, lib must exist.
458         }
459         catch( const IO_ERROR& )
460         {
461             // best efforts....
462         }
463 
464         if( exists )
465         {
466             if( !writable )
467             {
468                 wxString msg = wxString::Format( _( "Library %s is read only." ), libPath );
469                 ShowInfoBarError( msg );
470                 return wxEmptyString;
471             }
472             else
473             {
474                 wxString msg = wxString::Format( _( "Library %s already exists." ), libPath );
475                 KIDIALOG dlg( this, msg, _( "Confirmation" ), wxOK | wxCANCEL | wxICON_WARNING );
476                 dlg.SetOKLabel( _( "Overwrite" ) );
477                 dlg.DoNotShowCheckbox( __FILE__, __LINE__ );
478 
479                 if( dlg.ShowModal() == wxID_CANCEL )
480                     return wxEmptyString;
481 
482                 pi->FootprintLibDelete( libPath );
483             }
484         }
485 
486         pi->FootprintLibCreate( libPath );
487     }
488     catch( const IO_ERROR& ioe )
489     {
490         DisplayError( this, ioe.What() );
491         return wxEmptyString;
492     }
493 
494     if( doAdd )
495         AddLibrary( libPath, aTable );
496 
497     return libPath;
498 }
499 
500 
selectLibTable(bool aOptional)501 FP_LIB_TABLE* PCB_BASE_EDIT_FRAME::selectLibTable( bool aOptional )
502 {
503     // If no project is loaded, always work with the global table
504     if( Prj().IsNullProject() )
505     {
506         FP_LIB_TABLE* ret = &GFootprintTable;
507 
508         if( aOptional )
509         {
510             wxMessageDialog dlg( this, _( "Add the library to the global library table?" ),
511                                  _( "Add To Global Library Table" ), wxYES_NO );
512 
513             if( dlg.ShowModal() != wxID_OK )
514                 ret = nullptr;
515         }
516 
517         return ret;
518     }
519 
520     wxArrayString libTableNames;
521     libTableNames.Add( _( "Global" ) );
522     libTableNames.Add( _( "Project" ) );
523 
524     wxSingleChoiceDialog dlg( this, _( "Choose the Library Table to add the library to:" ),
525                               _( "Add To Library Table" ), libTableNames );
526 
527     if( aOptional )
528     {
529         dlg.FindWindow( wxID_CANCEL )->SetLabel( _( "Skip" ) );
530         dlg.FindWindow( wxID_OK )->SetLabel( _( "Add" ) );
531     }
532 
533     if( dlg.ShowModal() != wxID_OK )
534         return nullptr;
535 
536     switch( dlg.GetSelection() )
537     {
538     case 0: return &GFootprintTable;
539     case 1: return Prj().PcbFootprintLibs();
540     default: return nullptr;
541     }
542 }
543 
544 
AddLibrary(const wxString & aFilename,FP_LIB_TABLE * aTable)545 bool PCB_BASE_EDIT_FRAME::AddLibrary( const wxString& aFilename, FP_LIB_TABLE* aTable )
546 {
547     if( aTable == nullptr )
548         aTable = selectLibTable();
549 
550     if( aTable == nullptr )
551         return wxEmptyString;
552 
553     bool isGlobal = ( aTable == &GFootprintTable );
554 
555     wxFileName fn( aFilename );
556 
557     if( aFilename.IsEmpty() )
558     {
559         if( !LibraryFileBrowser( true, fn, KiCadFootprintLibPathWildcard(),
560                                  KiCadFootprintLibPathExtension, true, isGlobal,
561                                  PATHS::GetDefaultUserFootprintsPath() ) )
562         {
563             return false;
564         }
565     }
566 
567     wxString libPath = fn.GetFullPath();
568     wxString libName = fn.GetName();
569 
570     if( libName.IsEmpty() )
571         return false;
572 
573     wxString type = IO_MGR::ShowType( IO_MGR::GuessPluginTypeFromLibPath( libPath ) );
574 
575     // try to use path normalized to an environmental variable or project path
576     wxString normalizedPath = NormalizePath( libPath, &Pgm().GetLocalEnvVariables(), &Prj() );
577 
578     try
579     {
580         FP_LIB_TABLE_ROW* row = new FP_LIB_TABLE_ROW( libName, normalizedPath, type, wxEmptyString );
581         aTable->InsertRow( row );
582 
583         if( isGlobal )
584             GFootprintTable.Save( FP_LIB_TABLE::GetGlobalTableFileName() );
585         else
586             Prj().PcbFootprintLibs()->Save( Prj().FootprintLibTblName() );
587     }
588     catch( const IO_ERROR& ioe )
589     {
590         DisplayError( this, ioe.What() );
591         return false;
592     }
593 
594     auto editor = (FOOTPRINT_EDIT_FRAME*) Kiway().Player( FRAME_FOOTPRINT_EDITOR, false );
595 
596     if( editor )
597     {
598         LIB_ID libID( libName, wxEmptyString );
599         editor->SyncLibraryTree( true );
600         editor->FocusOnLibID( libID );
601     }
602 
603     auto viewer = (FOOTPRINT_VIEWER_FRAME*) Kiway().Player( FRAME_FOOTPRINT_VIEWER, false );
604 
605     if( viewer )
606         viewer->ReCreateLibraryList();
607 
608     return true;
609 }
610 
611 
DeleteFootprintFromLibrary(const LIB_ID & aFPID,bool aConfirm)612 bool FOOTPRINT_EDIT_FRAME::DeleteFootprintFromLibrary( const LIB_ID& aFPID, bool aConfirm )
613 {
614     if( !aFPID.IsValid() )
615         return false;
616 
617     wxString nickname = aFPID.GetLibNickname();
618     wxString fpname = aFPID.GetLibItemName();
619 
620     // Legacy libraries are readable, but modifying legacy format is not allowed
621     // So prompt the user if he try to delete a footprint from a legacy lib
622     wxString libfullname = Prj().PcbFootprintLibs()->FindRow( nickname )->GetFullURI();
623 
624     if( IO_MGR::GuessPluginTypeFromLibPath( libfullname ) == IO_MGR::LEGACY )
625     {
626         DisplayInfoMessage( this, INFO_LEGACY_LIB_WARN_DELETE );
627         return false;
628     }
629 
630     if( !Prj().PcbFootprintLibs()->IsFootprintLibWritable( nickname ) )
631     {
632         wxString msg = wxString::Format( _( "Library '%s' is read only." ), nickname );
633         ShowInfoBarError( msg );
634         return false;
635     }
636 
637     // Confirmation
638     wxString msg = wxString::Format( _( "Delete footprint '%s' from library '%s'?" ),
639                                      fpname.GetData(),
640                                      nickname.GetData() );
641 
642     if( aConfirm && !IsOK( this, msg ) )
643         return false;
644 
645     try
646     {
647         Prj().PcbFootprintLibs()->FootprintDelete( nickname, fpname );
648     }
649     catch( const IO_ERROR& ioe )
650     {
651         DisplayError( this, ioe.What() );
652         return false;
653     }
654 
655     msg.Printf( _( "Footprint '%s' deleted from library '%s'" ),
656                 fpname.GetData(),
657                 nickname.GetData() );
658 
659     SetStatusText( msg );
660 
661     return true;
662 }
663 
664 
ExportFootprintsToLibrary(bool aStoreInNewLib,const wxString & aLibName,wxString * aLibPath)665 void PCB_EDIT_FRAME::ExportFootprintsToLibrary( bool aStoreInNewLib, const wxString& aLibName,
666                                                 wxString* aLibPath )
667 {
668     if( GetBoard()->GetFirstFootprint() == nullptr )
669     {
670         DisplayInfoMessage( this, _( "No footprints to export!" ) );
671         return;
672     }
673 
674     wxString footprintName;
675 
676     auto resetReference =
677             []( FOOTPRINT* aFootprint )
678             {
679                 aFootprint->SetReference( "REF**" );
680             };
681 
682     if( !aStoreInNewLib )
683     {
684         // The footprints are saved in an existing .pretty library in the fp lib table
685         PROJECT& prj = Prj();
686         wxString last_nickname = prj.GetRString( PROJECT::PCB_LIB_NICKNAME );
687         wxString nickname = SelectLibrary( last_nickname );
688 
689         if( !nickname )     // Aborted
690             return;
691 
692         bool map = IsOK( this, wxString::Format( _( "Update footprints on board to refer to %s?" ),
693                                                  nickname ) );
694 
695         prj.SetRString( PROJECT::PCB_LIB_NICKNAME, nickname );
696 
697         for( FOOTPRINT* footprint : GetBoard()->Footprints() )
698         {
699             try
700             {
701                 FP_LIB_TABLE* tbl = prj.PcbFootprintLibs();
702 
703                 if( !footprint->GetFPID().GetLibItemName().empty() )    // Handle old boards.
704                 {
705                     FOOTPRINT* fpCopy = static_cast<FOOTPRINT*>( footprint->Duplicate() );
706 
707                     resetReference( fpCopy );
708                     tbl->FootprintSave( nickname, fpCopy, true );
709 
710                     delete fpCopy;
711                 }
712             }
713             catch( const IO_ERROR& ioe )
714             {
715                 DisplayError( this, ioe.What() );
716             }
717 
718             if( map )
719             {
720                 LIB_ID id = footprint->GetFPID();
721                 id.SetLibNickname( nickname );
722                 footprint->SetFPID( id );
723             }
724         }
725     }
726     else
727     {
728         // The footprints are saved in a new .pretty library.
729         // If this library already exists, all previous footprints will be deleted
730         wxString libPath = CreateNewLibrary( aLibName );
731 
732         if( libPath.IsEmpty() )     // Aborted
733             return;
734 
735         if( aLibPath )
736             *aLibPath = libPath;
737 
738         wxString libNickname;
739         bool     map = IsOK( this, _( "Update footprints on board to refer to new library?" ) );
740 
741         if( map )
742         {
743             const LIB_TABLE_ROW* row = Prj().PcbFootprintLibs()->FindRowByURI( libPath );
744 
745             if( row )
746                 libNickname = row->GetNickName();
747         }
748 
749         IO_MGR::PCB_FILE_T piType = IO_MGR::KICAD_SEXP;
750         PLUGIN::RELEASER   pi( IO_MGR::PluginFind( piType ) );
751 
752         for( FOOTPRINT* footprint : GetBoard()->Footprints() )
753         {
754             try
755             {
756                 if( !footprint->GetFPID().GetLibItemName().empty() )    // Handle old boards.
757                 {
758                     FOOTPRINT* fpCopy = static_cast<FOOTPRINT*>( footprint->Duplicate() );
759 
760                     resetReference( fpCopy );
761                     pi->FootprintSave( libPath, fpCopy );
762 
763                     delete fpCopy;
764                 }
765             }
766             catch( const IO_ERROR& ioe )
767             {
768                 DisplayError( this, ioe.What() );
769             }
770 
771             if( map )
772             {
773                 LIB_ID id = footprint->GetFPID();
774                 id.SetLibNickname( libNickname );
775                 footprint->SetFPID( id );
776             }
777         }
778     }
779 }
780 
781 
SaveFootprint(FOOTPRINT * aFootprint)782 bool FOOTPRINT_EDIT_FRAME::SaveFootprint( FOOTPRINT* aFootprint )
783 {
784     if( !aFootprint )      // Happen if no footprint loaded
785         return false;
786 
787     wxString libraryName = aFootprint->GetFPID().GetLibNickname();
788     wxString footprintName = aFootprint->GetFPID().GetLibItemName();
789     bool     nameChanged = m_footprintNameWhenLoaded != footprintName;
790 
791     if( aFootprint->GetLink() != niluuid )
792     {
793         if( SaveFootprintToBoard( false ) )
794         {
795             m_footprintNameWhenLoaded = footprintName;
796             return true;
797         }
798         else
799         {
800             return false;
801         }
802     }
803     else if( libraryName.IsEmpty() || footprintName.IsEmpty() )
804     {
805         if( SaveFootprintAs( aFootprint ) )
806         {
807             m_footprintNameWhenLoaded = footprintName;
808             SyncLibraryTree( true );
809             return true;
810         }
811         else
812         {
813             return false;
814         }
815     }
816 
817     FP_LIB_TABLE* tbl = Prj().PcbFootprintLibs();
818 
819     // Legacy libraries are readable, but modifying legacy format is not allowed
820     // So prompt the user if he try to add/replace a footprint in a legacy lib
821     wxString libfullname = tbl->FindRow( libraryName )->GetFullURI();
822 
823     if( IO_MGR::GuessPluginTypeFromLibPath( libfullname ) == IO_MGR::LEGACY )
824     {
825         DisplayInfoMessage( this, INFO_LEGACY_LIB_WARN_EDIT );
826         return false;
827     }
828 
829     if( nameChanged )
830     {
831         LIB_ID oldFPID( libraryName, m_footprintNameWhenLoaded );
832         DeleteFootprintFromLibrary( oldFPID, false );
833     }
834 
835     if( !SaveFootprintInLibrary( aFootprint, libraryName ) )
836         return false;
837 
838     if( nameChanged )
839     {
840         m_footprintNameWhenLoaded = footprintName;
841         SyncLibraryTree( true );
842     }
843 
844     return true;
845 }
846 
847 
SaveFootprintInLibrary(FOOTPRINT * aFootprint,const wxString & aLibraryName)848 bool FOOTPRINT_EDIT_FRAME::SaveFootprintInLibrary( FOOTPRINT* aFootprint,
849                                                    const wxString& aLibraryName )
850 {
851     try
852     {
853         aFootprint->SetFPID( LIB_ID( wxEmptyString, aFootprint->GetFPID().GetLibItemName() ) );
854 
855         Prj().PcbFootprintLibs()->FootprintSave( aLibraryName, aFootprint );
856 
857         aFootprint->SetFPID( LIB_ID( aLibraryName, aFootprint->GetFPID().GetLibItemName() ) );
858         return true;
859     }
860     catch( const IO_ERROR& ioe )
861     {
862         DisplayError( this, ioe.What() );
863 
864         aFootprint->SetFPID( LIB_ID( aLibraryName, aFootprint->GetFPID().GetLibItemName() ) );
865         return false;
866     }
867 }
868 
869 
SaveFootprintToBoard(bool aAddNew)870 bool FOOTPRINT_EDIT_FRAME::SaveFootprintToBoard( bool aAddNew )
871 {
872     // update footprint in the current board,
873     // not just add it to the board with total disregard for the netlist...
874     PCB_EDIT_FRAME* pcbframe = (PCB_EDIT_FRAME*) Kiway().Player( FRAME_PCB_EDITOR, false );
875 
876     if( pcbframe == nullptr )       // happens when the board editor is not active (or closed)
877     {
878         ShowInfoBarError( _( "No board currently open." ) );
879         return false;
880     }
881 
882     TOOL_MANAGER*   toolMgr = pcbframe->GetToolManager();
883     BOARD*     mainpcb  = pcbframe->GetBoard();
884     FOOTPRINT* sourceFootprint  = nullptr;
885     FOOTPRINT* editorFootprint = GetBoard()->GetFirstFootprint();
886 
887     // Search the old footprint (source) if exists
888     // Because this source could be deleted when editing the main board...
889     if( editorFootprint->GetLink() != niluuid )        // this is not a new footprint ...
890     {
891         sourceFootprint = nullptr;
892 
893         for( FOOTPRINT* candidate : mainpcb->Footprints() )
894         {
895             if( editorFootprint->GetLink() == candidate->m_Uuid )
896             {
897                 sourceFootprint = candidate;
898                 break;
899             }
900         }
901     }
902 
903     if( !aAddNew && sourceFootprint == nullptr )    // source not found
904     {
905         DisplayError( this, _( "Unable to find the footprint on the main board.\nCannot save." ) );
906         return false;
907     }
908 
909     if( aAddNew && toolMgr->GetTool<BOARD_EDITOR_CONTROL>()->PlacingFootprint() )
910     {
911         DisplayError( this, _( "Previous footprint placement still in progress." ) );
912         return false;
913     }
914 
915     m_toolManager->RunAction( PCB_ACTIONS::selectionClear, true );
916     toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
917     BOARD_COMMIT commit( pcbframe );
918 
919     // Create a copy for the board, first using Clone() to keep existing Uuids, and then either
920     // resetting the uuids to the board values or assigning new Uuids.
921     FOOTPRINT* newFootprint = static_cast<FOOTPRINT*>( editorFootprint->Clone() );
922     newFootprint->SetParent( mainpcb );
923     newFootprint->SetLink( niluuid );
924 
925     auto fixUuid =
926             [&]( KIID& aUuid )
927             {
928                 if( editorFootprint->GetLink() != niluuid && m_boardFootprintUuids.count( aUuid ) )
929                     aUuid = m_boardFootprintUuids[ aUuid ];
930                 else
931                     aUuid = KIID();
932             };
933 
934     fixUuid( const_cast<KIID&>( newFootprint->m_Uuid ) );
935     newFootprint->RunOnChildren(
936             [&]( BOARD_ITEM* aChild )
937             {
938                 fixUuid( const_cast<KIID&>( aChild->m_Uuid ) );
939             } );
940 
941     if( sourceFootprint )         // this is an update command
942     {
943         // In the main board the new footprint replaces the old one (pos, orient, ref, value,
944         // connections and properties are kept) and the sourceFootprint (old footprint) is
945         // deleted
946         pcbframe->ExchangeFootprint( sourceFootprint, newFootprint, commit );
947         commit.Push( wxT( "Update footprint" ) );
948     }
949     else        // This is an insert command
950     {
951         KIGFX::VIEW_CONTROLS* viewControls = pcbframe->GetCanvas()->GetViewControls();
952         VECTOR2D              cursorPos = viewControls->GetCursorPosition();
953 
954         commit.Add( newFootprint );
955         viewControls->SetCrossHairCursorPosition( VECTOR2D( 0, 0 ), false );
956         pcbframe->PlaceFootprint( newFootprint );
957         newFootprint->SetPosition( wxPoint( 0, 0 ) );
958         viewControls->SetCrossHairCursorPosition( cursorPos, false );
959         const_cast<KIID&>( newFootprint->m_Uuid ) = KIID();
960         commit.Push( wxT( "Insert footprint" ) );
961 
962         pcbframe->Raise();
963         toolMgr->RunAction( PCB_ACTIONS::placeFootprint, true, newFootprint );
964     }
965 
966     newFootprint->ClearFlags();
967 
968     return true;
969 }
970 
971 
SaveFootprintAs(FOOTPRINT * aFootprint)972 bool FOOTPRINT_EDIT_FRAME::SaveFootprintAs( FOOTPRINT* aFootprint )
973 {
974     if( aFootprint == nullptr )
975         return false;
976 
977     FP_LIB_TABLE* tbl = Prj().PcbFootprintLibs();
978 
979     SetMsgPanel( aFootprint );
980 
981     wxString libraryName = aFootprint->GetFPID().GetLibNickname();
982     wxString footprintName = aFootprint->GetFPID().GetLibItemName();
983     bool     updateValue = aFootprint->GetValue() == footprintName;
984 
985     wxArrayString              headers;
986     std::vector<wxArrayString> itemsToDisplay;
987     std::vector<wxString>      nicknames = tbl->GetLogicalLibs();
988 
989     headers.Add( _( "Nickname" ) );
990     headers.Add( _( "Description" ) );
991 
992     for( const wxString& nickname : nicknames )
993     {
994         wxArrayString item;
995         item.Add( nickname );
996         item.Add( tbl->GetDescription( nickname ) );
997         itemsToDisplay.push_back( item );
998     }
999 
1000     EDA_LIST_DIALOG dlg( this, _( "Save Footprint As" ), headers, itemsToDisplay, libraryName );
1001     dlg.SetListLabel( _( "Save in library:" ) );
1002     dlg.SetOKLabel( _( "Save" ) );
1003 
1004     wxBoxSizer* bNameSizer = new wxBoxSizer( wxHORIZONTAL );
1005 
1006     wxStaticText* label = new wxStaticText( &dlg, wxID_ANY, _( "Name:" ),
1007                                             wxDefaultPosition, wxDefaultSize, 0 );
1008     bNameSizer->Add( label, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxLEFT, 5 );
1009 
1010     wxTextCtrl* nameTextCtrl = new wxTextCtrl( &dlg, wxID_ANY, footprintName,
1011                                                wxDefaultPosition, wxDefaultSize, 0 );
1012     bNameSizer->Add( nameTextCtrl, 1, wxALIGN_CENTER_VERTICAL|wxALL, 5 );
1013 
1014     wxTextValidator nameValidator( wxFILTER_EXCLUDE_CHAR_LIST );
1015     nameValidator.SetCharExcludes( FOOTPRINT::StringLibNameInvalidChars( false ) );
1016     nameTextCtrl->SetValidator( nameValidator );
1017 
1018     wxSizer* mainSizer = dlg.GetSizer();
1019     mainSizer->Prepend( bNameSizer, 0, wxEXPAND|wxTOP|wxLEFT|wxRIGHT, 5 );
1020 
1021     // Move nameTextCtrl to the head of the tab-order
1022     if( dlg.GetChildren().DeleteObject( nameTextCtrl ) )
1023         dlg.GetChildren().Insert( nameTextCtrl );
1024 
1025     dlg.SetInitialFocus( nameTextCtrl );
1026 
1027     dlg.Layout();
1028     mainSizer->Fit( &dlg );
1029 
1030     if( dlg.ShowModal() != wxID_OK )
1031         return false;                   // canceled by user
1032 
1033     libraryName = dlg.GetTextSelection();
1034 
1035     if( libraryName.IsEmpty() )
1036     {
1037         DisplayError( this, _( "No library specified.  Footprint could not be saved." ) );
1038         return false;
1039     }
1040 
1041     footprintName = nameTextCtrl->GetValue();
1042     footprintName.Trim( true );
1043     footprintName.Trim( false );
1044 
1045     if( footprintName.IsEmpty() )
1046     {
1047         DisplayError( this, _( "No footprint name specified.  Footprint could not be saved." ) );
1048         return false;
1049     }
1050 
1051     aFootprint->SetFPID( LIB_ID( libraryName, footprintName ) );
1052 
1053     if( updateValue )
1054         aFootprint->SetValue( footprintName );
1055 
1056     // Legacy libraries are readable, but modifying legacy format is not allowed
1057     // So prompt the user if he try to add/replace a footprint in a legacy lib
1058     wxString    libfullname = Prj().PcbFootprintLibs()->FindRow( libraryName )->GetFullURI();
1059     IO_MGR::PCB_FILE_T  piType = IO_MGR::GuessPluginTypeFromLibPath( libfullname );
1060 
1061     if( piType == IO_MGR::LEGACY )
1062     {
1063         DisplayInfoMessage( this, INFO_LEGACY_LIB_WARN_EDIT );
1064         return false;
1065     }
1066 
1067     bool footprintExists = tbl->FootprintExists( libraryName, footprintName );
1068 
1069     if( footprintExists )
1070     {
1071         wxString msg = wxString::Format( _( "Footprint %s already exists in %s." ),
1072                                          footprintName,
1073                                          libraryName );
1074         KIDIALOG chkdlg( this, msg, _( "Confirmation" ), wxOK | wxCANCEL | wxICON_WARNING );
1075         chkdlg.SetOKLabel( _( "Overwrite" ) );
1076 
1077         if( chkdlg.ShowModal() == wxID_CANCEL )
1078             return false;
1079     }
1080 
1081     if( !SaveFootprintInLibrary( aFootprint, libraryName ) )
1082         return false;
1083 
1084     // Once saved-as a board footprint is no longer a board footprint
1085     aFootprint->SetLink( niluuid );
1086 
1087     wxString fmt = footprintExists ? _( "Footprint '%s' replaced in '%s'" )
1088                                    : _( "Footprint '%s' added to '%s'" );
1089 
1090     wxString msg = wxString::Format( fmt, footprintName.GetData(), libraryName.GetData() );
1091     SetStatusText( msg );
1092     UpdateTitle();
1093     ReCreateHToolbar();
1094 
1095     return true;
1096 }
1097 
1098 
RevertFootprint()1099 bool FOOTPRINT_EDIT_FRAME::RevertFootprint()
1100 {
1101     if( GetScreen()->IsContentModified() && m_revertModule )
1102     {
1103         wxString msg = wxString::Format( _( "Revert '%s' to last version saved?" ),
1104                                          GetLoadedFPID().GetLibItemName().wx_str() );
1105 
1106         if( ConfirmRevertDialog( this, msg ) )
1107         {
1108             Clear_Pcb( false );
1109             AddFootprintToBoard( static_cast<FOOTPRINT*>( m_revertModule->Clone() ) );
1110 
1111             Zoom_Automatique( false );
1112 
1113             Update3DView( true, true );
1114 
1115             ClearUndoRedoList();
1116             GetScreen()->SetContentModified( false );
1117 
1118             UpdateView();
1119             GetCanvas()->Refresh();
1120 
1121             return true;
1122         }
1123     }
1124 
1125     return false;
1126 }
1127 
1128 
CreateNewFootprint(const wxString & aFootprintName,bool aQuiet)1129 FOOTPRINT* PCB_BASE_FRAME::CreateNewFootprint( const wxString& aFootprintName, bool aQuiet )
1130 {
1131     wxString footprintName = aFootprintName;
1132 
1133     // Static to store user preference for a session
1134     static int footprintType = 1;
1135     int footprintTranslated = FP_SMD;
1136 
1137     // Ask for the new footprint name
1138     if( footprintName.IsEmpty() && !aQuiet )
1139     {
1140         WX_TEXT_ENTRY_DIALOG dlg( this, _( "Enter footprint name:" ), _( "New Footprint" ),
1141                                   footprintName, _( "Footprint type:" ),
1142                                   { _( "Through hole" ), _( "SMD" ), _( "Other" ) },
1143                                   footprintType );
1144         dlg.SetTextValidator( FOOTPRINT_NAME_VALIDATOR( &footprintName ) );
1145 
1146         if( dlg.ShowModal() != wxID_OK )
1147             return nullptr;    //Aborted by user
1148 
1149         footprintType = dlg.GetChoice();
1150 
1151         switch( footprintType )
1152         {
1153         case 0:
1154             footprintTranslated = FP_THROUGH_HOLE;
1155             break;
1156         case 1:
1157             footprintTranslated = FP_SMD;
1158             break;
1159         default:
1160             footprintTranslated = 0;
1161         }
1162     }
1163 
1164     footprintName.Trim( true );
1165     footprintName.Trim( false );
1166 
1167     if( footprintName.IsEmpty() )
1168     {
1169         if( !aQuiet )
1170             DisplayInfoMessage( this, _( "No footprint name defined." ) );
1171 
1172         return nullptr;
1173     }
1174 
1175     // Creates the new footprint and add it to the head of the linked list of footprints
1176     FOOTPRINT* footprint = new FOOTPRINT( GetBoard() );
1177 
1178     // Update parameters: timestamp ...
1179     footprint->SetLastEditTime();
1180 
1181     // Update its name in lib
1182     footprint->SetFPID( LIB_ID( wxEmptyString, footprintName ) );
1183 
1184     footprint->SetAttributes( footprintTranslated );
1185 
1186     PCB_LAYER_ID txt_layer;
1187     wxPoint default_pos;
1188     BOARD_DESIGN_SETTINGS& settings = GetDesignSettings();
1189 
1190     footprint->Reference().SetText( settings.m_DefaultFPTextItems[0].m_Text );
1191     footprint->Reference().SetVisible( settings.m_DefaultFPTextItems[0].m_Visible );
1192     txt_layer = (PCB_LAYER_ID) settings.m_DefaultFPTextItems[0].m_Layer;
1193     footprint->Reference().SetLayer( txt_layer );
1194     default_pos.y -= settings.GetTextSize( txt_layer ).y / 2;
1195     footprint->Reference().SetPosition( default_pos );
1196     default_pos.y += settings.GetTextSize( txt_layer ).y;
1197 
1198     footprint->Value().SetText( settings.m_DefaultFPTextItems[1].m_Text );
1199     footprint->Value().SetVisible( settings.m_DefaultFPTextItems[1].m_Visible );
1200     txt_layer = (PCB_LAYER_ID) settings.m_DefaultFPTextItems[1].m_Layer;
1201     footprint->Value().SetLayer( txt_layer );
1202     default_pos.y += settings.GetTextSize( txt_layer ).y / 2;
1203     footprint->Value().SetPosition( default_pos );
1204     default_pos.y += settings.GetTextSize( txt_layer ).y;
1205 
1206     for( size_t i = 2; i < settings.m_DefaultFPTextItems.size(); ++i )
1207     {
1208         FP_TEXT* textItem = new FP_TEXT( footprint );
1209         textItem->SetText( settings.m_DefaultFPTextItems[i].m_Text );
1210         textItem->SetVisible( settings.m_DefaultFPTextItems[i].m_Visible );
1211         txt_layer = (PCB_LAYER_ID) settings.m_DefaultFPTextItems[i].m_Layer;
1212         textItem->SetLayer( txt_layer );
1213         default_pos.y += settings.GetTextSize( txt_layer ).y / 2;
1214         textItem->SetPosition( default_pos );
1215         default_pos.y += settings.GetTextSize( txt_layer ).y;
1216         footprint->GraphicalItems().push_back( textItem );
1217     }
1218 
1219     if( footprint->GetReference().IsEmpty() )
1220         footprint->SetReference( footprintName );
1221 
1222     if( footprint->GetValue().IsEmpty() )
1223         footprint->SetValue( footprintName );
1224 
1225     footprint->RunOnChildren(
1226             [&] ( BOARD_ITEM* aChild )
1227             {
1228                 if( aChild->Type() == PCB_FP_TEXT_T )
1229                 {
1230                     FP_TEXT*     textItem = static_cast<FP_TEXT*>( aChild );
1231                     PCB_LAYER_ID layer = textItem->GetLayer();
1232 
1233                     textItem->SetTextThickness( settings.GetTextThickness( layer ) );
1234                     textItem->SetTextSize( settings.GetTextSize( layer ) );
1235                     textItem->SetItalic( settings.GetTextItalic( layer ) );
1236                     textItem->SetKeepUpright( settings.GetTextUpright( layer ) );
1237                 }
1238             } );
1239 
1240     SetMsgPanel( footprint );
1241     return footprint;
1242 }
1243 
1244 
SelectLibrary(const wxString & aNicknameExisting)1245 wxString PCB_BASE_FRAME::SelectLibrary( const wxString& aNicknameExisting )
1246 {
1247     wxArrayString headers;
1248 
1249     headers.Add( _( "Nickname" ) );
1250     headers.Add( _( "Description" ) );
1251 
1252     FP_LIB_TABLE*   fptbl = Prj().PcbFootprintLibs();
1253 
1254     std::vector< wxArrayString > itemsToDisplay;
1255     std::vector< wxString >      nicknames = fptbl->GetLogicalLibs();
1256 
1257     for( const wxString& nickname : nicknames )
1258     {
1259         wxArrayString item;
1260 
1261         item.Add( nickname );
1262         item.Add( fptbl->GetDescription( nickname ) );
1263 
1264         itemsToDisplay.push_back( item );
1265     }
1266 
1267     EDA_LIST_DIALOG dlg( this, _( "Select Library" ), headers, itemsToDisplay, aNicknameExisting );
1268 
1269     if( dlg.ShowModal() != wxID_OK )
1270         return wxEmptyString;
1271 
1272     return dlg.GetTextSelection();
1273 }
1274