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