1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2015 Jean-Pierre Charras, jp.charras at wanadoo.fr
5  * Copyright (C) 2004-2021 KiCad Developers, see AUTHORS.txt for contributors.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, you may find one here:
19  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20  * or you may search the http://www.gnu.org website for the version 2 license,
21  * or you may write to the Free Software Foundation, Inc.,
22  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
23  */
24 
25 #include <sch_draw_panel.h>
26 #include <confirm.h>
27 #include <kiface_base.h>
28 #include <project.h>
29 #include <wildcards_and_files_ext.h>
30 #include <tool/tool_manager.h>
31 #include <sch_edit_frame.h>
32 #include <sch_plugins/legacy/sch_legacy_plugin.h>
33 #include <sch_sheet.h>
34 #include <sch_sheet_path.h>
35 #include <sch_view.h>
36 #include <sch_painter.h>
37 #include <schematic.h>
38 #include <symbol_lib_table.h>
39 #include <dialogs/dialog_sheet_properties.h>
40 #include <tool/actions.h>
41 
42 #include <wx/clipbrd.h>
43 #include <wx/dcmemory.h>
44 #include <wx/log.h>
45 
46 
CheckSheetForRecursion(SCH_SHEET * aSheet,SCH_SHEET_PATH * aHierarchy)47 bool SCH_EDIT_FRAME::CheckSheetForRecursion( SCH_SHEET* aSheet, SCH_SHEET_PATH* aHierarchy )
48 {
49     wxASSERT( aSheet && aHierarchy );
50 
51     wxString msg;
52     SCH_SHEET_LIST hierarchy = Schematic().GetSheets();  // The full schematic sheet hierarchy.
53     SCH_SHEET_LIST sheetHierarchy( aSheet );  // This is the hierarchy of the loaded file.
54 
55     wxFileName destFile = aHierarchy->LastScreen()->GetFileName();
56 
57     // SCH_SCREEN object file paths are expected to be absolute.  If this assert fires,
58     // something is seriously broken.
59     wxASSERT( destFile.IsAbsolute() );
60 
61     if( hierarchy.TestForRecursion( sheetHierarchy, destFile.GetFullPath() ) )
62     {
63         msg.Printf( _( "The sheet changes cannot be made because the destination sheet already "
64                        "has the sheet '%s' or one of its subsheets as a parent somewhere in the "
65                        "schematic hierarchy." ),
66                     destFile.GetFullPath() );
67         DisplayError( this, msg );
68         return true;
69     }
70 
71     return false;
72 }
73 
74 
checkForNoFullyDefinedLibIds(SCH_SHEET * aSheet)75 bool SCH_EDIT_FRAME::checkForNoFullyDefinedLibIds( SCH_SHEET* aSheet )
76 {
77     wxASSERT( aSheet && aSheet->GetScreen() );
78 
79     wxString msg;
80     SCH_SCREENS newScreens( aSheet );
81 
82     if( newScreens.HasNoFullyDefinedLibIds() )
83     {
84         msg.Printf( _( "The schematic '%s' has not had its symbol library links remapped "
85                        "to the symbol library table.  The project this schematic belongs to "
86                        "must first be remapped before it can be imported into the current "
87                        "project." ),
88                     aSheet->GetScreen()->GetFileName() );
89         DisplayInfoMessage( this, msg );
90         return true;
91     }
92 
93     return false;
94 }
95 
96 
InitSheet(SCH_SHEET * aSheet,const wxString & aNewFilename)97 void SCH_EDIT_FRAME::InitSheet( SCH_SHEET* aSheet, const wxString& aNewFilename )
98 {
99     aSheet->SetScreen( new SCH_SCREEN( &Schematic() ) );
100     aSheet->GetScreen()->SetContentModified();
101     aSheet->GetScreen()->SetFileName( aNewFilename );
102 }
103 
104 
LoadSheetFromFile(SCH_SHEET * aSheet,SCH_SHEET_PATH * aHierarchy,const wxString & aFileName)105 bool SCH_EDIT_FRAME::LoadSheetFromFile( SCH_SHEET* aSheet, SCH_SHEET_PATH* aHierarchy,
106                                         const wxString& aFileName )
107 {
108     wxASSERT( aSheet && aHierarchy );
109 
110     wxString    msg;
111     wxString    topLevelSheetPath;
112     wxFileName  tmp;
113     wxFileName  currentSheetFileName;
114     bool        libTableChanged = false;
115     SCH_IO_MGR::SCH_FILE_T schFileType = SCH_IO_MGR::GuessPluginTypeFromSchPath( aFileName );
116     SCH_PLUGIN::SCH_PLUGIN_RELEASER pi( SCH_IO_MGR::FindPlugin( schFileType ) );
117     std::unique_ptr< SCH_SHEET> newSheet = std::make_unique<SCH_SHEET>( &Schematic() );
118 
119     // This will cause the sheet UUID to be set to the loaded schematic UUID.  This is required
120     // to ensure all of the sheet paths in any subsheets are correctly generated.
121     const_cast<KIID&>( newSheet->m_Uuid ) = KIID( 0 );
122 
123     wxFileName fileName( aFileName );
124 
125     if( !fileName.IsAbsolute() && !fileName.MakeAbsolute() )
126     {
127         wxFAIL_MSG( wxString::Format( "Cannot make file name '%s' path absolute.", aFileName ) );
128         return false;
129     }
130 
131     wxString fullFilename = fileName.GetFullPath();
132 
133     try
134     {
135         if( aSheet->GetScreen() != nullptr )
136         {
137             newSheet.reset( pi->Load( fullFilename, &Schematic() ) );
138         }
139         else
140         {
141             newSheet->SetFileName( fullFilename );
142             pi->Load( fullFilename, &Schematic(), newSheet.get() );
143         }
144 
145         if( !pi->GetError().IsEmpty() )
146         {
147             msg = _( "The entire schematic could not be loaded.  Errors occurred attempting "
148                      "to load hierarchical sheet schematics." );
149 
150             wxMessageDialog msgDlg1( this, msg, _( "Schematic Load Error" ),
151                                      wxOK | wxCANCEL | wxCANCEL_DEFAULT |
152                                      wxCENTER | wxICON_QUESTION );
153             msgDlg1.SetOKLabel( wxMessageDialog::ButtonLabel( _( "Use partial schematic" ) ) );
154             msgDlg1.SetExtendedMessage( pi->GetError() );
155 
156             if( msgDlg1.ShowModal() == wxID_CANCEL )
157                 return false;
158         }
159     }
160     catch( const IO_ERROR& ioe )
161     {
162         msg.Printf( _( "Error loading schematic '%s'." ), fullFilename );
163         DisplayErrorMessage( this, msg, ioe.What() );
164 
165         msg.Printf( _( "Failed to load '%s'." ), fullFilename );
166         SetMsgPanel( wxEmptyString, msg );
167 
168         return false;
169     }
170 
171     tmp = fileName;
172 
173     // If the loaded schematic is in a different folder from the current project and
174     // it contains hierarchical sheets, the hierarchical sheet paths need to be updated.
175     if( fileName.GetPathWithSep() != Prj().GetProjectPath() && newSheet->CountSheets() )
176     {
177         // Give the user the option to choose relative path if possible.
178         if( tmp.MakeRelativeTo( Prj().GetProjectPath() ) )
179             topLevelSheetPath = tmp.GetPathWithSep();
180         else
181             topLevelSheetPath = fileName.GetPathWithSep();
182 
183         if( wxFileName::GetPathSeparator() == '\\' )
184             topLevelSheetPath.Replace( "\\", "/" );
185     }
186 
187     // Make sure any new sheet changes do not cause any recursion issues.
188     SCH_SHEET_LIST hierarchy = Schematic().GetSheets(); // This is the schematic sheet hierarchy.
189     SCH_SHEET_LIST sheetHierarchy( newSheet.get() );    // This is the hierarchy of the loaded file.
190 
191     if( CheckSheetForRecursion( newSheet.get(), aHierarchy )
192           || checkForNoFullyDefinedLibIds( newSheet.get() ) )
193     {
194         return false;
195     }
196 
197     // Make a valiant attempt to warn the user of all possible scenarios where there could
198     // be broken symbol library links.
199     wxArrayString    names;
200     wxArrayString    newLibNames;
201     SCH_SCREENS      newScreens( newSheet.get() );   // All screens associated with the import.
202     SCH_SCREENS      prjScreens( &Schematic().Root() );
203 
204     newScreens.GetLibNicknames( names );
205 
206     wxMessageDialog::ButtonLabel okButtonLabel( _( "Continue Load" ) );
207     wxMessageDialog::ButtonLabel cancelButtonLabel( _( "Cancel Load" ) );
208 
209     if( fileName.GetPathWithSep() == Prj().GetProjectPath()
210       && !prjScreens.HasSchematic( fullFilename ) )
211     {
212         // A schematic in the current project path that isn't part of the current project.
213         // It's possible the user copied this schematic from another project so the library
214         // links may not be available.  Even this is check is no guarantee that all symbol
215         // library links are valid but it's better than nothing.
216         for( const wxString& name : names )
217         {
218             if( !Prj().SchSymbolLibTable()->HasLibrary( name ) )
219                 newLibNames.Add( name );
220         }
221 
222         if( !newLibNames.IsEmpty() )
223         {
224             msg = _( "There are library names in the loaded schematic that are missing "
225                      "from the project library table.  This may result in broken symbol "
226                      "library links for the loaded schematic.  Do you wish to continue?" );
227             wxMessageDialog msgDlg3( this, msg, _( "Continue Load Schematic" ),
228                                      wxOK | wxCANCEL | wxCANCEL_DEFAULT |
229                                      wxCENTER | wxICON_QUESTION );
230             msgDlg3.SetOKCancelLabels( okButtonLabel, cancelButtonLabel );
231 
232             if( msgDlg3.ShowModal() == wxID_CANCEL )
233                 return false;
234         }
235     }
236     else if( fileName.GetPathWithSep() != Prj().GetProjectPath() )
237     {
238         // A schematic loaded from a path other than the current project path.
239 
240         // If there are symbol libraries in the imported schematic that are not in the
241         // symbol library table of this project, there could be a lot of broken symbol
242         // library links.  Attempt to add the missing libraries to the project symbol
243         // library table.
244         wxArrayString    duplicateLibNames;
245 
246         for( const wxString& name : names )
247         {
248             if( !Prj().SchSymbolLibTable()->HasLibrary( name ) )
249                 newLibNames.Add( name );
250             else
251                 duplicateLibNames.Add( name );
252         }
253 
254         SYMBOL_LIB_TABLE table;
255         wxFileName symLibTableFn( fileName.GetPath(),
256                                   SYMBOL_LIB_TABLE::GetSymbolLibTableFileName() );
257 
258         // If there are any new or duplicate libraries, check to see if it's possible that
259         // there could be any missing libraries that would cause broken symbol library links.
260         if( !newLibNames.IsEmpty() || !duplicateLibNames.IsEmpty() )
261         {
262             if( !symLibTableFn.Exists() || !symLibTableFn.IsFileReadable() )
263             {
264                 msg.Printf( _( "The project library table '%s' does not exist or cannot "
265                                "be read.  This may result in broken symbol links for the "
266                                "schematic.  Do you wish to continue?" ),
267                             fileName.GetFullPath() );
268                 wxMessageDialog msgDlg4( this, msg, _( "Continue Load Schematic" ),
269                                          wxOK | wxCANCEL | wxCANCEL_DEFAULT |
270                                          wxCENTER | wxICON_QUESTION );
271                 msgDlg4.SetOKCancelLabels( okButtonLabel, cancelButtonLabel );
272 
273                 if( msgDlg4.ShowModal() == wxID_CANCEL )
274                     return false;
275             }
276             else
277             {
278                 try
279                 {
280                     table.Load( symLibTableFn.GetFullPath() );
281                 }
282                 catch( const IO_ERROR& ioe )
283                 {
284                     msg.Printf( _( "Error loading the symbol library table '%s'." ),
285                                 symLibTableFn.GetFullPath() );
286                     DisplayErrorMessage( nullptr, msg, ioe.What() );
287                     return false;
288                 }
289             }
290         }
291 
292         // Check to see if any of the symbol libraries found in the appended schematic do
293         // not exist in the current project are missing from the appended project symbol
294         // library table.
295         if( !newLibNames.IsEmpty() )
296         {
297             bool missingLibNames = table.IsEmpty();
298 
299             if( !missingLibNames )
300             {
301                 for( const wxString& newLibName : newLibNames )
302                 {
303                     if( !table.HasLibrary( newLibName ) )
304                     {
305                         missingLibNames = true;
306                         break;
307                     }
308                 }
309             }
310 
311             if( missingLibNames )
312             {
313                 msg = _( "There are library names in the loaded schematic that are missing "
314                          "from the loaded schematic project library table.  This may result "
315                          "in broken symbol library links for the schematic.  "
316                          "Do you wish to continue?" );
317                 wxMessageDialog msgDlg5( this, msg, _( "Continue Load Schematic" ),
318                                          wxOK | wxCANCEL | wxCANCEL_DEFAULT |
319                                          wxCENTER | wxICON_QUESTION );
320                 msgDlg5.SetOKCancelLabels( okButtonLabel, cancelButtonLabel );
321 
322                 if( msgDlg5.ShowModal() == wxID_CANCEL )
323                     return false;
324             }
325         }
326 
327         // The library name already exists in the current project.  Check to see if the
328         // duplicate name is the same library in the current project.  If it's not, it's
329         // most likely that the symbol library links will be broken.
330         if( !duplicateLibNames.IsEmpty() && !table.IsEmpty() )
331         {
332             bool libNameConflict = false;
333 
334             for( const wxString& duplicateLibName : duplicateLibNames )
335             {
336                 const SYMBOL_LIB_TABLE_ROW* thisRow = nullptr;
337                 const SYMBOL_LIB_TABLE_ROW* otherRow = nullptr;
338 
339                 if( Prj().SchSymbolLibTable()->HasLibrary( duplicateLibName ) )
340                     thisRow = Prj().SchSymbolLibTable()->FindRow( duplicateLibName );
341 
342                 if( table.HasLibrary( duplicateLibName ) )
343                     otherRow = table.FindRow( duplicateLibName );
344 
345                 // It's in the global library table so there is no conflict.
346                 if( thisRow && !otherRow )
347                     continue;
348 
349                 if( !thisRow || !otherRow )
350                     continue;
351 
352                 wxFileName otherUriFileName;
353                 wxString thisURI = thisRow->GetFullURI( true );
354                 wxString otherURI = otherRow->GetFullURI( false);
355 
356                 if( otherURI.Contains( "${KIPRJMOD}" ) || otherURI.Contains( "$(KIPRJMOD)" ) )
357                 {
358                     // Cannot use relative paths here, "${KIPRJMOD}../path-to-cache-lib" does
359                     // not expand to a valid symbol library path.
360                     otherUriFileName.SetPath( fileName.GetPath() );
361                     otherUriFileName.SetFullName( otherURI.AfterLast( '}' ) );
362                     otherURI = otherUriFileName.GetFullPath();
363                 }
364 
365                 if( thisURI != otherURI )
366                 {
367                     libNameConflict = true;
368                     break;
369                 }
370             }
371 
372             if( libNameConflict )
373             {
374                 msg = _( "A duplicate library name that references a different library exists "
375                          "in the current library table.  This conflict cannot be resolved and "
376                          "may result in broken symbol library links for the schematic.  "
377                          "Do you wish to continue?" );
378                 wxMessageDialog msgDlg6( this, msg, _( "Continue Load Schematic" ),
379                                          wxOK | wxCANCEL | wxCANCEL_DEFAULT |
380                                          wxCENTER | wxICON_QUESTION );
381                 msgDlg6.SetOKCancelLabels( okButtonLabel, cancelButtonLabel );
382 
383                 if( msgDlg6.ShowModal() == wxID_CANCEL )
384                     return false;
385             }
386         }
387 
388         // All (most?) of the possible broken symbol library link cases are covered.  Map the
389         // new appended schematic project symbol library table entries to the current project
390         // symbol library table.
391         if( !newLibNames.IsEmpty() && !table.IsEmpty() )
392         {
393             for( const wxString& libName : newLibNames )
394             {
395                 if( !table.HasLibrary( libName )
396                   || Prj().SchSymbolLibTable()->HasLibrary( libName ) )
397                 {
398                     continue;
399                 }
400 
401                 // Don't expand environment variable because KIPRJMOD will not be correct
402                 // for a different project.
403                 wxString uri = table.GetFullURI( libName, false );
404                 wxFileName newLib;
405 
406                 if( uri.Contains( "${KIPRJMOD}" ) || uri.Contains( "$(KIPRJMOD)" ) )
407                 {
408                     // Cannot use relative paths here, "${KIPRJMOD}../path-to-cache-lib" does
409                     // not expand to a valid symbol library path.
410                     newLib.SetPath( fileName.GetPath() );
411                     newLib.SetFullName( uri.AfterLast( '}' ) );
412                     uri = newLib.GetFullPath();
413                 }
414                 else
415                 {
416                     uri = table.GetFullURI( libName );
417                 }
418 
419                 // Add the library from the imported project to the current project
420                 // symbol library table.
421                 const SYMBOL_LIB_TABLE_ROW* row = table.FindRow( libName );
422 
423                 wxCHECK( row, false );
424 
425                 SYMBOL_LIB_TABLE_ROW* newRow = new SYMBOL_LIB_TABLE_ROW( libName, uri,
426                                                                          row->GetType(),
427                                                                          row->GetOptions(),
428                                                                          row->GetDescr() );
429 
430                 Prj().SchSymbolLibTable()->InsertRow( newRow );
431                 libTableChanged = true;
432             }
433         }
434     }
435 
436     SCH_SCREEN* newScreen = newSheet->GetScreen();
437     wxCHECK_MSG( newScreen, false, "No screen defined for sheet." );
438 
439     if( libTableChanged )
440     {
441         Prj().SchSymbolLibTable()->Save( Prj().GetProjectPath() +
442                                          SYMBOL_LIB_TABLE::GetSymbolLibTableFileName() );
443     }
444 
445     // It is finally safe to add or append the imported schematic.
446     if( aSheet->GetScreen() == nullptr )
447         aSheet->SetScreen( newScreen );
448     else
449         aSheet->GetScreen()->Append( newScreen );
450 
451     SCH_SCREENS allScreens( Schematic().Root() );
452     allScreens.ReplaceDuplicateTimeStamps();
453 
454     return true;
455 }
456 
457 
EditSheetProperties(SCH_SHEET * aSheet,SCH_SHEET_PATH * aHierarchy,bool * aClearAnnotationNewItems)458 bool SCH_EDIT_FRAME::EditSheetProperties( SCH_SHEET* aSheet, SCH_SHEET_PATH* aHierarchy,
459                                           bool* aClearAnnotationNewItems )
460 {
461     if( aSheet == nullptr || aHierarchy == nullptr )
462         return false;
463 
464     // Get the new texts
465     DIALOG_SHEET_PROPERTIES dlg( this, aSheet, aClearAnnotationNewItems );
466 
467     if( dlg.ShowModal() == wxID_CANCEL )
468         return false;
469 
470     return true;
471 }
472 
473 
DrawCurrentSheetToClipboard()474 void SCH_EDIT_FRAME::DrawCurrentSheetToClipboard()
475 {
476     wxRect  DrawArea;
477     BASE_SCREEN* screen = GetScreen();
478 
479     DrawArea.SetSize( GetPageSizeIU() );
480 
481     // Calculate a reasonable dc size, in pixels, and the dc scale to fit
482     // the drawings into the dc size
483     // scale is the ratio resolution (in PPI) / internal units
484     double ppi = 300;   // Use 300 pixels per inch to create bitmap images on start
485     double inch2Iu = 1000.0 * IU_PER_MILS;
486     double scale = ppi / inch2Iu;
487 
488     wxSize dcsize = DrawArea.GetSize();
489 
490     int maxdim = std::max( dcsize.x, dcsize.y );
491 
492     // the max size in pixels of the bitmap used to build the sheet copy
493     const int maxbitmapsize = 5600;
494 
495     while( int( maxdim * scale ) > maxbitmapsize )
496     {
497         ppi = ppi / 1.5;
498         scale = ppi / inch2Iu;
499     }
500 
501     dcsize.x *= scale;
502     dcsize.y *= scale;
503 
504     // Set draw offset, zoom... to values needed to draw in the memory DC
505     // after saving initial values:
506     wxPoint tmp_startvisu = screen->m_StartVisu;
507     wxPoint old_org       = screen->m_DrawOrg;
508     screen->m_DrawOrg.x   = screen->m_DrawOrg.y = 0;
509     screen->m_StartVisu.x = screen->m_StartVisu.y = 0;
510 
511     wxMemoryDC dc;
512     wxBitmap image( dcsize );
513     dc.SelectObject( image );
514     dc.Clear();
515 
516     GRResetPenAndBrush( &dc );
517     GRForceBlackPen( false );
518     dc.SetUserScale( scale, scale );
519 
520     GetRenderSettings()->SetPrintDC( &dc );
521     // Init the color of the layer actually used to print the drawing sheet:
522     GetRenderSettings()->SetLayerColor( LAYER_DRAWINGSHEET,
523                 GetRenderSettings()->GetLayerColor( LAYER_SCHEMATIC_DRAWINGSHEET ) );
524 
525     PrintPage( GetRenderSettings() );
526 
527     {
528         wxLogNull doNotLog; // disable logging of failed clipboard actions
529 
530         if( wxTheClipboard->Open() )
531         {
532             // This data objects are held by the clipboard, so do not delete them in the app.
533             wxBitmapDataObject* clipbrd_data = new wxBitmapDataObject( image );
534             wxTheClipboard->SetData( clipbrd_data );
535             wxTheClipboard->Flush(); // Allow data to be available after closing KiCad
536             wxTheClipboard->Close();
537         }
538     }
539 
540     // Deselect Bitmap from DC in order to delete the MemoryDC
541     dc.SelectObject( wxNullBitmap );
542 
543     GRForceBlackPen( false );
544 
545     screen->m_StartVisu = tmp_startvisu;
546     screen->m_DrawOrg   = old_org;
547 }
548 
549 
AllowCaseSensitiveFileNameClashes(const wxString & aSchematicFileName)550 bool SCH_EDIT_FRAME::AllowCaseSensitiveFileNameClashes( const wxString& aSchematicFileName )
551 {
552     wxString msg;
553     SCH_SCREENS screens( Schematic().Root() );
554     wxFileName fn = aSchematicFileName;
555 
556     wxCHECK( fn.IsAbsolute(), false );
557 
558     if( eeconfig()->m_Appearance.show_sheet_filename_case_sensitivity_dialog
559       && screens.CanCauseCaseSensitivityIssue( aSchematicFileName ) )
560     {
561         msg.Printf( _( "The file name '%s' can cause issues with an existing file name\n"
562                        "already defined in the schematic on systems that support case\n"
563                        "insensitive file names.  This will cause issues if you copy this\n"
564                        "project to an operating system that supports case insensitive file\n"
565                        "names.\n\nDo you wish to continue?" ),
566                     fn.GetName() );
567 
568         wxRichMessageDialog dlg( this, msg, _( "Warning" ),
569                                  wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION );
570         dlg.ShowCheckBox( _( "Do not show this message again." ) );
571         dlg.SetYesNoLabels( wxMessageDialog::ButtonLabel( _( "Create New Sheet" ) ),
572                             wxMessageDialog::ButtonLabel( _( "Discard New Sheet" ) ) );
573 
574         if( dlg.ShowModal() == wxID_NO )
575             return false;
576 
577         eeconfig()->m_Appearance.show_sheet_filename_case_sensitivity_dialog =
578                                                             !dlg.IsCheckBoxChecked();
579     }
580 
581     return true;
582 }
583