1 /*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2013 Jean-Pierre Charras, jp.charras at wanadoo.fr
5 * Copyright (C) 2013 Wayne Stambaugh <stambaughw@gmail.com>
6 * Copyright (C) 2013 CERN (www.cern.ch)
7 * Copyright (C) 1992-2021 KiCad Developers, see AUTHORS.txt for contributors.
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License
11 * as published by the Free Software Foundation; either version 2
12 * of the License, or (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, you may find one here:
21 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
22 * or you may search the http://www.gnu.org website for the version 2 license,
23 * or you may write to the Free Software Foundation, Inc.,
24 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
25 */
26
27 #include <symbol_library.h>
28 #include <confirm.h>
29 #include <connection_graph.h>
30 #include <dialog_migrate_buses.h>
31 #include <dialog_symbol_remap.h>
32 #include <eeschema_settings.h>
33 #include <id.h>
34 #include <kiface_base.h>
35 #include <kiplatform/app.h>
36 #include <pgm_base.h>
37 #include <profile.h>
38 #include <project/project_file.h>
39 #include <project_rescue.h>
40 #include <wx_html_report_box.h>
41 #include <dialog_HTML_reporter_base.h>
42 #include <reporter.h>
43 #include <richio.h>
44 #include <sch_bus_entry.h>
45 #include <sch_edit_frame.h>
46 #include <sch_plugins/legacy/sch_legacy_plugin.h>
47 #include <sch_file_versions.h>
48 #include <sch_line.h>
49 #include <sch_sheet.h>
50 #include <sch_sheet_path.h>
51 #include <schematic.h>
52 #include <settings/settings_manager.h>
53 #include <sim/sim_plot_frame.h>
54 #include <tool/actions.h>
55 #include <tool/tool_manager.h>
56 #include <tools/sch_editor_control.h>
57 #include <trace_helpers.h>
58 #include <widgets/infobar.h>
59 #include <wildcards_and_files_ext.h>
60 #include <drawing_sheet/ds_data_model.h>
61 #include <wx/app.h>
62 #include <wx/ffile.h>
63 #include <wx/filedlg.h>
64 #include <wx/log.h>
65 #include <tools/ee_inspection_tool.h>
66 #include <paths.h>
67 #include <wx_filename.h> // For ::ResolvePossibleSymlinks
68 #include <widgets/wx_progress_reporters.h>
69
70
71 ///< Helper widget to select whether a new project should be created for a file when saving
72 class CREATE_PROJECT_CHECKBOX : public wxPanel
73 {
74 public:
CREATE_PROJECT_CHECKBOX(wxWindow * aParent)75 CREATE_PROJECT_CHECKBOX( wxWindow* aParent )
76 : wxPanel( aParent )
77 {
78 m_cbCreateProject = new wxCheckBox( this, wxID_ANY,
79 _( "Create a new project for this schematic" ) );
80 m_cbCreateProject->SetValue( true );
81 m_cbCreateProject->SetToolTip( _( "Creating a project will enable features such as "
82 "text variables, net classes, and ERC exclusions" ) );
83
84 wxBoxSizer* sizer = new wxBoxSizer( wxHORIZONTAL );
85 sizer->Add( m_cbCreateProject, 0, wxALL, 8 );
86
87 SetSizerAndFit( sizer );
88 }
89
GetValue() const90 bool GetValue() const
91 {
92 return m_cbCreateProject->GetValue();
93 }
94
Create(wxWindow * aParent)95 static wxWindow* Create( wxWindow* aParent )
96 {
97 return new CREATE_PROJECT_CHECKBOX( aParent );
98 }
99
100 protected:
101 wxCheckBox* m_cbCreateProject;
102 };
103
104
OpenProjectFiles(const std::vector<wxString> & aFileSet,int aCtl)105 bool SCH_EDIT_FRAME::OpenProjectFiles( const std::vector<wxString>& aFileSet, int aCtl )
106 {
107 // implement the pseudo code from KIWAY_PLAYER.h:
108 wxString msg;
109
110 EESCHEMA_SETTINGS* cfg = dynamic_cast<EESCHEMA_SETTINGS*>( Kiface().KifaceSettings() );
111
112 // This is for python:
113 if( aFileSet.size() != 1 )
114 {
115 msg.Printf( "Eeschema:%s() takes only a single filename.", __WXFUNCTION__ );
116 DisplayError( this, msg );
117 return false;
118 }
119
120 wxString fullFileName( aFileSet[0] );
121 wxFileName wx_filename( fullFileName );
122
123 // We insist on caller sending us an absolute path, if it does not, we say it's a bug.
124 wxASSERT_MSG( wx_filename.IsAbsolute(), wxT( "Path is not absolute!" ) );
125
126 if( !LockFile( fullFileName ) )
127 {
128 msg.Printf( _( "Schematic '%s' is already open." ), wx_filename.GetFullName() );
129
130 if( !OverrideLock( this, msg ) )
131 return false;
132 }
133
134 if( !AskToSaveChanges() )
135 return false;
136
137 #ifdef PROFILE
138 PROF_COUNTER openFiles( "OpenProjectFile" );
139 #endif
140
141 wxFileName pro = fullFileName;
142 pro.SetExt( ProjectFileExtension );
143
144 bool is_new = !wxFileName::IsFileReadable( fullFileName );
145
146 // If its a non-existent schematic and caller thinks it exists
147 if( is_new && !( aCtl & KICTL_CREATE ) )
148 {
149 // notify user that fullFileName does not exist, ask if user wants to create it.
150 msg.Printf( _( "Schematic '%s' does not exist. Do you wish to create it?" ),
151 fullFileName );
152
153 if( !IsOK( this, msg ) )
154 return false;
155 }
156
157 // unload current project file before loading new
158 {
159 ClearUndoRedoList();
160 SetScreen( nullptr );
161 m_toolManager->GetTool<EE_INSPECTION_TOOL>()->Reset( TOOL_BASE::MODEL_RELOAD );
162 CreateScreens();
163 }
164
165 SetStatusText( wxEmptyString );
166 m_infoBar->Dismiss();
167
168 WX_PROGRESS_REPORTER progressReporter( this, is_new ? _( "Creating Schematic" )
169 : _( "Loading Schematic" ), 1 );
170
171 bool differentProject = pro.GetFullPath() != Prj().GetProjectFullName();
172
173 if( differentProject )
174 {
175 if( !Prj().IsNullProject() )
176 GetSettingsManager()->SaveProject();
177
178 Schematic().SetProject( nullptr );
179 GetSettingsManager()->UnloadProject( &Prj(), false );
180
181 GetSettingsManager()->LoadProject( pro.GetFullPath() );
182
183 wxFileName legacyPro( pro );
184 legacyPro.SetExt( LegacyProjectFileExtension );
185
186 // Do not allow saving a project if one doesn't exist. This normally happens if we are
187 // standalone and opening a schematic that has been moved from its project folder.
188 if( !pro.Exists() && !legacyPro.Exists() && !( aCtl & KICTL_CREATE ) )
189 Prj().SetReadOnly();
190
191 CreateScreens();
192 }
193
194 SCH_IO_MGR::SCH_FILE_T schFileType = SCH_IO_MGR::GuessPluginTypeFromSchPath( fullFileName );
195
196 if( schFileType == SCH_IO_MGR::SCH_LEGACY )
197 {
198 // Don't reload the symbol libraries if we are just launching Eeschema from KiCad again.
199 // They are already saved in the kiface project object.
200 if( differentProject || !Prj().GetElem( PROJECT::ELEM_SCH_SYMBOL_LIBS ) )
201 {
202 // load the libraries here, not in SCH_SCREEN::Draw() which is a context
203 // that will not tolerate DisplayError() dialog since we're already in an
204 // event handler in there.
205 // And when a schematic file is loaded, we need these libs to initialize
206 // some parameters (links to PART LIB, dangling ends ...)
207 Prj().SetElem( PROJECT::ELEM_SCH_SYMBOL_LIBS, nullptr );
208 Prj().SchLibs();
209 }
210 }
211 else
212 {
213 // No legacy symbol libraries including the cache are loaded with the new file format.
214 Prj().SetElem( PROJECT::ELEM_SCH_SYMBOL_LIBS, nullptr );
215 }
216
217 // Load the symbol library table, this will be used forever more.
218 Prj().SetElem( PROJECT::ELEM_SYMBOL_LIB_TABLE, nullptr );
219 Prj().SchSymbolLibTable();
220
221 // Load project settings after schematic has been set up with the project link, since this will
222 // update some of the needed schematic settings such as drawing defaults
223 LoadProjectSettings();
224
225 wxFileName rfn( GetCurrentFileName() );
226 rfn.MakeRelativeTo( Prj().GetProjectPath() );
227 LoadWindowState( rfn.GetFullPath() );
228
229 KIPLATFORM::APP::SetShutdownBlockReason( this, _( "Schematic file changes are unsaved" ) );
230
231 if( Kiface().IsSingle() )
232 {
233 KIPLATFORM::APP::RegisterApplicationRestart( fullFileName );
234 }
235
236 if( is_new )
237 {
238 // mark new, unsaved file as modified.
239 GetScreen()->SetContentModified();
240 GetScreen()->SetFileName( fullFileName );
241 }
242 else
243 {
244 wxFileName autoSaveFn = fullFileName;
245
246 autoSaveFn.SetName( getAutoSaveFileName() );
247 autoSaveFn.ClearExt();
248
249 CheckForAutoSaveFile( autoSaveFn );
250
251 SetScreen( nullptr );
252
253 SCH_PLUGIN* plugin = SCH_IO_MGR::FindPlugin( schFileType );
254 SCH_PLUGIN::SCH_PLUGIN_RELEASER pi( plugin );
255
256 pi->SetProgressReporter( &progressReporter );
257
258 bool failedLoad = false;
259
260 try
261 {
262 Schematic().SetRoot( pi->Load( fullFileName, &Schematic() ) );
263
264 if( !pi->GetError().IsEmpty() )
265 {
266 DisplayErrorMessage( this, _( "The entire schematic could not be loaded. Errors "
267 "occurred attempting to load hierarchical sheets." ),
268 pi->GetError() );
269 }
270 }
271 catch( const FUTURE_FORMAT_ERROR& ffe )
272 {
273 msg.Printf( _( "Error loading schematic '%s'." ), fullFileName);
274 progressReporter.Hide();
275 DisplayErrorMessage( this, msg, ffe.Problem() );
276
277 failedLoad = true;
278 }
279 catch( const IO_ERROR& ioe )
280 {
281 msg.Printf( _( "Error loading schematic '%s'." ), fullFileName);
282 progressReporter.Hide();
283 DisplayErrorMessage( this, msg, ioe.What() );
284
285 failedLoad = true;
286 }
287 catch( const std::bad_alloc& )
288 {
289 msg.Printf( _( "Memory exhausted loading schematic '%s'." ), fullFileName );
290 progressReporter.Hide();
291 DisplayErrorMessage( this, msg, wxEmptyString );
292
293 failedLoad = true;
294 }
295
296 // This fixes a focus issue after the progress reporter is done on GTK. It shouldn't
297 // cause any issues on macOS and Windows. If it does, it will have to be conditionally
298 // compiled.
299 Raise();
300
301 if( failedLoad )
302 {
303 // Do not leave g_RootSheet == NULL because it is expected to be
304 // a valid sheet. Therefore create a dummy empty root sheet and screen.
305 CreateScreens();
306 m_toolManager->RunAction( ACTIONS::zoomFitScreen, true );
307
308 msg.Printf( _( "Failed to load '%s'." ), fullFileName );
309 SetMsgPanel( wxEmptyString, msg );
310
311 return false;
312 }
313
314 // It's possible the schematic parser fixed errors due to bugs so warn the user
315 // that the schematic has been fixed (modified).
316 SCH_SHEET_LIST sheetList = Schematic().GetSheets();
317
318 if( sheetList.IsModified() )
319 {
320 DisplayInfoMessage( this,
321 _( "An error was found when loading the schematic that has "
322 "been automatically fixed. Please save the schematic to "
323 "repair the broken file or it may not be usable with other "
324 "versions of KiCad." ) );
325 }
326
327 if( sheetList.AllSheetPageNumbersEmpty() )
328 sheetList.SetInitialPageNumbers();
329
330 UpdateFileHistory( fullFileName );
331
332 SCH_SCREENS schematic( Schematic().Root() );
333
334 // LIB_ID checks and symbol rescue only apply to the legacy file formats.
335 if( schFileType == SCH_IO_MGR::SCH_LEGACY )
336 {
337 // Convert any legacy bus-bus entries to just be bus wires
338 for( SCH_SCREEN* screen = schematic.GetFirst(); screen; screen = schematic.GetNext() )
339 {
340 std::vector<SCH_ITEM*> deleted;
341
342 for( SCH_ITEM* item : screen->Items() )
343 {
344 if( item->Type() == SCH_BUS_BUS_ENTRY_T )
345 {
346 SCH_BUS_BUS_ENTRY* entry = static_cast<SCH_BUS_BUS_ENTRY*>( item );
347 std::unique_ptr<SCH_LINE> wire = std::make_unique<SCH_LINE>();
348
349 wire->SetLayer( LAYER_BUS );
350 wire->SetStartPoint( entry->GetPosition() );
351 wire->SetEndPoint( entry->GetEnd() );
352
353 screen->Append( wire.release() );
354 deleted.push_back( item );
355 }
356 }
357
358 for( SCH_ITEM* item : deleted )
359 screen->Remove( item );
360 }
361
362
363 // Convert old projects over to use symbol library table.
364 if( schematic.HasNoFullyDefinedLibIds() )
365 {
366 DIALOG_SYMBOL_REMAP dlgRemap( this );
367
368 dlgRemap.ShowQuasiModal();
369 }
370 else
371 {
372 // Double check to ensure no legacy library list entries have been
373 // added to the project file symbol library list.
374 wxString paths;
375 wxArrayString libNames;
376
377 SYMBOL_LIBS::LibNamesAndPaths( &Prj(), false, &paths, &libNames );
378
379 if( !libNames.IsEmpty() )
380 {
381 if( eeconfig()->m_Appearance.show_illegal_symbol_lib_dialog )
382 {
383 wxRichMessageDialog invalidLibDlg(
384 this,
385 _( "Illegal entry found in project file symbol library list." ),
386 _( "Project Load Warning" ),
387 wxOK | wxCENTER | wxICON_EXCLAMATION );
388 invalidLibDlg.ShowDetailedText(
389 _( "Symbol libraries defined in the project file symbol library "
390 "list are no longer supported and will be removed.\n\n"
391 "This may cause broken symbol library links under certain "
392 "conditions." ) );
393 invalidLibDlg.ShowCheckBox( _( "Do not show this dialog again." ) );
394 invalidLibDlg.ShowModal();
395 eeconfig()->m_Appearance.show_illegal_symbol_lib_dialog =
396 !invalidLibDlg.IsCheckBoxChecked();
397 }
398
399 libNames.Clear();
400 paths.Clear();
401 SYMBOL_LIBS::LibNamesAndPaths( &Prj(), true, &paths, &libNames );
402 }
403
404 if( !cfg || !cfg->m_RescueNeverShow )
405 {
406 SCH_EDITOR_CONTROL* editor = m_toolManager->GetTool<SCH_EDITOR_CONTROL>();
407 editor->RescueSymbolLibTableProject( false );
408 }
409 }
410
411 // Ensure there is only one legacy library loaded and that it is the cache library.
412 SYMBOL_LIBS* legacyLibs = Schematic().Prj().SchLibs();
413
414 if( legacyLibs->GetLibraryCount() == 0 )
415 {
416 wxString extMsg;
417 wxFileName cacheFn = pro;
418
419 cacheFn.SetName( cacheFn.GetName() + "-cache" );
420 cacheFn.SetExt( LegacySymbolLibFileExtension );
421
422 msg.Printf( _( "The project symbol library cache file '%s' was not found." ),
423 cacheFn.GetFullName() );
424 extMsg = _( "This can result in a broken schematic under certain conditions. "
425 "If the schematic does not have any missing symbols upon opening, "
426 "save it immediately before making any changes to prevent data "
427 "loss. If there are missing symbols, either manual recovery of "
428 "the schematic or recovery of the symbol cache library file and "
429 "reloading the schematic is required." );
430
431 wxMessageDialog dlgMissingCache( this, msg, _( "Warning" ),
432 wxOK | wxCANCEL | wxICON_EXCLAMATION | wxCENTER );
433 dlgMissingCache.SetExtendedMessage( extMsg );
434 dlgMissingCache.SetOKCancelLabels(
435 wxMessageDialog::ButtonLabel( _( "Load Without Cache File" ) ),
436 wxMessageDialog::ButtonLabel( _( "Abort" ) ) );
437
438 if( dlgMissingCache.ShowModal() == wxID_CANCEL )
439 {
440 Schematic().Reset();
441 CreateScreens();
442 return false;
443 }
444 }
445
446 // Update all symbol library links for all sheets.
447 schematic.UpdateSymbolLinks();
448
449 m_infoBar->RemoveAllButtons();
450 m_infoBar->AddCloseButton();
451 m_infoBar->ShowMessage( _( "This file was created by an older version of KiCad. "
452 "It will be converted to the new format when saved." ),
453 wxICON_WARNING, WX_INFOBAR::MESSAGE_TYPE::OUTDATED_SAVE );
454
455 // Legacy schematic can have duplicate time stamps so fix that before converting
456 // to the s-expression format.
457 schematic.ReplaceDuplicateTimeStamps();
458
459 // Allow the schematic to be saved to new file format without making any edits.
460 OnModify();
461 }
462 else // S-expression schematic.
463 {
464 if( schematic.GetFirst()->GetFileFormatVersionAtLoad() < SEXPR_SCHEMATIC_FILE_VERSION )
465 {
466 m_infoBar->RemoveAllButtons();
467 m_infoBar->AddCloseButton();
468 m_infoBar->ShowMessage( _( "This file was created by an older version of KiCad. "
469 "It will be converted to the new format when saved." ),
470 wxICON_WARNING, WX_INFOBAR::MESSAGE_TYPE::OUTDATED_SAVE );
471 }
472
473 for( SCH_SCREEN* screen = schematic.GetFirst(); screen; screen = schematic.GetNext() )
474 screen->UpdateLocalLibSymbolLinks();
475
476 // Restore all of the loaded symbol and sheet instances from the root sheet.
477 sheetList.UpdateSymbolInstances( Schematic().RootScreen()->GetSymbolInstances() );
478 sheetList.UpdateSheetInstances( Schematic().RootScreen()->GetSheetInstances() );
479 }
480
481 Schematic().ConnectionGraph()->Reset();
482
483 SetScreen( GetCurrentSheet().LastScreen() );
484
485 // Migrate conflicting bus definitions
486 // TODO(JE) This should only run once based on schematic file version
487 if( Schematic().ConnectionGraph()->GetBusesNeedingMigration().size() > 0 )
488 {
489 DIALOG_MIGRATE_BUSES dlg( this );
490 dlg.ShowQuasiModal();
491 RecalculateConnections( NO_CLEANUP );
492 OnModify();
493 }
494
495 RecalculateConnections( GLOBAL_CLEANUP );
496 ClearUndoRedoList();
497 }
498
499 // Load any exclusions from the project file
500 ResolveERCExclusions();
501
502 initScreenZoom();
503 SetSheetNumberAndCount();
504
505 RecomputeIntersheetRefs();
506 GetCurrentSheet().UpdateAllScreenReferences();
507
508 // re-create junctions if needed. Eeschema optimizes wires by merging
509 // colinear segments. If a schematic is saved without a valid
510 // cache library or missing installed libraries, this can cause connectivity errors
511 // unless junctions are added.
512 if( schFileType == SCH_IO_MGR::SCH_LEGACY )
513 FixupJunctions();
514
515 SyncView();
516 GetScreen()->ClearDrawingState();
517
518 UpdateHierarchyNavigator();
519 UpdateTitle();
520
521 wxFileName fn = Prj().AbsolutePath( GetScreen()->GetFileName() );
522
523 if( fn.FileExists() && !fn.IsFileWritable() )
524 {
525 m_infoBar->RemoveAllButtons();
526 m_infoBar->AddCloseButton();
527 m_infoBar->ShowMessage( _( "Schematic is read only." ), wxICON_WARNING );
528 }
529
530 #ifdef PROFILE
531 openFiles.Show();
532 #endif
533
534 return true;
535 }
536
537
AppendSchematic()538 bool SCH_EDIT_FRAME::AppendSchematic()
539 {
540 wxString fullFileName;
541 SCH_SCREEN* screen = GetScreen();
542
543 if( !screen )
544 {
545 wxLogError( wxT( "Document not ready, cannot import" ) );
546 return false;
547 }
548
549 // open file chooser dialog
550 wxString path = wxPathOnly( Prj().GetProjectFullName() );
551
552 wxFileDialog dlg( this, _( "Insert Schematic" ), path, wxEmptyString,
553 KiCadSchematicFileWildcard(), wxFD_OPEN | wxFD_FILE_MUST_EXIST );
554
555 if( dlg.ShowModal() == wxID_CANCEL )
556 return false;
557
558 fullFileName = dlg.GetPath();
559
560 if( !LoadSheetFromFile( GetCurrentSheet().Last(), &GetCurrentSheet(), fullFileName ) )
561 return false;
562
563 initScreenZoom();
564 SetSheetNumberAndCount();
565
566 SyncView();
567 OnModify();
568 HardRedraw(); // Full reinit of the current screen and the display.
569
570 UpdateHierarchyNavigator();
571
572 return true;
573 }
574
575
OnAppendProject(wxCommandEvent & event)576 void SCH_EDIT_FRAME::OnAppendProject( wxCommandEvent& event )
577 {
578 if( GetScreen() && GetScreen()->IsModified() )
579 {
580 wxString msg = _( "This operation cannot be undone.\n\n"
581 "Do you want to save the current document before proceeding?" );
582
583 if( IsOK( this, msg ) )
584 SaveProject();
585 }
586
587 AppendSchematic();
588 }
589
590
OnImportProject(wxCommandEvent & aEvent)591 void SCH_EDIT_FRAME::OnImportProject( wxCommandEvent& aEvent )
592 {
593 if( !AskToSaveChanges() )
594 return;
595
596 // Set the project location if none is set or if we are running in standalone mode
597 bool setProject = Prj().GetProjectFullName().IsEmpty() || Kiface().IsSingle();
598 wxString path = wxPathOnly( Prj().GetProjectFullName() );
599
600 std::list<std::pair<const wxString, const SCH_IO_MGR::SCH_FILE_T>> loaders;
601
602 // Import Altium schematic files.
603 loaders.emplace_back( AltiumSchematicFileWildcard(), SCH_IO_MGR::SCH_ALTIUM );
604
605 // Import CADSTAR Schematic Archive files.
606 loaders.emplace_back( CadstarSchematicArchiveFileWildcard(), SCH_IO_MGR::SCH_CADSTAR_ARCHIVE );
607
608 // Import Eagle schematic files.
609 loaders.emplace_back( EagleSchematicFileWildcard(), SCH_IO_MGR::SCH_EAGLE );
610
611 wxString fileFilters;
612 wxString allWildcards;
613
614 for( std::pair<const wxString, const SCH_IO_MGR::SCH_FILE_T>& loader : loaders )
615 {
616 if( !fileFilters.IsEmpty() )
617 fileFilters += wxChar( '|' );
618
619 fileFilters += wxGetTranslation( loader.first );
620
621 SCH_PLUGIN::SCH_PLUGIN_RELEASER plugin( SCH_IO_MGR::FindPlugin( loader.second ) );
622 wxCHECK( plugin, /*void*/ );
623 allWildcards += "*." + formatWildcardExt( plugin->GetFileExtension() ) + ";";
624 }
625
626 fileFilters = _( "All supported formats|" ) + allWildcards + "|" + fileFilters;
627
628 wxFileDialog dlg( this, _( "Import Schematic" ), path, wxEmptyString, fileFilters,
629 wxFD_OPEN | wxFD_FILE_MUST_EXIST ); // TODO
630
631 if( dlg.ShowModal() == wxID_CANCEL )
632 return;
633
634 if( setProject )
635 {
636 Schematic().SetProject( nullptr );
637 GetSettingsManager()->UnloadProject( &Prj(), false );
638
639 Schematic().Reset();
640
641 wxFileName projectFn( dlg.GetPath() );
642 projectFn.SetExt( ProjectFileExtension );
643 GetSettingsManager()->LoadProject( projectFn.GetFullPath() );
644
645 Schematic().SetProject( &Prj() );
646 }
647
648 wxFileName fn = dlg.GetPath();
649
650 SCH_IO_MGR::SCH_FILE_T pluginType = SCH_IO_MGR::SCH_FILE_T::SCH_FILE_UNKNOWN;
651
652 for( std::pair<const wxString, const SCH_IO_MGR::SCH_FILE_T>& loader : loaders )
653 {
654 if( fn.GetExt().CmpNoCase( SCH_IO_MGR::GetFileExtension( loader.second ) ) == 0 )
655 {
656 pluginType = loader.second;
657 break;
658 }
659 }
660
661 if( pluginType == SCH_IO_MGR::SCH_FILE_T::SCH_FILE_UNKNOWN )
662 {
663 wxLogError( _( "Unexpected file extension: '%s'." ), fn.GetExt() );
664 return;
665 }
666
667 m_toolManager->GetTool<EE_SELECTION_TOOL>()->ClearSelection();
668
669 importFile( dlg.GetPath(), pluginType );
670
671 RefreshCanvas();
672 }
673
674
saveSchematicFile(SCH_SHEET * aSheet,const wxString & aSavePath)675 bool SCH_EDIT_FRAME::saveSchematicFile( SCH_SHEET* aSheet, const wxString& aSavePath )
676 {
677 wxString msg;
678 wxFileName schematicFileName;
679 wxFileName oldFileName;
680 bool success;
681
682 SCH_SCREEN* screen = aSheet->GetScreen();
683
684 wxCHECK( screen, false );
685
686 // Construct the name of the file to be saved
687 schematicFileName = Prj().AbsolutePath( aSavePath );
688 oldFileName = schematicFileName;
689
690 // Write through symlinks, don't replace them
691 WX_FILENAME::ResolvePossibleSymlinks( schematicFileName );
692
693 if( !IsWritable( schematicFileName ) )
694 return false;
695
696 wxFileName tempFile( schematicFileName );
697 tempFile.SetName( wxT( "." ) + tempFile.GetName() );
698 tempFile.SetExt( tempFile.GetExt() + wxT( "$" ) );
699
700 // Save
701 wxLogTrace( traceAutoSave, "Saving file " + schematicFileName.GetFullPath() );
702
703 SCH_IO_MGR::SCH_FILE_T pluginType = SCH_IO_MGR::GuessPluginTypeFromSchPath(
704 schematicFileName.GetFullPath() );
705 SCH_PLUGIN::SCH_PLUGIN_RELEASER pi( SCH_IO_MGR::FindPlugin( pluginType ) );
706
707 try
708 {
709 pi->Save( tempFile.GetFullPath(), aSheet, &Schematic() );
710 success = true;
711 }
712 catch( const IO_ERROR& ioe )
713 {
714 msg.Printf( _( "Error saving schematic file '%s'.\n%s" ),
715 schematicFileName.GetFullPath(),
716 ioe.What() );
717 DisplayError( this, msg );
718
719 msg.Printf( _( "Failed to create temporary file '%s'." ),
720 tempFile.GetFullPath() );
721 SetMsgPanel( wxEmptyString, msg );
722
723 // In case we started a file but didn't fully write it, clean up
724 wxRemoveFile( tempFile.GetFullPath() );
725
726 success = false;
727 }
728
729 if( success )
730 {
731 // Replace the original with the temporary file we just wrote
732 success = wxRenameFile( tempFile.GetFullPath(), schematicFileName.GetFullPath() );
733
734 if( !success )
735 {
736 msg.Printf( _( "Error saving schematic file '%s'.\n"
737 "Failed to rename temporary file '%s'." ),
738 schematicFileName.GetFullPath(),
739 tempFile.GetFullPath() );
740 DisplayError( this, msg );
741
742 msg.Printf( _( "Failed to rename temporary file '%s'." ),
743 tempFile.GetFullPath() );
744 SetMsgPanel( wxEmptyString, msg );
745 }
746 }
747
748 if( success )
749 {
750 // Delete auto save file.
751 wxFileName autoSaveFileName = schematicFileName;
752 autoSaveFileName.SetName( GetAutoSaveFilePrefix() + schematicFileName.GetName() );
753
754 if( autoSaveFileName.FileExists() )
755 {
756 wxLogTrace( traceAutoSave,
757 wxT( "Removing auto save file <" ) + autoSaveFileName.GetFullPath() +
758 wxT( ">" ) );
759
760 wxRemoveFile( autoSaveFileName.GetFullPath() );
761 }
762
763 screen->SetContentModified( false );
764
765 msg.Printf( _( "File '%s' saved." ), screen->GetFileName() );
766 SetStatusText( msg, 0 );
767 }
768 else
769 {
770 DisplayError( this, _( "File write operation failed." ) );
771 }
772
773 return success;
774 }
775
776
SaveProject(bool aSaveAs)777 bool SCH_EDIT_FRAME::SaveProject( bool aSaveAs )
778 {
779 wxString msg;
780 SCH_SCREEN* screen;
781 SCH_SCREENS screens( Schematic().Root() );
782 bool saveCopyAs = aSaveAs && !Kiface().IsSingle();
783 bool success = true;
784 bool updateFileType = false;
785 bool createNewProject = false;
786
787 // I want to see it in the debugger, show me the string! Can't do that with wxFileName.
788 wxString fileName = Prj().AbsolutePath( Schematic().Root().GetFileName() );
789 wxFileName fn = fileName;
790
791 // Path to save each screen to: will be the stored filename by default, but is overwritten by
792 // a Save As Copy operation.
793 std::unordered_map<SCH_SCREEN*, wxString> filenameMap;
794
795 // Handle "Save As" and saving a new project/schematic for the first time in standalone
796 if( Prj().IsNullProject() || aSaveAs )
797 {
798 // Null project should only be possible in standalone mode.
799 wxCHECK( Kiface().IsSingle() || aSaveAs, false );
800
801 wxFileName newFileName;
802 wxFileName savePath( Prj().GetProjectFullName() );
803
804 if( !savePath.IsOk() || !savePath.IsDirWritable() )
805 {
806 savePath = GetMruPath();
807
808 if( !savePath.IsOk() || !savePath.IsDirWritable() )
809 savePath = PATHS::GetDefaultUserProjectsPath();
810 }
811
812 if( savePath.HasExt() )
813 savePath.SetExt( KiCadSchematicFileExtension );
814 else
815 savePath.SetName( wxEmptyString );
816
817 wxFileDialog dlg( this, _( "Schematic Files" ), savePath.GetPath(),
818 savePath.GetFullName(), KiCadSchematicFileWildcard(),
819 wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
820
821 if( Kiface().IsSingle() || aSaveAs )
822 {
823 // Add a "Create a project" checkbox in standalone mode and one isn't loaded
824 dlg.SetExtraControlCreator( &CREATE_PROJECT_CHECKBOX::Create );
825 }
826
827 if( dlg.ShowModal() == wxID_CANCEL )
828 return false;
829
830 newFileName = dlg.GetPath();
831 newFileName.SetExt( KiCadSchematicFileExtension );
832
833 if( ( !newFileName.DirExists() && !newFileName.Mkdir() ) ||
834 !newFileName.IsDirWritable() )
835 {
836 msg.Printf( _( "Folder '%s' could not be created.\n\n"
837 "Make sure you have write permissions and try again." ),
838 newFileName.GetPath() );
839
840 wxMessageDialog dlgBadPath( this, msg, _( "Error" ),
841 wxOK | wxICON_EXCLAMATION | wxCENTER );
842
843 dlgBadPath.ShowModal();
844 return false;
845 }
846
847 if( wxWindow* ec = dlg.GetExtraControl() )
848 createNewProject = static_cast<CREATE_PROJECT_CHECKBOX*>( ec )->GetValue();
849
850 if( !saveCopyAs )
851 {
852 Schematic().Root().SetFileName( newFileName.GetFullName() );
853 Schematic().RootScreen()->SetFileName( newFileName.GetFullPath() );
854 }
855 else
856 {
857 filenameMap[Schematic().RootScreen()] = newFileName.GetFullPath();
858 }
859
860 // Set the base path to all new sheets.
861 for( size_t i = 0; i < screens.GetCount(); i++ )
862 {
863 screen = screens.GetScreen( i );
864
865 wxCHECK2( screen, continue );
866
867 // The root screen file name has already been set.
868 if( screen == Schematic().RootScreen() )
869 continue;
870
871 wxFileName tmp = screen->GetFileName();
872
873 // Assume existing sheet files are being reused and do not save them to the new
874 // path. Maybe in the future, add a user option to copy schematic files to the
875 // new project path.
876 if( tmp.FileExists() )
877 continue;
878
879 if( tmp.GetPath().IsEmpty() )
880 {
881 tmp.SetPath( newFileName.GetPath() );
882 }
883 else if( tmp.GetPath() == fn.GetPath() )
884 {
885 tmp.SetPath( newFileName.GetPath() );
886 }
887 else if( tmp.GetPath().StartsWith( fn.GetPath() ) )
888 {
889 // NOTE: this hasn't been tested because the sheet properties dialog no longer
890 // allows adding a path specifier in the file name field.
891 wxString newPath = newFileName.GetPath();
892 newPath += tmp.GetPath().Right( fn.GetPath().Length() );
893 tmp.SetPath( newPath );
894 }
895
896 wxLogTrace( tracePathsAndFiles,
897 wxT( "Moving schematic from '%s' to '%s'." ),
898 screen->GetFileName(),
899 tmp.GetFullPath() );
900
901 if( !tmp.DirExists() && !tmp.Mkdir() )
902 {
903 msg.Printf( _( "Folder '%s' could not be created.\n\n"
904 "Make sure you have write permissions and try again." ),
905 newFileName.GetPath() );
906
907 wxMessageDialog dlgBadFilePath( this, msg, _( "Error" ),
908 wxOK | wxICON_EXCLAMATION | wxCENTER );
909
910 dlgBadFilePath.ShowModal();
911 return false;
912 }
913
914 if( saveCopyAs )
915 filenameMap[screen] = tmp.GetFullPath();
916 else
917 screen->SetFileName( tmp.GetFullPath() );
918 }
919
920 // Attempt to make sheet file name paths relative to the new root schematic path.
921 SCH_SHEET_LIST sheets = Schematic().GetSheets();
922
923 for( SCH_SHEET_PATH& sheet : sheets )
924 {
925 if( sheet.Last()->IsRootSheet() )
926 continue;
927
928 sheet.MakeFilePathRelativeToParentSheet();
929 }
930 }
931
932 if( filenameMap.empty() || !saveCopyAs )
933 {
934 for( size_t i = 0; i < screens.GetCount(); i++ )
935 filenameMap[screens.GetScreen( i )] = screens.GetScreen( i )->GetFileName();
936 }
937
938 // Warn user on potential file overwrite. This can happen on shared sheets.
939 wxArrayString overwrittenFiles;
940
941 for( size_t i = 0; i < screens.GetCount(); i++ )
942 {
943 screen = screens.GetScreen( i );
944
945 wxCHECK2( screen, continue );
946
947 // Convert legacy schematics file name extensions for the new format.
948 wxFileName tmpFn = filenameMap[screen];
949
950 if( !tmpFn.IsOk() )
951 continue;
952
953 if( tmpFn.GetExt() == KiCadSchematicFileExtension )
954 continue;
955
956 tmpFn.SetExt( KiCadSchematicFileExtension );
957
958 if( tmpFn.FileExists() )
959 overwrittenFiles.Add( tmpFn.GetFullPath() );
960 }
961
962 if( !overwrittenFiles.IsEmpty() )
963 {
964 for( const wxString& overwrittenFile : overwrittenFiles )
965 {
966 if( msg.IsEmpty() )
967 msg = overwrittenFile;
968 else
969 msg += "\n" + overwrittenFile;
970 }
971
972 wxRichMessageDialog dlg( this, _( "Saving will overwrite existing files." ),
973 _( "Save Warning" ),
974 wxOK | wxCANCEL | wxCANCEL_DEFAULT | wxCENTER |
975 wxICON_EXCLAMATION );
976 dlg.ShowDetailedText( _( "The following files will be overwritten:\n\n" ) + msg );
977 dlg.SetOKCancelLabels( wxMessageDialog::ButtonLabel( _( "Overwrite Files" ) ),
978 wxMessageDialog::ButtonLabel( _( "Abort Project Save" ) ) );
979
980 if( dlg.ShowModal() == wxID_CANCEL )
981 return false;
982 }
983
984 screens.BuildClientSheetPathList();
985
986 for( size_t i = 0; i < screens.GetCount(); i++ )
987 {
988 screen = screens.GetScreen( i );
989
990 wxCHECK2( screen, continue );
991
992 // Convert legacy schematics file name extensions for the new format.
993 wxFileName tmpFn = filenameMap[screen];
994
995 if( tmpFn.IsOk() && tmpFn.GetExt() != KiCadSchematicFileExtension )
996 {
997 updateFileType = true;
998 tmpFn.SetExt( KiCadSchematicFileExtension );
999
1000 for( auto item : screen->Items().OfType( SCH_SHEET_T ) )
1001 {
1002 SCH_SHEET* sheet = static_cast<SCH_SHEET*>( item );
1003 wxFileName sheetFileName = sheet->GetFileName();
1004
1005 if( !sheetFileName.IsOk() || sheetFileName.GetExt() == KiCadSchematicFileExtension )
1006 continue;
1007
1008 sheetFileName.SetExt( KiCadSchematicFileExtension );
1009 sheet->SetFileName( sheetFileName.GetFullPath() );
1010 UpdateItem( sheet );
1011 }
1012
1013 filenameMap[screen] = tmpFn.GetFullPath();
1014
1015 if( !saveCopyAs )
1016 screen->SetFileName( tmpFn.GetFullPath() );
1017 }
1018
1019 std::vector<SCH_SHEET_PATH>& sheets = screen->GetClientSheetPaths();
1020
1021 if( sheets.size() == 1 )
1022 screen->SetVirtualPageNumber( 1 );
1023 else
1024 screen->SetVirtualPageNumber( 0 ); // multiple uses; no way to store the real sheet #
1025
1026 // This is a new schematic file so make sure it has a unique ID.
1027 if( !saveCopyAs && tmpFn.GetFullPath() != screen->GetFileName() )
1028 screen->AssignNewUuid();
1029
1030 success &= saveSchematicFile( screens.GetSheet( i ), tmpFn.GetFullPath() );
1031 }
1032
1033 // One or more of the modified sheets did not save correctly so update the auto save file.
1034 if( !aSaveAs && !success )
1035 success &= updateAutoSaveFile();
1036
1037 if( aSaveAs && success )
1038 LockFile( Schematic().RootScreen()->GetFileName() );
1039
1040 if( updateFileType )
1041 UpdateFileHistory( Schematic().RootScreen()->GetFileName() );
1042
1043 // Save the sheet name map to the project file
1044 std::vector<FILE_INFO_PAIR>& sheets = Prj().GetProjectFile().GetSheets();
1045 sheets.clear();
1046
1047 for( SCH_SHEET_PATH& sheetPath : Schematic().GetSheets() )
1048 {
1049 SCH_SHEET* sheet = sheetPath.Last();
1050
1051 wxCHECK2( sheet, continue );
1052
1053 // Use the schematic UUID for the root sheet.
1054 if( sheet->IsRootSheet() )
1055 {
1056 screen = sheet->GetScreen();
1057
1058 wxCHECK2( screen, continue );
1059
1060 sheets.emplace_back( std::make_pair( screen->GetUuid(), sheet->GetName() ) );
1061 }
1062 else
1063 {
1064 sheets.emplace_back( std::make_pair( sheet->m_Uuid, sheet->GetName() ) );
1065 }
1066 }
1067
1068 wxASSERT( filenameMap.count( Schematic().RootScreen() ) );
1069 wxFileName projectPath( filenameMap.at( Schematic().RootScreen() ) );
1070 projectPath.SetExt( ProjectFileExtension );
1071
1072 if( Prj().IsNullProject() || ( aSaveAs && !saveCopyAs ) )
1073 {
1074 Prj().SetReadOnly( !createNewProject );
1075 GetSettingsManager()->SaveProjectAs( projectPath.GetFullPath() );
1076 }
1077 else if( saveCopyAs && createNewProject )
1078 {
1079 GetSettingsManager()->SaveProjectCopy( projectPath.GetFullPath() );
1080 }
1081 else
1082 {
1083 GetSettingsManager()->SaveProject();
1084 }
1085
1086 if( !Kiface().IsSingle() )
1087 {
1088 WX_STRING_REPORTER backupReporter( &msg );
1089
1090 if( !GetSettingsManager()->TriggerBackupIfNeeded( backupReporter ) )
1091 SetStatusText( msg, 0 );
1092 }
1093
1094 UpdateTitle();
1095
1096 if( m_infoBar->GetMessageType() == WX_INFOBAR::MESSAGE_TYPE::OUTDATED_SAVE )
1097 m_infoBar->Dismiss();
1098
1099 return success;
1100 }
1101
1102
doAutoSave()1103 bool SCH_EDIT_FRAME::doAutoSave()
1104 {
1105 wxFileName tmpFileName = Schematic().Root().GetFileName();
1106 wxFileName fn = tmpFileName;
1107 wxFileName tmp;
1108 SCH_SCREENS screens( Schematic().Root() );
1109
1110 // Don't run autosave if content has not been modified
1111 if( !IsContentModified() )
1112 return true;
1113
1114 bool autoSaveOk = true;
1115
1116 if( fn.GetPath().IsEmpty() )
1117 tmp.AssignDir( Prj().GetProjectPath() );
1118 else
1119 tmp.AssignDir( fn.GetPath() );
1120
1121 if( !tmp.IsOk() )
1122 return false;
1123
1124 if( !IsWritable( tmp ) )
1125 return false;
1126
1127 wxString title = GetTitle(); // Save frame title, that can be modified by the save process
1128
1129 for( size_t i = 0; i < screens.GetCount(); i++ )
1130 {
1131 // Only create auto save files for the schematics that have been modified.
1132 if( !screens.GetScreen( i )->IsContentModified() )
1133 continue;
1134
1135 tmpFileName = fn = screens.GetScreen( i )->GetFileName();
1136
1137 // Auto save file name is the normal file name prefixed with GetAutoSavePrefix().
1138 fn.SetName( GetAutoSaveFilePrefix() + fn.GetName() );
1139
1140 if( saveSchematicFile( screens.GetSheet( i ), fn.GetFullPath() ) )
1141 screens.GetScreen( i )->SetContentModified();
1142 else
1143 autoSaveOk = false;
1144 }
1145
1146 if( autoSaveOk && updateAutoSaveFile() )
1147 {
1148 m_autoSaveState = false;
1149
1150 if( !Kiface().IsSingle() &&
1151 GetSettingsManager()->GetCommonSettings()->m_Backup.backup_on_autosave )
1152 {
1153 GetSettingsManager()->TriggerBackupIfNeeded( NULL_REPORTER::GetInstance() );
1154 }
1155 }
1156
1157 SetTitle( title );
1158
1159 return autoSaveOk;
1160 }
1161
1162
importFile(const wxString & aFileName,int aFileType)1163 bool SCH_EDIT_FRAME::importFile( const wxString& aFileName, int aFileType )
1164 {
1165 wxFileName filename( aFileName );
1166 wxFileName newfilename;
1167 SCH_SHEET_LIST sheetList = Schematic().GetSheets();
1168 SCH_IO_MGR::SCH_FILE_T fileType = (SCH_IO_MGR::SCH_FILE_T) aFileType;
1169
1170 switch( fileType )
1171 {
1172 case SCH_IO_MGR::SCH_ALTIUM:
1173 case SCH_IO_MGR::SCH_CADSTAR_ARCHIVE:
1174 case SCH_IO_MGR::SCH_EAGLE:
1175 // We insist on caller sending us an absolute path, if it does not, we say it's a bug.
1176 wxASSERT_MSG( filename.IsAbsolute(), wxT( "Import schematic: path is not absolute!" ) );
1177
1178 if( !LockFile( aFileName ) )
1179 {
1180 wxString msg;
1181 msg.Printf( _( "Schematic '%s' is already open." ), filename.GetFullName() );
1182
1183 if( !OverrideLock( this, msg ) )
1184 return false;
1185 }
1186
1187 try
1188 {
1189 SCH_PLUGIN::SCH_PLUGIN_RELEASER pi( SCH_IO_MGR::FindPlugin( fileType ) );
1190 DIALOG_HTML_REPORTER errorReporter( this );
1191 WX_PROGRESS_REPORTER progressReporter( this, _( "Importing Schematic" ), 1 );
1192
1193 pi->SetReporter( errorReporter.m_Reporter );
1194 pi->SetProgressReporter( &progressReporter );
1195 Schematic().SetRoot( pi->Load( aFileName, &Schematic() ) );
1196
1197 if( errorReporter.m_Reporter->HasMessage() )
1198 {
1199 errorReporter.m_Reporter->Flush(); // Build HTML messages
1200 errorReporter.ShowModal();
1201 }
1202
1203 // Non-KiCad schematics do not use a drawing-sheet (or if they do, it works differently
1204 // to KiCad), so set it to an empty one
1205 DS_DATA_MODEL& drawingSheet = DS_DATA_MODEL::GetTheInstance();
1206 drawingSheet.SetEmptyLayout();
1207
1208 BASE_SCREEN::m_DrawingSheetFileName = "empty.kicad_wks";
1209 wxFileName layoutfn( Prj().GetProjectPath(), BASE_SCREEN::m_DrawingSheetFileName );
1210 wxFFile layoutfile;
1211
1212 if( layoutfile.Open( layoutfn.GetFullPath(), "wb" ) )
1213 {
1214 layoutfile.Write( DS_DATA_MODEL::EmptyLayout() );
1215 layoutfile.Close();
1216 }
1217
1218 newfilename.SetPath( Prj().GetProjectPath() );
1219 newfilename.SetName( Prj().GetProjectName() );
1220 newfilename.SetExt( KiCadSchematicFileExtension );
1221
1222 SetScreen( GetCurrentSheet().LastScreen() );
1223
1224 Schematic().Root().SetFileName( newfilename.GetFullPath() );
1225 GetScreen()->SetFileName( newfilename.GetFullPath() );
1226 GetScreen()->SetContentModified();
1227
1228 // Only fix junctions for CADSTAR importer for now as it may cause issues with
1229 // other importers
1230 if( fileType == SCH_IO_MGR::SCH_CADSTAR_ARCHIVE )
1231 {
1232 FixupJunctions();
1233 }
1234
1235 RecalculateConnections( GLOBAL_CLEANUP );
1236
1237 // Only perform the dangling end test on root sheet.
1238 GetScreen()->TestDanglingEnds();
1239
1240 ClearUndoRedoList();
1241
1242 initScreenZoom();
1243 SetSheetNumberAndCount();
1244 SyncView();
1245
1246 UpdateHierarchyNavigator();
1247 UpdateTitle();
1248 }
1249 catch( const IO_ERROR& ioe )
1250 {
1251 // Do not leave g_RootSheet == NULL because it is expected to be
1252 // a valid sheet. Therefore create a dummy empty root sheet and screen.
1253 CreateScreens();
1254 m_toolManager->RunAction( ACTIONS::zoomFitScreen, true );
1255
1256 wxString msg = wxString::Format( _( "Error loading schematic '%s'." ), aFileName );
1257 DisplayErrorMessage( this, msg, ioe.What() );
1258
1259 msg.Printf( _( "Failed to load '%s'." ), aFileName );
1260 SetMsgPanel( wxEmptyString, msg );
1261
1262 return false;
1263 }
1264
1265 return true;
1266
1267 default:
1268 return false;
1269 }
1270 }
1271
1272
AskToSaveChanges()1273 bool SCH_EDIT_FRAME::AskToSaveChanges()
1274 {
1275 SCH_SCREENS screenList( Schematic().Root() );
1276
1277 // Save any currently open and modified project files.
1278 for( SCH_SCREEN* screen = screenList.GetFirst(); screen; screen = screenList.GetNext() )
1279 {
1280 SIM_PLOT_FRAME* simFrame = (SIM_PLOT_FRAME*) Kiway().Player( FRAME_SIMULATOR, false );
1281
1282 // Simulator must be closed before loading another schematic, otherwise it may crash.
1283 // If there are any changes in the simulator the user will be prompted to save them.
1284 if( simFrame && !simFrame->Close() )
1285 return false;
1286
1287 if( screen->IsContentModified() )
1288 {
1289 if( !HandleUnsavedChanges( this, _( "The current schematic has been modified. "
1290 "Save changes?" ),
1291 [&]() -> bool
1292 {
1293 return SaveProject();
1294 } ) )
1295 {
1296 return false;
1297 }
1298 }
1299 }
1300
1301 return true;
1302 }
1303
1304
updateAutoSaveFile()1305 bool SCH_EDIT_FRAME::updateAutoSaveFile()
1306 {
1307 wxFileName tmpFn = Prj().GetProjectFullName();
1308 wxFileName autoSaveFileName( tmpFn.GetPath(), getAutoSaveFileName() );
1309
1310 wxLogTrace( traceAutoSave, "Creating auto save file %s", autoSaveFileName.GetFullPath() );
1311
1312 wxCHECK( autoSaveFileName.IsDirWritable(), false );
1313
1314 wxFileName fn;
1315 SCH_SCREENS screens( Schematic().Root() );
1316 std::vector< wxString > autoSavedFiles;
1317
1318 for( size_t i = 0; i < screens.GetCount(); i++ )
1319 {
1320 // Only create auto save files for the schematics that have been modified.
1321 if( !screens.GetScreen( i )->IsContentModified() )
1322 continue;
1323
1324 fn = screens.GetScreen( i )->GetFileName();
1325
1326 // Auto save file name is the normal file name prefixed with GetAutoSavePrefix().
1327 fn.SetName( GetAutoSaveFilePrefix() + fn.GetName() );
1328 autoSavedFiles.emplace_back( fn.GetFullPath() );
1329 }
1330
1331 wxTextFile autoSaveFile( autoSaveFileName.GetFullPath() );
1332
1333 if( autoSaveFileName.FileExists() && !wxRemoveFile( autoSaveFileName.GetFullPath() ) )
1334 {
1335 wxLogTrace( traceAutoSave, "Error removing auto save file %s",
1336 autoSaveFileName.GetFullPath() );
1337
1338 return false;
1339 }
1340
1341 // No modified sheet files to save.
1342 if( autoSavedFiles.empty() )
1343 return true;
1344
1345 if( !autoSaveFile.Create() )
1346 return false;
1347
1348 for( const wxString& fileName : autoSavedFiles )
1349 {
1350 wxLogTrace( traceAutoSave, "Adding auto save file %s to %s",
1351 fileName, autoSaveFileName.GetName() );
1352 autoSaveFile.AddLine( fileName );
1353 }
1354
1355 if( !autoSaveFile.Write() )
1356 return false;
1357
1358 wxLogTrace( traceAutoSave, "Auto save file '%s' written", autoSaveFileName.GetFullName() );
1359
1360 return true;
1361 }
1362
1363
CheckForAutoSaveFile(const wxFileName & aFileName)1364 void SCH_EDIT_FRAME::CheckForAutoSaveFile( const wxFileName& aFileName )
1365 {
1366 wxCHECK_RET( aFileName.IsOk(), wxT( "Invalid file name!" ) );
1367
1368 wxLogTrace( traceAutoSave,
1369 wxT( "Checking for auto save file " ) + aFileName.GetFullPath() );
1370
1371 if( !aFileName.FileExists() )
1372 return;
1373
1374 wxString msg = _(
1375 "Well this is potentially embarrassing!\n"
1376 "It appears that the last time you were editing one or more of the schematic files\n"
1377 "were not saved properly. Do you wish to restore the last saved edits you made?" );
1378
1379 int response = wxMessageBox( msg, Pgm().App().GetAppDisplayName(), wxYES_NO | wxICON_QUESTION,
1380 this );
1381
1382 wxTextFile autoSaveFile( aFileName.GetFullPath() );
1383
1384 if( !autoSaveFile.Open() )
1385 {
1386 msg.Printf( _( "The file '%s` could not be opened.\n"
1387 "Manual recovery of automatically saved files is required." ),
1388 aFileName.GetFullPath() );
1389
1390 wxMessageBox( msg, Pgm().App().GetAppDisplayName(), wxOK | wxICON_EXCLAMATION, this );
1391 return;
1392 }
1393
1394 if( response == wxYES )
1395 {
1396 wxArrayString unrecoveredFiles;
1397
1398 for( wxString fn = autoSaveFile.GetFirstLine(); !autoSaveFile.Eof();
1399 fn = autoSaveFile.GetNextLine() )
1400 {
1401 wxFileName recoveredFn = fn;
1402 wxString tmp = recoveredFn.GetName();
1403
1404 // Strip "_autosave-" prefix from the auto save file name.
1405 tmp.Replace( GetAutoSaveFilePrefix(), wxT( "" ), false );
1406 recoveredFn.SetName( tmp );
1407
1408 wxFileName backupFn = recoveredFn;
1409
1410 backupFn.SetExt( backupFn.GetExt() + BackupFileSuffix );
1411
1412 wxLogTrace( traceAutoSave, wxT( "Recovering auto save file:\n"
1413 " Original file: '%s'\n"
1414 " Backup file: '%s'\n"
1415 " Auto save file: '%s'" ),
1416 recoveredFn.GetFullPath(), backupFn.GetFullPath(), fn );
1417
1418 // Attempt to back up the last schematic file before overwriting it with the auto
1419 // save file.
1420 if( !wxRenameFile( recoveredFn.GetFullPath(), backupFn.GetFullPath() ) )
1421 {
1422 unrecoveredFiles.Add( recoveredFn.GetFullPath() );
1423 }
1424 else if( !wxRenameFile( fn, recoveredFn.GetFullPath() ) )
1425 {
1426 unrecoveredFiles.Add( recoveredFn.GetFullPath() );
1427 }
1428 }
1429
1430 if( !unrecoveredFiles.IsEmpty() )
1431 {
1432 msg = _( "The following automatically saved file(s) could not be restored\n" );
1433
1434 for( size_t i = 0; i < unrecoveredFiles.GetCount(); i++ )
1435 msg += unrecoveredFiles[i] + wxT( "\n" );
1436
1437 msg += _( "Manual recovery will be required to restore the file(s) above." );
1438 wxMessageBox( msg, Pgm().App().GetAppDisplayName(), wxOK | wxICON_EXCLAMATION,
1439 this );
1440 }
1441 }
1442 else
1443 {
1444 wxArrayString unremovedFiles;
1445
1446 for( wxString fn = autoSaveFile.GetFirstLine(); !autoSaveFile.Eof();
1447 fn = autoSaveFile.GetNextLine() )
1448 {
1449 wxLogTrace( traceAutoSave, wxT( "Removing auto save file " ) + fn );
1450
1451 if( !wxRemoveFile( fn ) )
1452 {
1453 unremovedFiles.Add( fn );
1454 }
1455 }
1456
1457 if( !unremovedFiles.IsEmpty() )
1458 {
1459 msg = _( "The following automatically saved file(s) could not be removed\n" );
1460
1461 for( size_t i = 0; i < unremovedFiles.GetCount(); i++ )
1462 msg += unremovedFiles[i] + wxT( "\n" );
1463
1464 msg += _( "Manual removal will be required for the file(s) above." );
1465 wxMessageBox( msg, Pgm().App().GetAppDisplayName(), wxOK | wxICON_EXCLAMATION,
1466 this );
1467 }
1468 }
1469
1470 // Remove the auto save master file.
1471 wxLogTrace( traceAutoSave, wxT( "Removing auto save file '%s'" ), aFileName.GetFullPath() );
1472
1473 if( !wxRemoveFile( aFileName.GetFullPath() ) )
1474 {
1475 msg.Printf( _( "The automatic save master file\n"
1476 "'%s'\n"
1477 "could not be deleted." ), aFileName.GetFullPath() );
1478
1479 wxMessageDialog dlg( this, msg, Pgm().App().GetAppDisplayName(),
1480 wxOK | wxICON_EXCLAMATION | wxCENTER );
1481
1482 dlg.SetExtendedMessage(
1483 _( "This file must be manually removed or the auto save feature will be\n"
1484 "shown every time the schematic editor is launched." ) );
1485
1486 dlg.ShowModal();
1487 }
1488 }
1489
1490
getAutoSaveFileName() const1491 const wxString& SCH_EDIT_FRAME::getAutoSaveFileName() const
1492 {
1493 static wxString autoSaveFileName( wxT( "#auto_saved_files#" ) );
1494
1495 return autoSaveFileName;
1496 }
1497
1498