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