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