KiCad PCB EDA Suite
Loading...
Searching...
No Matches
sch_inspection_tool.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2019-2023 CERN
5 * Copyright The 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, see <https://www.gnu.org/licenses/>.
19 */
20
21#include <tool/tool_manager.h>
24
25#include <sch_symbol.h>
26#include <id.h>
27#include <kiway.h>
28#include <kiplatform/ui.h>
29#include <confirm.h>
30#include <string_utils.h>
34#include <tools/sch_actions.h>
36#include <tools/sch_selection.h>
37#include <sim/simulator_frame.h>
38#include <sch_edit_frame.h>
39#include <symbol_edit_frame.h>
40#include <symbol_viewer_frame.h>
41#include <eda_doc.h>
42#include <sch_marker.h>
43#include <project.h>
44#include <project_sch.h>
46#include <dialogs/dialog_erc.h>
51#include <math/util.h> // for KiROUND
52
58#include <eeschema_helpers.h>
59#include <schematic.h>
61#include <local_history.h>
63#include <wx/filedlg.h>
64#include <wx/filename.h>
65
66
68 SCH_TOOL_BASE<SCH_BASE_FRAME>( "eeschema.InspectionTool" ), m_busSyntaxHelp( nullptr )
69{
70}
71
72
74{
76
77 // Add inspection actions to the selection tool menu
78 //
79 CONDITIONAL_MENU& selToolMenu = m_selectionTool->GetToolMenu().GetMenu();
80
82
85
86 return true;
87}
88
89
91{
92 SCH_TOOL_BASE::Reset( aReason );
93
94 if( aReason == SUPERMODEL_RELOAD || aReason == RESET_REASON::SHUTDOWN )
95 {
96 wxCommandEvent* evt = new wxCommandEvent( EDA_EVT_CLOSE_ERC_DIALOG, wxID_ANY );
97
98 wxQueueEvent( m_frame, evt );
99 }
100}
101
102
104{
106 return 0;
107}
108
109
111{
112 SCH_EDIT_FRAME* frame = dynamic_cast<SCH_EDIT_FRAME*>( m_frame );
113
114 wxCHECK( frame, /* void */ );
115
116 DIALOG_ERC* dlg = frame->GetErcDialog();
117
118 wxCHECK( dlg, /* void */ );
119
120 // Needed at least on Windows. Raise() is not enough
121 dlg->Show( true );
122
123 // Bring it to the top if already open. Dual monitor users need this.
124 dlg->Raise();
125
126 if( wxButton* okButton = dynamic_cast<wxButton*>( dlg->FindWindow( wxID_OK ) ) )
127 {
128 KIPLATFORM::UI::ForceFocus( okButton );
129 okButton->SetDefault();
130 }
131}
132
133
135{
136 SCH_EDIT_FRAME* frame = dynamic_cast<SCH_EDIT_FRAME*>( m_frame );
137
138 wxCHECK( frame, 0 );
139
140 DIALOG_ERC* dlg = frame->GetErcDialog();
141
142 if( dlg )
143 {
144 dlg->Show( true );
145 dlg->Raise();
146 dlg->PrevMarker();
147 }
148
149 return 0;
150}
151
152
154{
155 SCH_EDIT_FRAME* frame = dynamic_cast<SCH_EDIT_FRAME*>( m_frame );
156
157 wxCHECK( frame, 0 );
158
159 DIALOG_ERC* dlg = frame->GetErcDialog();
160
161 wxCHECK( dlg, 0 );
162
163 dlg->Show( true );
164 dlg->Raise();
165 dlg->NextMarker();
166
167 return 0;
168}
169
170
172{
173 SCH_SELECTION_TOOL* selectionTool = m_toolMgr->GetTool<SCH_SELECTION_TOOL>();
174
175 wxCHECK( selectionTool, 0 );
176
177 SCH_SELECTION& selection = selectionTool->GetSelection();
178
179 if( selection.GetSize() == 1 && selection.Front()->Type() == SCH_MARKER_T )
180 {
181 SCH_EDIT_FRAME* frame = dynamic_cast<SCH_EDIT_FRAME*>( m_frame );
182 DIALOG_ERC* dlg = frame ? frame->GetErcDialog() : nullptr;
183
184 if( dlg && dlg->IsShownOnScreen() )
185 dlg->SelectMarker( static_cast<SCH_MARKER*>( selection.Front() ) );
186 }
187
188 // Show the item info on a left click on this item
189 UpdateMessagePanel( aEvent );
190
191 return 0;
192}
193
194
196{
197 SCH_EDIT_FRAME* frame = dynamic_cast<SCH_EDIT_FRAME*>( m_frame );
198
199 wxCHECK( frame, /* void */ );
200
201 DIALOG_ERC* dlg = frame->GetErcDialog();
202
203 if( dlg )
204 {
205 if( !dlg->IsShownOnScreen() )
206 {
207 dlg->Show( true );
208 dlg->Raise();
209 }
210
211 dlg->SelectMarker( aMarker );
212 }
213}
214
215
216wxString SCH_INSPECTION_TOOL::InspectERCErrorMenuText( const std::shared_ptr<RC_ITEM>& aERCItem )
217{
218 if( aERCItem->GetErrorCode() == ERCE_BUS_TO_NET_CONFLICT )
219 {
220 return m_frame->GetRunMenuCommandDescription( SCH_ACTIONS::showBusSyntaxHelp );
221 }
222 else if( aERCItem->GetErrorCode() == ERCE_LIB_SYMBOL_MISMATCH )
223 {
224 return m_frame->GetRunMenuCommandDescription( SCH_ACTIONS::diffSymbol );
225 }
226
227 return wxEmptyString;
228}
229
230
231void SCH_INSPECTION_TOOL::InspectERCError( const std::shared_ptr<RC_ITEM>& aERCItem )
232{
233 SCH_EDIT_FRAME* frame = dynamic_cast<SCH_EDIT_FRAME*>( m_frame );
234
235 wxCHECK( frame, /* void */ );
236
237 EDA_ITEM* a = frame->ResolveItem( aERCItem->GetMainItemID() );
238
239 if( aERCItem->GetErrorCode() == ERCE_BUS_TO_NET_CONFLICT )
240 {
242 }
243 else if( aERCItem->GetErrorCode() == ERCE_LIB_SYMBOL_MISMATCH )
244 {
245 if( SCH_SYMBOL* symbol = dynamic_cast<SCH_SYMBOL*>( a ) )
246 DiffSymbol( symbol );
247 }
248}
249
250
252{
253 SCH_SELECTION_TOOL* selTool = m_toolMgr->GetTool<SCH_SELECTION_TOOL>();
254 SCH_SELECTION& selection = selTool->GetSelection();
255 SCH_MARKER* marker = nullptr;
256
257 if( selection.GetSize() == 1 && selection.Front()->Type() == SCH_MARKER_T )
258 marker = static_cast<SCH_MARKER*>( selection.Front() );
259
260 SCH_EDIT_FRAME* frame = dynamic_cast<SCH_EDIT_FRAME*>( m_frame );
261
262 wxCHECK( frame, 0 );
263
264 DIALOG_ERC* dlg = frame->GetErcDialog();
265
266 wxCHECK( dlg, 0 );
267
268 // Let the ERC dialog handle it since it owns the marker provider's cached counts and view
269 // updates. If marker is nullptr the dialog excludes whichever marker is selected in the
270 // dialog itself.
271 dlg->ExcludeMarker( marker );
272
273 return 0;
274}
275
276
277extern void CheckLibSymbol( LIB_SYMBOL* aSymbol, std::vector<wxString>& aMessages,
278 int aGridForPins, UNITS_PROVIDER* aUnitsProvider );
279
281{
282 LIB_SYMBOL* symbol = static_cast<SYMBOL_EDIT_FRAME*>( m_frame )->GetCurSymbol();
283
284 if( !symbol )
285 return 0;
286
287 std::vector<wxString> messages;
288 const int grid_size = KiROUND( getView()->GetGAL()->GetGridSize().x );
289
290 CheckLibSymbol( symbol, messages, grid_size, m_frame );
291
292 if( messages.empty() )
293 {
294 DisplayInfoMessage( m_frame, _( "No symbol issues found." ) );
295 }
296 else
297 {
298 HTML_MESSAGE_BOX dlg( m_frame, _( "Symbol Warnings" ) );
299
300 for( const wxString& single_msg : messages )
301 dlg.AddHTML_Text( single_msg );
302
303 dlg.ShowModal();
304 }
305
306 return 0;
307}
308
309
311{
312 if( m_busSyntaxHelp )
313 {
314 m_busSyntaxHelp->Raise();
315 m_busSyntaxHelp->Show( true );
316 return 0;
317 }
318
320 return 0;
321}
322
323
325{
326 SCH_EDIT_FRAME* schEditorFrame = dynamic_cast<SCH_EDIT_FRAME*>( m_frame );
327
328 wxCHECK( schEditorFrame, 0 );
329
330 SCH_SELECTION& selection = m_selectionTool->RequestSelection( { SCH_SYMBOL_T } );
331
332 if( selection.Empty() )
333 {
334 m_frame->ShowInfoBarError( _( "Select a symbol to diff against its library equivalent." ) );
335 return 0;
336 }
337
338 DiffSymbol( static_cast<SCH_SYMBOL*>( selection.Front() ) );
339 return 0;
340}
341
342
344{
345 SCH_EDIT_FRAME* schEditorFrame = dynamic_cast<SCH_EDIT_FRAME*>( m_frame );
346
347 wxCHECK( schEditorFrame, /* void */ );
348
349 DIALOG_BOOK_REPORTER* dialog = schEditorFrame->GetSymbolDiffDialog();
350
351 wxCHECK( dialog, /* void */ );
352
353 dialog->DeleteAllPages();
354 dialog->SetUserItemID( symbol->m_Uuid );
355
356 wxString symbolDesc = wxString::Format( _( "Symbol %s" ), symbol->GetField( FIELD_T::REFERENCE )->GetText() );
357 LIB_ID libId = symbol->GetLibId();
358 wxString libName = libId.GetLibNickname();
359 wxString symbolName = libId.GetLibItemName();
360
361 WX_HTML_REPORT_BOX* r = dialog->AddHTMLPage( _( "Summary" ) );
362
363 r->Report( wxS( "<h7>" ) + _( "Schematic vs library diff for:" ) + wxS( "</h7>" ) );
364 r->Report( wxS( "<ul><li>" ) + EscapeHTML( symbolDesc ) + wxS( "</li>" )
365 + wxS( "<li>" ) + _( "Library: " ) + EscapeHTML( libName ) + wxS( "</li>" )
366 + wxS( "<li>" ) + _( "Library item: " ) + EscapeHTML( symbolName )
367 + wxS( "</li></ul>" ) );
368
369 r->Report( "" );
370
372
373 if( !libs->HasLibrary( libName, false ) )
374 {
375 r->Report( _( "The library is not included in the current configuration." )
376 + wxS( "&nbsp;&nbsp;&nbsp" )
377 + wxS( "<a href='$CONFIG'>" ) + _( "Manage Symbol Libraries" ) + wxS( "</a>" ) );
378 }
379 else if( !libs->HasLibrary( libName, true ) )
380 {
381 r->Report( _( "The library is not enabled in the current configuration." )
382 + wxS( "&nbsp;&nbsp;&nbsp" )
383 + wxS( "<a href='$CONFIG'>" ) + _( "Manage Symbol Libraries" ) + wxS( "</a>" ) );
384 }
385 else
386 {
387 std::unique_ptr<LIB_SYMBOL> flattenedLibSymbol;
388 std::unique_ptr<LIB_SYMBOL> flattenedSchSymbol = symbol->GetLibSymbolRef()->Flatten();
389
390 try
391 {
392 if( LIB_SYMBOL* libAlias = libs->LoadSymbol( libName, symbolName ) )
393 flattenedLibSymbol = libAlias->Flatten();
394 }
395 catch( const IO_ERROR& )
396 {
397 }
398
399 if( !flattenedLibSymbol )
400 {
401 r->Report( wxString::Format( _( "The library no longer contains the item %s." ), symbolName ) );
402 }
403 else
404 {
405 std::vector<SCH_FIELD> fields;
406
407 for( SCH_FIELD& field : symbol->GetFields() )
408 {
409 fields.emplace_back( SCH_FIELD( flattenedLibSymbol.get(), field.GetId(), field.GetName( false ) ) );
410 fields.back().CopyText( field );
411 fields.back().SetAttributes( field );
412 fields.back().Move( -symbol->GetPosition() );
413 }
414
415 flattenedSchSymbol->SetFields( fields );
416
417 int flags = schEditorFrame->Schematic().Settings().SymbolCompareFlags();
418
419 if( flattenedSchSymbol->Compare( *flattenedLibSymbol, flags, r ) == 0 )
420 r->Report( _( "No relevant differences detected." ) );
421
422 wxPanel* panel = dialog->AddBlankPage( _( "Visual" ) );
423 SYMBOL_DIFF_WIDGET* diff = constructDiffPanel( panel );
424
425 diff->DisplayDiff( flattenedSchSymbol.release(), flattenedLibSymbol.release(),
426 symbol->GetUnit(), symbol->GetBodyStyle() );
427 }
428 }
429
430 r->Flush();
431
432 dialog->Raise();
433 dialog->Show( true );
434}
435
436
438{
439 wxBoxSizer* sizer = new wxBoxSizer( wxVERTICAL );
440
441 EDA_DRAW_PANEL_GAL::GAL_TYPE backend = m_frame->GetCanvas()->GetBackend();
442 SYMBOL_DIFF_WIDGET* diffWidget = new SYMBOL_DIFF_WIDGET( aParentPanel, backend );
443
444 sizer->Add( diffWidget, 1, wxEXPAND | wxALL, 5 );
445 aParentPanel->SetSizer( sizer );
446 aParentPanel->Layout();
447
448 return diffWidget;
449}
450
451
452namespace
453{
454void buildSchOverrides( const KICAD_DIFF::DOCUMENT_DIFF& aDiff, const KICAD_DIFF::DIFF_COLOR_THEME& aTheme,
455 std::map<KIID, KIGFX::COLOR4D>& aRefO, std::map<KIID, KIGFX::COLOR4D>& aCompO,
456 std::map<KIID, KICAD_DIFF::CATEGORY>& aCats )
457{
458 aRefO.clear();
459 aCompO.clear();
460 aCats.clear();
461
462 std::function<void( const KICAD_DIFF::ITEM_CHANGE& )> visit =
463 [&]( const KICAD_DIFF::ITEM_CHANGE& aChange )
464 {
465 if( !aChange.id.empty() )
466 {
467 const KIID& kiid = aChange.id.back();
468 aCats[kiid] = KICAD_DIFF::CategoryFor( aChange.kind );
469
470 switch( aChange.kind )
471 {
472 case KICAD_DIFF::CHANGE_KIND::ADDED: aCompO[kiid] = aTheme.added; break;
474 aRefO[kiid] = aTheme.removed;
475 aCompO[kiid] = aTheme.removed;
476 break;
478 aRefO[kiid] = aTheme.modified;
479 aCompO[kiid] = aTheme.modified;
480 break;
481 default:
482 aRefO[kiid] = aTheme.conflict;
483 aCompO[kiid] = aTheme.conflict;
484 break;
485 }
486 }
487
488 for( const KICAD_DIFF::ITEM_CHANGE& child : aChange.children )
489 visit( child );
490 };
491
492 for( const KICAD_DIFF::ITEM_CHANGE& change : aDiff.changes )
493 visit( change );
494}
495
496
497DIALOG_KICAD_DIFF::SHEET_SWITCHER makeSchSwitcher( SCHEMATIC* aRef, SCHEMATIC* aComp,
498 std::map<KIID, KIGFX::COLOR4D> aRefOverrides,
499 std::map<KIID, KIGFX::COLOR4D> aCompOverrides,
500 std::map<KIID, KICAD_DIFF::CATEGORY> aCategories,
501 const KICAD_DIFF::DIFF_COLOR_THEME& aTheme )
502{
503 auto holder = std::make_shared<std::vector<std::unique_ptr<SCH_ITEM>>>();
504
505 return [aRef, aComp, modifiedColor = aTheme.modified, removedColor = aTheme.removed,
506 refO = std::move( aRefOverrides ), compO = std::move( aCompOverrides ), cats = std::move( aCategories ),
507 holder]( WIDGET_DIFF_CANVAS& aCanvas, const KIID_PATH& aSheetPath )
508 {
509 SCH_SCREEN* compScreen = aComp->RootScreen();
510 SCH_SCREEN* refScreen = aRef->RootScreen();
511
512 if( !aSheetPath.empty() )
513 {
514 if( auto sp = aComp->Hierarchy().GetSheetPathByKIIDPath( aSheetPath, true ) )
515 compScreen = sp->LastScreen();
516
517 if( auto sp = aRef->Hierarchy().GetSheetPathByKIIDPath( aSheetPath, true ) )
518 refScreen = sp->LastScreen();
519 }
520
521 holder->clear();
522
523 if( refScreen )
524 {
525 for( const auto& [kiid, c] : refO )
526 {
527 if( c != removedColor )
528 continue;
529
530 for( SCH_ITEM* item : refScreen->Items() )
531 {
532 if( item && item->m_Uuid == kiid )
533 {
534 if( auto* clone = dynamic_cast<SCH_ITEM*>( item->Clone() ) )
535 holder->emplace_back( clone );
536
537 break;
538 }
539 }
540 }
541 }
542
543 std::vector<KIGFX::VIEW_ITEM*> extras;
544
545 for( const auto& clone : *holder )
546 extras.push_back( clone.get() );
547
548 KICAD_DIFF::ConfigureSchDiffCanvasContext( aCanvas, nullptr, aComp, modifiedColor, compO, extras, cats, nullptr,
549 compScreen );
550 };
551}
552
553
554struct DRILL_FRAME
555{
556 SCH_SHEET_PATH editorPath;
557 SCHEMATIC* compSch = nullptr;
558 wxString compFile;
559 KIID_PATH compScope;
560};
561
562
563void reloadDrillFrame( DIALOG_KICAD_DIFF& aDlg, SCHEMATIC* aRefSch, const KICAD_DIFF::DIFF_COLOR_THEME& aTheme,
564 const DRILL_FRAME& aFrame )
565{
566 KICAD_DIFF::SCH_DIFFER differ( aRefSch, aFrame.compSch, aFrame.compFile );
567 const KIID_PATH scope = aFrame.editorPath.Path();
568
569 if( !scope.empty() && !aFrame.compScope.empty() )
570 differ.SetScope( scope, aFrame.compScope );
571
572 KICAD_DIFF::DOCUMENT_DIFF diff = differ.Diff();
573
574 std::map<KIID, KIGFX::COLOR4D> refO;
575 std::map<KIID, KIGFX::COLOR4D> compO;
576 std::map<KIID, KICAD_DIFF::CATEGORY> cats;
577 buildSchOverrides( diff, aTheme, refO, compO, cats );
578
579 auto switcher = makeSchSwitcher( aRefSch, aFrame.compSch, refO, compO, cats, aTheme );
580
581 SCH_SCREEN* refScreen = aFrame.editorPath.LastScreen();
582 wxString refLabel = refScreen ? refScreen->GetFileName() : wxString();
583
584 aDlg.Reload( refLabel, aFrame.compFile, std::move( diff ), /*aReferenceGeometry=*/{},
585 /*aComparisonGeometry=*/{}, std::move( switcher ), scope );
586}
587} // namespace
588
589
591{
592 SCH_EDIT_FRAME* schEditorFrame = dynamic_cast<SCH_EDIT_FRAME*>( m_frame );
593
594 wxCHECK( schEditorFrame, 0 );
595
596 wxFileDialog dlg( schEditorFrame, _( "Choose Schematic to Compare With" ), wxEmptyString,
597 wxEmptyString, FILEEXT::KiCadSchematicFileWildcard(), wxFD_OPEN | wxFD_FILE_MUST_EXIST );
598
599 if( dlg.ShowModal() != wxID_OK )
600 return 0;
601
602 wxFileName otherFn( dlg.GetPath() );
603 otherFn.MakeAbsolute();
604
605 if( !otherFn.GetExt().IsSameAs( FILEEXT::KiCadSchematicFileExtension, false ) )
606 {
607 schEditorFrame->ShowInfoBarError( _( "Select a KiCad s-expression schematic file (.kicad_sch)." ) );
608 return 0;
609 }
610
611 const wxString otherPath = otherFn.GetFullPath();
612
613 wxFileName projectFn = otherFn;
614 projectFn.SetExt( FILEEXT::ProjectFileExtension );
615 const wxString projectPath = projectFn.GetFullPath();
616
617 wxFileName activeProjectFn( schEditorFrame->Prj().GetProjectFullName() );
618 activeProjectFn.MakeAbsolute();
619
620 // Refuse the self-compare; attaching the active PROJECT to a second
621 // SCHEMATIC would clobber its ERC/schematic settings.
622 if( projectFn.SameAs( activeProjectFn ) )
623 {
624 schEditorFrame->ShowInfoBarError( _( "Select a schematic file from another project to compare." ) );
625 return 0;
626 }
627
628 return showSchematicComparison( otherPath, projectPath, otherPath );
629}
630
631
632int SCH_INSPECTION_TOOL::showSchematicComparison( const wxString& aOtherPath, const wxString& aProjectPath,
633 const wxString& aComparisonLabel )
634{
635 SCH_EDIT_FRAME* schEditorFrame = dynamic_cast<SCH_EDIT_FRAME*>( m_frame );
636
637 wxCHECK( schEditorFrame, 0 );
638
639 SETTINGS_MANAGER* mgr = schEditorFrame->GetSettingsManager();
640
641 wxCHECK( mgr, 0 );
642
643 // A missing .kicad_pro is fine (LoadProject returns false but still
644 // inserts a defaults-only slot); a present-but-malformed .kicad_pro is
645 // treated as failure.
646 bool projectLoadOk = mgr->LoadProject( aProjectPath, false );
647 PROJECT* otherPrj = mgr->GetProject( aProjectPath );
648
649 if( !otherPrj )
650 {
651 schEditorFrame->ShowInfoBarError( wxString::Format( _( "Failed to load project for %s" ), aOtherPath ) );
652 return 0;
653 }
654
655 if( !projectLoadOk && wxFileName( aProjectPath ).FileExists() )
656 {
657 mgr->UnloadProject( otherPrj, false );
658 schEditorFrame->ShowInfoBarError( wxString::Format( _( "Failed to load project for %s" ), aOtherPath ) );
659 return 0;
660 }
661
662 // SCH_DIFFER walks the sheet hierarchy and properties; connectivity build
663 // is unnecessary and slow for a read-only compare.
664 SCHEMATIC* loadedSchematic = nullptr;
665
666 try
667 {
668 loadedSchematic = EESCHEMA_HELPERS::LoadSchematic( aOtherPath,
669 /*aSetActive=*/false,
670 /*aForceDefaultProject=*/false, otherPrj,
671 /*aCalculateConnectivity=*/false );
672 }
673 catch( ... )
674 {
675 schEditorFrame->ShowInfoBarError( wxString::Format( _( "Failed to load %s" ), aOtherPath ) );
676 mgr->UnloadProject( otherPrj, false );
677 return 0;
678 }
679
680 SCHEMATIC* mySch = &schEditorFrame->Schematic();
681
682 // Belt-and-suspenders: EESCHEMA_HELPERS::LoadSchematic has a fallback
683 // branch that returns the active editor's schematic when the project
684 // happens to be the active one. The path guard above should have caught
685 // this, but a path-alias miss would leave the unique_ptr owning the live
686 // schematic and SetProject(nullptr) would destroy editor state.
687 if( loadedSchematic == mySch )
688 {
689 schEditorFrame->ShowInfoBarError( _( "Select a schematic file from another project to compare." ) );
690 mgr->UnloadProject( otherPrj, false );
691 return 0;
692 }
693
694 std::unique_ptr<SCHEMATIC> otherSchematic{ loadedSchematic };
695
696 if( !otherSchematic )
697 {
698 schEditorFrame->ShowInfoBarError( wxString::Format( _( "Failed to load %s" ), aOtherPath ) );
699 mgr->UnloadProject( otherPrj, false );
700 return 0;
701 }
702
703 KICAD_DIFF::SCH_DIFFER differ( mySch, otherSchematic.get(), aOtherPath );
704
705 // Scope to the editor's current sheet. Comparison resolves the matching
706 // sheet by KIID when present, else falls back to its root.
707 const KIID_PATH scopeBefore = schEditorFrame->GetCurrentSheet().Path();
708 const SCH_SHEET_LIST otherSheets = otherSchematic->BuildSheetListSortedByPageNumbers();
709 KIID_PATH scopeAfter;
710
711 if( auto sp = otherSchematic->Hierarchy().GetSheetPathByKIIDPath( scopeBefore, true ) )
712 scopeAfter = sp->Path();
713 else if( !otherSheets.empty() )
714 scopeAfter = otherSheets.front().Path();
715
716 if( !scopeBefore.empty() && !scopeAfter.empty() )
717 differ.SetScope( scopeBefore, scopeAfter );
718
720
722
723 std::map<KIID, KIGFX::COLOR4D> refOverrides;
724 std::map<KIID, KIGFX::COLOR4D> compOverrides;
725 std::map<KIID, KICAD_DIFF::CATEGORY> kiidCategories;
726
727 buildSchOverrides( result, theme, refOverrides, compOverrides, kiidCategories );
728
730 refOverrides );
732 theme.comparison,
733 compOverrides );
734
735 DIALOG_KICAD_DIFF::SHEET_SWITCHER initialSwitcher = makeSchSwitcher( mySch, otherSchematic.get(), refOverrides,
736 compOverrides, kiidCategories, theme );
737
738 KIID_PATH initialSheet = schEditorFrame->GetCurrentSheet().Path();
739 SCH_SCREEN* currentSheetScreen = schEditorFrame->GetCurrentSheet().LastScreen();
740 wxString referenceLabel = currentSheetScreen ? currentSheetScreen->GetFileName() : mySch->GetFileName();
741
742 DIALOG_KICAD_DIFF dlgDiff( schEditorFrame, referenceLabel, aComparisonLabel, result, std::move( refGeometry ),
743 std::move( compGeometry ), std::move( initialSwitcher ), std::move( initialSheet ) );
744
745 // Drill state owns the comparison schematics loaded across double-click
746 // drills. The editor schematic stays the reference on all drills.
747 DRILL_FRAME drillCurrent{ schEditorFrame->GetCurrentSheet(), otherSchematic.get(), aOtherPath, scopeAfter };
748
749 std::vector<DRILL_FRAME> drillBack;
750 std::vector<std::unique_ptr<SCHEMATIC>> drilledSchs;
751
752 drilledSchs.push_back( std::move( otherSchematic ) );
753
754 if( WIDGET_DIFF_CANVAS* canvas = dlgDiff.DiffCanvas() )
755 {
756 canvas->SetDoubleClickHandler(
757 [&dlgDiff, &drillCurrent, &drillBack, &drilledSchs, mySch, otherPrj, theme,
758 schEditorFrame]( KIGFX::VIEW_ITEM* aItem )
759 {
760 auto* sheet = dynamic_cast<SCH_SHEET*>( aItem );
761
762 if( !sheet )
763 return;
764
765 const wxString sheetFile = sheet->GetFileName();
766
767 if( sheetFile.IsEmpty() )
768 return;
769
770 KIID_PATH newEditorKiid = drillCurrent.editorPath.Path();
771 newEditorKiid.push_back( sheet->m_Uuid );
772
773 auto newEditorSheet = mySch->Hierarchy().GetSheetPathByKIIDPath( newEditorKiid, true );
774
775 if( !newEditorSheet )
776 return;
777
778 // Prefer resolving inside the current comparison schematic so
779 // shared-sheet instance context is preserved on both sides.
780 DRILL_FRAME next{ *newEditorSheet, drillCurrent.compSch, drillCurrent.compFile, KIID_PATH() };
781
782 if( auto compMatch = next.compSch->Hierarchy().GetSheetPathByKIIDPath( newEditorKiid, true ) )
783 {
784 next.compScope = compMatch->Path();
785
786 if( SCH_SCREEN* compMatchScreen = compMatch->LastScreen() )
787 next.compFile = compMatchScreen->GetFileName();
788 }
789 else
790 {
791 wxFileName newCompFn( wxFileName( drillCurrent.compFile ).GetPath(), sheetFile );
792 newCompFn.MakeAbsolute();
793
794 SCHEMATIC* loaded = EESCHEMA_HELPERS::LoadSchematic( newCompFn.GetFullPath(),
795 /*aSetActive*/ false,
796 /*aForceDefaultProject*/ false, otherPrj,
797 /*aCalculateConnectivity*/ false );
798
799 if( !loaded )
800 {
801 schEditorFrame->ShowInfoBarError( wxString::Format( _( "Failed to load %s" ),
802 newCompFn.GetFullPath() ) );
803 return;
804 }
805
806 const SCH_SHEET_LIST loadedSheets = loaded->BuildSheetListSortedByPageNumbers();
807
808 if( !loadedSheets.empty() )
809 next.compScope = loadedSheets.front().Path();
810
811 drilledSchs.emplace_back( loaded );
812 next.compSch = loaded;
813 next.compFile = newCompFn.GetFullPath();
814 }
815
816 reloadDrillFrame( dlgDiff, mySch, theme, next );
817
818 drillBack.push_back( drillCurrent );
819 drillCurrent = next;
820 dlgDiff.EnableUp( true );
821 } );
822
823 dlgDiff.SetUpHandler(
824 [&dlgDiff, &drillCurrent, &drillBack, mySch, theme]()
825 {
826 if( drillBack.empty() )
827 return;
828
829 reloadDrillFrame( dlgDiff, mySch, theme, drillBack.back() );
830
831 drillCurrent = drillBack.back();
832 drillBack.pop_back();
833 dlgDiff.EnableUp( !drillBack.empty() );
834 } );
835 }
836
837 dlgDiff.ShowModal();
838
839 // Detach schematics from the project before unloading so the project's
840 // ERC and schematic settings release cleanly.
841 for( auto& sch : drilledSchs )
842 {
843 if( sch )
844 sch->SetProject( nullptr );
845 }
846
847 drilledSchs.clear();
848
849 mgr->UnloadProject( otherPrj, false );
850
851 return 0;
852}
853
854
856{
857 SCH_EDIT_FRAME* schEditorFrame = dynamic_cast<SCH_EDIT_FRAME*>( m_frame );
858
859 wxCHECK( schEditorFrame, 0 );
860
861 SCHEMATIC* mySch = &schEditorFrame->Schematic();
862
863 if( mySch->GetFileName().IsEmpty() )
864 {
865 schEditorFrame->ShowInfoBarError( _( "Save the schematic before comparing against local history." ) );
866 return 0;
867 }
868
869 const wxString projectPath = schEditorFrame->Prj().GetProjectPath();
870 LOCAL_HISTORY& history = schEditorFrame->Kiway().LocalHistory();
871
872 std::vector<LOCAL_HISTORY_SNAPSHOT_INFO> snapshots = history.GetSnapshots( projectPath );
873
874 if( snapshots.empty() )
875 {
876 schEditorFrame->ShowInfoBarError( _( "No local history snapshots for this project." ) );
877 return 0;
878 }
879
880 SETTINGS_MANAGER* mgr = schEditorFrame->GetSettingsManager();
881
882 wxCHECK( mgr, 0 );
883
884 auto relTo =
885 [&]( const wxString& aFull )
886 {
887 wxFileName fn( aFull );
888 fn.MakeRelativeTo( projectPath );
889 return fn.GetFullPath( wxPATH_UNIX );
890 };
891
892 const wxString rootRel = relTo( mySch->GetFileName() );
893 const wxString projRel = relTo( schEditorFrame->Prj().GetProjectFullName() );
894
895 // One entry per distinct schematic version: newest-first, skipping commits
896 // with no schematic or whose schematic content (any .kicad_sch sheet) matches
897 // the previously kept one. This drops board-only saves like "PCB Save".
898 std::vector<LOCAL_HISTORY_SNAPSHOT_INFO> filtered;
899 wxString prevFingerprint;
900
901 for( const LOCAL_HISTORY_SNAPSHOT_INFO& s : snapshots )
902 {
903 wxString fingerprint = history.TreeFingerprint( projectPath, s.hash, wxS( ".kicad_sch" ) );
904
905 if( fingerprint.IsEmpty() || fingerprint == prevFingerprint )
906 continue;
907
908 prevFingerprint = fingerprint;
909 filtered.push_back( s );
910 }
911
912 if( filtered.empty() )
913 {
914 schEditorFrame->ShowInfoBarError( _( "No local history snapshots change this schematic." ) );
915 return 0;
916 }
917
918 snapshots = std::move( filtered );
919
920 std::vector<wxString> labels;
921
922 for( const LOCAL_HISTORY_SNAPSHOT_INFO& s : snapshots )
923 {
924 wxString summary = s.summary.IsEmpty() ? s.message.BeforeFirst( '\n' ) : s.summary;
925 labels.push_back( wxString::Format( wxS( "%s (%s)" ), summary, s.hash.Left( 8 ) ) );
926 }
927
929 const KIID_PATH scopeBefore = schEditorFrame->GetCurrentSheet().Path();
930
931 struct SCH_DIFF_VIEW
932 {
937 KIID_PATH compScope;
938 };
939
940 auto buildView =
941 [&]( SCHEMATIC* aComp, const wxString& aPath ) -> SCH_DIFF_VIEW
942 {
943 SCH_DIFF_VIEW view;
944 KICAD_DIFF::SCH_DIFFER differ( mySch, aComp, aPath );
945
946 KIID_PATH scopeAfter;
947
948 if( std::optional<SCH_SHEET_PATH> sp = aComp->Hierarchy().GetSheetPathByKIIDPath( scopeBefore, true ) )
949 {
950 scopeAfter = sp->Path();
951 }
952 else
953 {
955
956 if( !sheets.empty() )
957 scopeAfter = sheets.front().Path();
958 }
959
960 if( !scopeBefore.empty() && !scopeAfter.empty() )
961 differ.SetScope( scopeBefore, scopeAfter );
962
963 view.compScope = scopeAfter;
964 view.result = differ.Diff();
965
966 std::map<KIID, KIGFX::COLOR4D> refO;
967 std::map<KIID, KIGFX::COLOR4D> compO;
968 std::map<KIID, KICAD_DIFF::CATEGORY> cats;
969 buildSchOverrides( view.result, theme, refO, compO, cats );
970
971 view.refGeom = KICAD_DIFF::ExtractSchematicGeometry( *mySch, theme.reference, refO );
972 view.compGeom = KICAD_DIFF::ExtractSchematicGeometry( *aComp, theme.comparison, compO );
973 view.switcher = makeSchSwitcher( mySch, aComp, refO, compO, cats, theme );
974
975 return view;
976 };
977
978 // State for the revision currently shown. Swapped on each dropdown change.
979 std::unique_ptr<SCHEMATIC> curSch;
980 PROJECT* curPrj = nullptr;
981 wxString curTempDir;
982
983 // Drill state: which comparison sheet is shown and sub-schematics loaded on
984 // double-click. Reset when the revision changes.
985 DRILL_FRAME drillCurrent;
986 std::vector<DRILL_FRAME> drillBack;
987 std::vector<std::unique_ptr<SCHEMATIC>> drilledSchs;
988
989 auto cleanupCurrent =
990 [&]()
991 {
992 for( std::unique_ptr<SCHEMATIC>& sch : drilledSchs )
993 {
994 if( sch )
995 sch->SetProject( nullptr );
996 }
997
998 drilledSchs.clear();
999
1000 if( curSch )
1001 {
1002 curSch->SetProject( nullptr );
1003 curSch.reset();
1004 }
1005
1006 // Skip if the project was already evicted from the manager.
1007 if( curPrj && mgr->IsProjectLoaded( curPrj ) )
1008 mgr->UnloadProject( curPrj, false );
1009
1010 curPrj = nullptr;
1011
1012 if( !curTempDir.IsEmpty() )
1013 {
1014 wxFileName::Rmdir( curTempDir, wxPATH_RMDIR_RECURSIVE );
1015 curTempDir.Clear();
1016 }
1017 };
1018
1019 // Extract the hierarchy at snapshot aIndex and load its root sheet, cleaning
1020 // up the temp dir on any failure.
1021 auto loadRevision =
1022 [&]( int aIndex, std::unique_ptr<SCHEMATIC>& aSch, PROJECT*& aPrj, wxString& aTempDir ) -> bool
1023 {
1024 const wxString hash = snapshots[aIndex].hash;
1025 wxFileName dirFn;
1026 dirFn.AssignDir( wxFileName::GetTempDir() );
1027 dirFn.AppendDir( wxS( "kicad-history-" ) + hash.Left( 8 ) );
1028 const wxString tempDir = dirFn.GetPath();
1029
1030 // Extract just the schematic sheets and project file, skipping the
1031 // board, 3D models, gerbers, etc.
1032 if( !history.ExtractAllFilesAtCommit( projectPath, hash, tempDir,
1033 { wxS( ".kicad_sch" ), wxS( ".kicad_pro" ) } ) )
1034 {
1035 wxFileName::Rmdir( tempDir, wxPATH_RMDIR_RECURSIVE );
1036 return false;
1037 }
1038
1039 const wxString root = tempDir + wxS( "/" ) + rootRel;
1040 const wxString proj = tempDir + wxS( "/" ) + projRel;
1041
1042 mgr->LoadProject( proj, false );
1043 PROJECT* prj = mgr->GetProject( proj );
1044
1045 if( !prj )
1046 {
1047 wxFileName::Rmdir( tempDir, wxPATH_RMDIR_RECURSIVE );
1048 return false;
1049 }
1050
1051 SCHEMATIC* loaded = nullptr;
1052
1053 try
1054 {
1055 loaded = EESCHEMA_HELPERS::LoadSchematic( root, /*aSetActive*/ false,
1056 /*aForceDefaultProject*/ false, prj,
1057 /*aCalculateConnectivity*/ false );
1058 }
1059 catch( ... )
1060 {
1061 mgr->UnloadProject( prj, false );
1062 wxFileName::Rmdir( tempDir, wxPATH_RMDIR_RECURSIVE );
1063 return false;
1064 }
1065
1066 if( !loaded || loaded == mySch )
1067 {
1068 mgr->UnloadProject( prj, false );
1069 wxFileName::Rmdir( tempDir, wxPATH_RMDIR_RECURSIVE );
1070 return false;
1071 }
1072
1073 aSch.reset( loaded );
1074 aPrj = prj;
1075 aTempDir = tempDir;
1076 return true;
1077 };
1078
1079 auto loadView =
1080 [&]( int aIndex, std::unique_ptr<SCHEMATIC>& aSch, PROJECT*& aPrj, wxString& aTempDir,
1081 SCH_DIFF_VIEW& aView ) -> bool
1082 {
1083 if( !loadRevision( aIndex, aSch, aPrj, aTempDir ) )
1084 return false;
1085
1086 try
1087 {
1088 aView = buildView( aSch.get(), aTempDir + wxS( "/" ) + rootRel );
1089 }
1090 catch( ... )
1091 {
1092 aSch->SetProject( nullptr );
1093 aSch.reset();
1094 mgr->UnloadProject( aPrj, false );
1095 aPrj = nullptr;
1096 wxFileName::Rmdir( aTempDir, wxPATH_RMDIR_RECURSIVE );
1097 aTempDir.Clear();
1098 return false;
1099 }
1100
1101 return true;
1102 };
1103
1104 SCH_DIFF_VIEW view;
1105 int startIndex = 0;
1106
1107 if( !loadView( 0, curSch, curPrj, curTempDir, view ) )
1108 {
1109 schEditorFrame->ShowInfoBarError( _( "Could not compare against the selected snapshot." ) );
1110 return 0;
1111 }
1112
1113 // Default to the first commit back from HEAD that actually differs from the
1114 // current schematic, so opening lands on real changes rather than an in-sync HEAD.
1115 while( view.result.Empty() && startIndex + 1 < static_cast<int>( snapshots.size() ) )
1116 {
1117 std::unique_ptr<SCHEMATIC> nextSch;
1118 PROJECT* nextPrj = nullptr;
1119 wxString nextTempDir;
1120 SCH_DIFF_VIEW nextView;
1121
1122 if( !loadView( startIndex + 1, nextSch, nextPrj, nextTempDir, nextView ) )
1123 break;
1124
1125 cleanupCurrent();
1126 view = std::move( nextView );
1127 curSch = std::move( nextSch );
1128 curPrj = nextPrj;
1129 curTempDir = nextTempDir;
1130 startIndex++;
1131 }
1132
1133 drillCurrent = { schEditorFrame->GetCurrentSheet(), curSch.get(), curTempDir + wxS( "/" ) + rootRel,
1134 view.compScope };
1135
1136 int shownIndex = startIndex;
1137
1138 SCH_SCREEN* curScreen = schEditorFrame->GetCurrentSheet().LastScreen();
1139 wxString referenceLabel = curScreen ? curScreen->GetFileName() : mySch->GetFileName();
1140
1141 std::unique_ptr<DIALOG_KICAD_DIFF> dlgDiff = std::make_unique<DIALOG_KICAD_DIFF>( schEditorFrame, referenceLabel,
1142 labels[startIndex], view.result,
1143 view.refGeom, view.compGeom,
1144 view.switcher, scopeBefore );
1145
1146 // Double-click a sheet to drill into its sub-schematic, within the current
1147 // revision's extracted hierarchy.
1148 if( WIDGET_DIFF_CANVAS* canvas = dlgDiff->DiffCanvas() )
1149 {
1150 canvas->SetDoubleClickHandler(
1151 [&]( KIGFX::VIEW_ITEM* aItem )
1152 {
1153 SCH_SHEET* sheet = dynamic_cast<SCH_SHEET*>( aItem );
1154
1155 if( !sheet || sheet->GetFileName().IsEmpty() )
1156 return;
1157
1158 KIID_PATH newEditorKiid = drillCurrent.editorPath.Path();
1159 newEditorKiid.push_back( sheet->m_Uuid );
1160
1161 auto newEditorSheet = mySch->Hierarchy().GetSheetPathByKIIDPath( newEditorKiid, true );
1162
1163 if( !newEditorSheet )
1164 return;
1165
1166 DRILL_FRAME next{ *newEditorSheet, drillCurrent.compSch, drillCurrent.compFile, KIID_PATH() };
1167
1168 if( auto compMatch = next.compSch->Hierarchy().GetSheetPathByKIIDPath( newEditorKiid, true ) )
1169 {
1170 next.compScope = compMatch->Path();
1171
1172 if( SCH_SCREEN* matchScreen = compMatch->LastScreen() )
1173 next.compFile = matchScreen->GetFileName();
1174 }
1175 else
1176 {
1177 wxFileName subFn( wxFileName( drillCurrent.compFile ).GetPath(), sheet->GetFileName() );
1178 subFn.MakeAbsolute();
1179
1180 SCHEMATIC* loaded = nullptr;
1181
1182 try
1183 {
1184 loaded = EESCHEMA_HELPERS::LoadSchematic( subFn.GetFullPath(), /*aSetActive=*/false,
1185 /*aForceDefaultProject=*/false, curPrj,
1186 /*aCalculateConnectivity=*/false );
1187 }
1188 catch( ... )
1189 {
1190 loaded = nullptr;
1191 }
1192
1193 if( !loaded || loaded == mySch )
1194 {
1195 schEditorFrame->ShowInfoBarError( wxString::Format( _( "Failed to load %s" ),
1196 subFn.GetFullPath() ) );
1197 return;
1198 }
1199
1200 SCH_SHEET_LIST loadedSheets = loaded->BuildSheetListSortedByPageNumbers();
1201
1202 if( !loadedSheets.empty() )
1203 next.compScope = loadedSheets.front().Path();
1204
1205 drilledSchs.emplace_back( loaded );
1206 next.compSch = loaded;
1207 next.compFile = subFn.GetFullPath();
1208 }
1209
1210 try
1211 {
1212 reloadDrillFrame( *dlgDiff, mySch, theme, next );
1213 }
1214 catch( ... )
1215 {
1216 schEditorFrame->ShowInfoBarError( _( "Could not open this sheet for comparison." ) );
1217 return;
1218 }
1219
1220 drillBack.push_back( drillCurrent );
1221 drillCurrent = next;
1222 dlgDiff->EnableUp( true );
1223 } );
1224
1225 dlgDiff->SetUpHandler(
1226 [&]()
1227 {
1228 if( drillBack.empty() )
1229 return;
1230
1231 try
1232 {
1233 reloadDrillFrame( *dlgDiff, mySch, theme, drillBack.back() );
1234 }
1235 catch( ... )
1236 {
1237 schEditorFrame->ShowInfoBarError( _( "Could not open this sheet for comparison." ) );
1238 return;
1239 }
1240
1241 drillCurrent = drillBack.back();
1242 drillBack.pop_back();
1243 dlgDiff->EnableUp( !drillBack.empty() );
1244 } );
1245 }
1246
1247 dlgDiff->SetRevisionChooser(
1248 labels, startIndex,
1249 [&]( int aIndex )
1250 {
1251 if( aIndex == shownIndex )
1252 return;
1253
1254 std::unique_ptr<SCHEMATIC> newSch;
1255 PROJECT* newPrj = nullptr;
1256 wxString newTempDir;
1257 SCH_DIFF_VIEW newView;
1258
1259 if( !loadView( aIndex, newSch, newPrj, newTempDir, newView ) )
1260 {
1261 schEditorFrame->ShowInfoBarError( _( "Could not compare against the selected snapshot." ) );
1262 return;
1263 }
1264
1265 dlgDiff->Reload( referenceLabel, labels[aIndex], newView.result, newView.refGeom,
1266 newView.compGeom, newView.switcher, scopeBefore );
1267
1268 cleanupCurrent();
1269 view = std::move( newView );
1270 curSch = std::move( newSch );
1271 curPrj = newPrj;
1272 curTempDir = newTempDir;
1273 shownIndex = aIndex;
1274
1275 // Restart drilling from the new revision's root.
1276 drillBack.clear();
1277 drillCurrent = { schEditorFrame->GetCurrentSheet(), curSch.get(), curTempDir + wxS( "/" ) + rootRel,
1278 view.compScope };
1279 dlgDiff->EnableUp( false );
1280 } );
1281
1282 dlgDiff->ShowModal();
1283
1284 // Destroy the dialog before the schematics it references are freed.
1285 dlgDiff.reset();
1286
1287 cleanupCurrent();
1288 return 0;
1289}
1290
1291
1293{
1294 SIMULATOR_FRAME* simFrame = (SIMULATOR_FRAME*) m_frame->Kiway().Player( FRAME_SIMULATOR, true );
1295
1296 if( !simFrame )
1297 return -1;
1298
1299 if( wxWindow* blocking_win = simFrame->Kiway().GetBlockingDialog() )
1300 blocking_win->Close( true );
1301
1302 simFrame->Show( true );
1303
1304 // On Windows, Raise() does not bring the window on screen, when iconized
1305 if( simFrame->IsIconized() )
1306 simFrame->Iconize( false );
1307
1308 simFrame->Raise();
1309
1310 return 0;
1311}
1312
1313
1315{
1316 wxString datasheet;
1317 std::vector<EMBEDDED_FILES*> filesStack;
1318
1319 if( m_frame->IsType( FRAME_SCH_SYMBOL_EDITOR ) )
1320 {
1321 LIB_SYMBOL* symbol = static_cast<SYMBOL_EDIT_FRAME*>( m_frame )->GetCurSymbol();
1322
1323 if( !symbol )
1324 return 0;
1325
1326 datasheet = symbol->GetDatasheetField().GetText();
1327 filesStack.push_back( symbol );
1328 }
1329 else if( m_frame->IsType( FRAME_SCH_VIEWER ) )
1330 {
1331 LIB_SYMBOL* entry = static_cast<SYMBOL_VIEWER_FRAME*>( m_frame )->GetSelectedSymbol();
1332
1333 if( !entry )
1334 return 0;
1335
1336 datasheet = entry->GetDatasheetField().GetText();
1337 filesStack.push_back( entry );
1338 }
1339 else if( m_frame->IsType( FRAME_SCH ) )
1340 {
1341 SCH_SELECTION& selection = m_selectionTool->RequestSelection( { SCH_SYMBOL_T } );
1342
1343 if( selection.Empty() )
1344 return 0;
1345
1346 SCH_SYMBOL* symbol = (SCH_SYMBOL*) selection.Front();
1347 SCH_FIELD* field = symbol->GetField( FIELD_T::DATASHEET );
1348
1349 // Use GetShownText() to resolve any text variables
1350 datasheet = field->GetShownText( &symbol->Schematic()->CurrentSheet(), FOR_GUI );
1351 filesStack.push_back( symbol->Schematic() );
1352
1353 if( symbol->GetLibSymbolRef() )
1354 filesStack.push_back( symbol->GetLibSymbolRef().get() );
1355 }
1356
1357 if( datasheet.IsEmpty() || datasheet == wxS( "~" ) )
1358 {
1359 m_frame->ShowInfoBarError( _( "No datasheet defined." ) );
1360 }
1361 else
1362 {
1364 filesStack );
1365 }
1366
1367 return 0;
1368}
1369
1370
1372{
1373 SYMBOL_EDIT_FRAME* symbolEditFrame = dynamic_cast<SYMBOL_EDIT_FRAME*>( m_frame );
1374 SCH_EDIT_FRAME* schEditFrame = dynamic_cast<SCH_EDIT_FRAME*>( m_frame );
1375 SCH_SELECTION_TOOL* selTool = m_toolMgr->GetTool<SCH_SELECTION_TOOL>();
1376 SCH_SELECTION& selection = selTool->GetSelection();
1377
1378 // Note: the symbol viewer manages its own message panel
1379
1380 if( symbolEditFrame || schEditFrame )
1381 {
1382 if( selection.GetSize() == 1 )
1383 {
1384 EDA_ITEM* item = (EDA_ITEM*) selection.Front();
1385 std::vector<MSG_PANEL_ITEM> msgItems;
1386
1387 if( std::optional<wxString> uuid = GetMsgPanelDisplayUuid( item->m_Uuid ) )
1388 msgItems.emplace_back( _( "UUID" ), *uuid );
1389
1390 item->GetMsgPanelInfo( m_frame, msgItems );
1391 m_frame->SetMsgPanel( msgItems );
1392 }
1393 else
1394 {
1395 m_frame->ClearMsgPanel();
1396 }
1397 }
1398
1399 if( schEditFrame )
1400 {
1401 schEditFrame->UpdateNetHighlightStatus();
1402 schEditFrame->UpdateHierarchySelection();
1403 }
1404
1405 return 0;
1406}
1407
1408
1410{
1414 // See note 1:
1418
1425
1427
1428 // Note 1: tUpdateMessagePanel is called by CrossProbe. So uncomment this line if
1429 // call to CrossProbe is modifiied
1430 // Go( &SCH_INSPECTION_TOOL::UpdateMessagePanel, EVENTS::SelectedEvent );
1434}
1435
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:982
static TOOL_ACTION excludeMarker
Definition actions.h:125
static TOOL_ACTION nextMarker
Definition actions.h:124
static TOOL_ACTION showDatasheet
Definition actions.h:263
static TOOL_ACTION prevMarker
Definition actions.h:123
void AddItem(const TOOL_ACTION &aAction, const SELECTION_CONDITION &aCondition, int aOrder=ANY_ORDER)
Add a menu entry to run a TOOL_ACTION on selected items.
wxPanel * AddBlankPage(const wxString &aTitle)
WX_HTML_REPORT_BOX * AddHTMLPage(const wxString &aTitle)
void SetUserItemID(const KIID &aID)
void ExcludeMarker(SCH_MARKER *aMarker=nullptr)
Exclude aMarker from the ERC list.
void SelectMarker(const SCH_MARKER *aMarker)
void PrevMarker()
void NextMarker()
File-compare dialog (Phase 7).
void SetUpHandler(UP_HANDLER aHandler)
std::function< void(WIDGET_DIFF_CANVAS &, const KIID_PATH &)> SHEET_SWITCHER
WIDGET_DIFF_CANVAS * DiffCanvas() const
void EnableUp(bool aEnable)
void Reload(const wxString &aReferencePath, const wxString &aComparisonPath, KICAD_DIFF::DOCUMENT_DIFF aDiff, KICAD_DIFF::DOCUMENT_GEOMETRY aReferenceGeometry, KICAD_DIFF::DOCUMENT_GEOMETRY aComparisonGeometry, SHEET_SWITCHER aSheetSwitcher, KIID_PATH aInitialSheet)
Swap in a fresh diff with new schematics.
bool Show(bool show) override
int ShowModal() override
SETTINGS_MANAGER * GetSettingsManager() const
void ShowInfoBarError(const wxString &aErrorMsg, bool aShowCloseButton=false, INFOBAR_MESSAGE_TYPE aType=INFOBAR_MESSAGE_TYPE::GENERIC)
Show the WX_INFOBAR displayed on the top of the canvas with a message and an error icon on the left o...
A base class for most all the KiCad significant classes used in schematics and boards.
Definition eda_item.h:98
const KIID m_Uuid
Definition eda_item.h:599
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:110
virtual void GetMsgPanelInfo(EDA_DRAW_FRAME *aFrame, std::vector< MSG_PANEL_ITEM > &aList)
Populate aList of MSG_PANEL_ITEM objects with it's internal state for display purposes.
Definition eda_item.h:296
static SCHEMATIC * LoadSchematic(const wxString &aFileName, bool aSetActive, bool aForceDefaultProject, PROJECT *aProject=nullptr, bool aCalculateConnectivity=true, REPORTER *aRootReporter=nullptr)
static const TOOL_EVENT ClearedEvent
Definition actions.h:347
static const TOOL_EVENT SelectedEvent
Definition actions.h:345
static const TOOL_EVENT SelectedItemsModified
Selected item had a property changed (except movement)
Definition actions.h:352
static const TOOL_EVENT PointSelectedEvent
Definition actions.h:344
static const TOOL_EVENT UnselectedEvent
Definition actions.h:346
void AddHTML_Text(const wxString &message)
Add HTML text (without any change) to message list.
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
Diff two already-parsed SCHEMATICs and produce a DOCUMENT_DIFF.
Definition sch_differ.h:55
void SetScope(const KIID_PATH &aBeforeScope, const KIID_PATH &aAfterScope)
Restrict the diff to one sheet on each side.
DOCUMENT_DIFF Diff() override
Produce a DOCUMENT_DIFF of the inputs the concrete differ was constructed with.
An abstract base class for deriving all objects that can be added to a VIEW.
Definition view_item.h:82
Definition kiid.h:46
PROJECT & Prj() const
Return a reference to the PROJECT associated with this KIWAY.
KIWAY & Kiway() const
Return a reference to the KIWAY that this object has an opportunity to participate in.
wxWindow * GetBlockingDialog()
Gets the window pointer to the blocking dialog (to send it signals)
Definition kiway.cpp:680
LOCAL_HISTORY & LocalHistory()
Return the LOCAL_HISTORY associated with this KIWAY.
Definition kiway.h:459
bool HasLibrary(const wxString &aNickname, bool aCheckEnabled=false) const
Test for the existence of aNickname in the library tables.
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:45
const UTF8 & GetLibItemName() const
Definition lib_id.h:98
const UTF8 & GetLibNickname() const
Return the logical library name portion of a LIB_ID.
Definition lib_id.h:83
Define a library symbol object.
Definition lib_symbol.h:114
SCH_FIELD & GetDatasheetField()
Definition lib_symbol.h:459
std::unique_ptr< LIB_SYMBOL > Flatten() const
Return a flattened symbol inheritance to the caller.
Simple local history manager built on libgit2.
std::vector< LOCAL_HISTORY_SNAPSHOT_INFO > GetSnapshots(const wxString &aProjectPath)
Snapshots (commits) for the project, newest first.
wxString TreeFingerprint(const wxString &aProjectPath, const wxString &aHash, const wxString &aExtension)
Fingerprint of all files ending in aExtension recorded by commit aHash (sorted path:blob pairs).
bool ExtractAllFilesAtCommit(const wxString &aProjectPath, const wxString &aHash, const wxString &aDestDir, const std::vector< wxString > &aExtensions={})
Write files recorded at aHash into aDestDir, recreating the project's relative folder structure.
static SYMBOL_LIBRARY_ADAPTER * SymbolLibAdapter(PROJECT *aProject)
Accessor for project symbol library manager adapter.
static SEARCH_STACK * SchSearchS(PROJECT *aProject)
Accessor for Eeschema search stack.
Container for project specific data.
Definition project.h:63
virtual const wxString GetProjectFullName() const
Return the full path and name of the project.
Definition project.cpp:177
virtual const wxString GetProjectPath() const
Return the full path of the project.
Definition project.cpp:183
Holds all the data relating to one schematic.
Definition schematic.h:149
SCH_SHEET_LIST BuildSheetListSortedByPageNumbers() const
wxString GetFileName() const
Helper to retrieve the filename from the root sheet screen.
SCHEMATIC_SETTINGS & Settings() const
SCH_SHEET_LIST Hierarchy() const
Return the full schematic flattened hierarchical sheet list.
SCH_SCREEN * RootScreen() const
Helper to retrieve the screen of the root sheet.
SCH_SHEET_PATH & CurrentSheet() const
Definition schematic.h:304
static TOOL_ACTION compareSchematicWithHistory
static TOOL_ACTION showBusSyntaxHelp
static TOOL_ACTION checkSymbol
static TOOL_ACTION compareSchematicWithFile
static TOOL_ACTION showSimulator
static TOOL_ACTION runERC
Inspection and Editing.
static TOOL_ACTION diffSymbol
A shim class between EDA_DRAW_FRAME and several derived classes: SYMBOL_EDIT_FRAME,...
static SELECTION_CONDITION SingleSymbol
static SELECTION_CONDITION SingleNonExcludedMarker
Schematic editor (Eeschema) main window.
void UpdateHierarchySelection()
Update the hierarchy navigation tree selection (cross-probe from schematic to hierarchy pane).
SCH_SHEET_PATH & GetCurrentSheet() const
SCHEMATIC & Schematic() const
DIALOG_BOOK_REPORTER * GetSymbolDiffDialog()
void UpdateNetHighlightStatus()
virtual const wxString & GetText() const override
Return the string associated with the text object.
Definition sch_field.h:139
wxString GetShownText(const SCH_SHEET_PATH *aPath, RESOLUTION_CONTEXT aContext, const wxString &aVariantName=wxEmptyString, int aDepth=0) const
int CompareSchematicWithHistory(const TOOL_EVENT &aEvent)
Diff the current schematic against the most recent local-history commit.
void InspectERCError(const std::shared_ptr< RC_ITEM > &aERCItem)
bool Init() override
Init() is called once upon a registration of the tool.
SYMBOL_DIFF_WIDGET * constructDiffPanel(wxPanel *aParentPanel)
int NextMarker(const TOOL_EVENT &aEvent)
int DiffSymbol(const TOOL_EVENT &aEvent)
int RunERC(const TOOL_EVENT &aEvent)
HTML_MESSAGE_BOX * m_busSyntaxHelp
int RunSimulation(const TOOL_EVENT &aEvent)
int ShowDatasheet(const TOOL_EVENT &aEvent)
wxString InspectERCErrorMenuText(const std::shared_ptr< RC_ITEM > &aERCItem)
int ExcludeMarker(const TOOL_EVENT &aEvent)
int CheckSymbol(const TOOL_EVENT &aEvent)
void setTransitions() override
This method is meant to be overridden in order to specify handlers for events.
int UpdateMessagePanel(const TOOL_EVENT &aEvent)
Display the selected item info (when clicking on a item)
int PrevMarker(const TOOL_EVENT &aEvent)
int showSchematicComparison(const wxString &aOtherPath, const wxString &aProjectPath, const wxString &aComparisonLabel)
Diff the schematic at aOtherPath against the live one and show the dialog.
int CompareSchematicWithFile(const TOOL_EVENT &aEvent)
Diff the current schematic against a user-selected .kicad_sch file.
int CrossProbe(const TOOL_EVENT &aEvent)
Called when clicking on a item:
int ShowBusSyntaxHelp(const TOOL_EVENT &aEvent)
void Reset(RESET_REASON aReason) override
Bring the tool to a known, initial state.
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition sch_item.h:170
SCHEMATIC * Schematic() const
Search the item hierarchy to find a SCHEMATIC.
Definition sch_item.cpp:302
int GetBodyStyle() const
Definition sch_item.h:253
int GetUnit() const
Definition sch_item.h:243
EE_RTREE & Items()
Get the full RTree, usually for iterating.
Definition sch_screen.h:122
const wxString & GetFileName() const
Definition sch_screen.h:157
SCH_SELECTION & GetSelection()
A container for handling SCH_SHEET_PATH objects in a flattened hierarchy.
std::optional< SCH_SHEET_PATH > GetSheetPathByKIIDPath(const KIID_PATH &aPath, bool aIncludeLastSheet=true) const
Finds a SCH_SHEET_PATH that matches the provided KIID_PATH.
KIID_PATH Path() const
Get the sheet path as an KIID_PATH.
SCH_SCREEN * LastScreen()
Sheet symbol placed in a schematic, and is the entry point for a sub schematic.
Definition sch_sheet.h:48
wxString GetFileName() const
Return the filename corresponding to this sheet.
Definition sch_sheet.h:386
Schematic symbol object.
Definition sch_symbol.h:73
void GetFields(std::vector< SCH_FIELD * > &aVector, bool aVisibleOnly) const override
Populate a std::vector with SCH_FIELDs, sorted in ordinal order.
VECTOR2I GetPosition() const override
Definition sch_symbol.h:895
const LIB_ID & GetLibId() const override
Definition sch_symbol.h:163
std::unique_ptr< LIB_SYMBOL > & GetLibSymbolRef()
Definition sch_symbol.h:182
SCH_FIELD * GetField(FIELD_T aFieldType)
Return a mandatory field in this symbol.
static HTML_MESSAGE_BOX * ShowSyntaxHelp(wxWindow *aParentWindow)
bool Init() override
Init() is called once upon a registration of the tool.
void Reset(RESET_REASON aReason) override
Bring the tool to a known, initial state.
SCH_TOOL_BASE(const std::string &aName)
SCH_SELECTION_TOOL * m_selectionTool
static bool Idle(const SELECTION &aSelection)
Test if there no items selected or being edited.
virtual unsigned int GetSize() const override
Return the number of stored items.
Definition selection.h:104
EDA_ITEM * Front() const
Definition selection.h:176
bool Empty() const
Checks if there is anything selected.
Definition selection.h:114
bool LoadProject(const wxString &aFullPath, bool aSetActive=true)
Load a project or sets up a new project with a specified path.
bool IsProjectLoaded(PROJECT *aProject) const
True if aProject is still owned by the manager.
PROJECT * GetProject(const wxString &aFullPath) const
Retrieve a loaded project by name.
bool UnloadProject(PROJECT *aProject, bool aSave=true)
Save, unload and unregister the given PROJECT.
The SIMULATOR_FRAME holds the main user-interface for running simulations.
void DisplayDiff(LIB_SYMBOL *aSchSymbol, LIB_SYMBOL *aLibSymbol, int aUnit, int aBodyStyle)
Set the currently displayed symbol.
The symbol library editor main window.
An interface to the global shared library manager that is schematic-specific and linked to one projec...
LIB_SYMBOL * LoadSymbol(const wxString &aNickname, const wxString &aName)
Load a LIB_SYMBOL having aName from the library given by aNickname.
Symbol library viewer main window.
KIGFX::VIEW * getView() const
Definition tool_base.cpp:34
@ SHUTDOWN
Tool is being shut down.
Definition tool_base.h:80
Generic, UI-independent tool event.
Definition tool_event.h:167
void Go(int(SCH_BASE_FRAME::*aStateFunc)(const TOOL_EVENT &), const TOOL_EVENT_LIST &aConditions=TOOL_EVENT(TC_ANY, TA_ANY))
GAL-backed canvas for visualizing a KICAD_DIFF::DIFF_SCENE.
A slimmed down version of WX_HTML_REPORT_PANEL.
REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED) override
Report a string with a given severity.
void Flush()
Build the HTML messages page.
@ FOR_GUI
Definition common.h:91
void DisplayInfoMessage(wxWindow *aParent, const wxString &aMessage, const wxString &aExtraInfo)
Display an informational message box with aMessage.
Definition confirm.cpp:245
This file is part of the common library.
#define _(s)
bool GetAssociatedDocument(wxWindow *aParent, const wxString &aDocName, PROJECT *aProject, SEARCH_STACK *aPaths, std::vector< EMBEDDED_FILES * > aFilesStack)
Open a document (file) with the suitable browser.
Definition eda_doc.cpp:59
This file is part of the common library.
@ ERCE_BUS_TO_NET_CONFLICT
A bus wire is graphically connected to a net port/pin (or vice versa).
@ ERCE_LIB_SYMBOL_MISMATCH
Symbol doesn't match copy in library.
@ FRAME_SCH_SYMBOL_EDITOR
Definition frame_type.h:31
@ FRAME_SCH_VIEWER
Definition frame_type.h:32
@ FRAME_SCH
Definition frame_type.h:30
@ FRAME_SIMULATOR
Definition frame_type.h:34
static const std::string ProjectFileExtension
static const std::string KiCadSchematicFileExtension
static wxString KiCadSchematicFileWildcard()
std::optional< wxString > GetMsgPanelDisplayUuid(const KIID &aKiid)
Get a formatted UUID string for display in the message panel, according to the current advanced confi...
Definition msgpanel.cpp:216
void ConfigureSchDiffCanvasContext(WIDGET_DIFF_CANVAS &aCanvas, SCHEMATIC *aReference, SCHEMATIC *aComparison, const KIGFX::COLOR4D &aColor, const std::map< KIID, KIGFX::COLOR4D > &aOverrides, const std::vector< KIGFX::VIEW_ITEM * > &aExtraItems, const std::map< KIID, KICAD_DIFF::CATEGORY > &aCategories, SCH_SCREEN *aReferenceScreen, SCH_SCREEN *aComparisonScreen)
DOCUMENT_GEOMETRY ExtractSchematicGeometry(const SCHEMATIC &aSchematic, const KIGFX::COLOR4D &aColor, const std::map< KIID, KIGFX::COLOR4D > &aOverrides, bool aOnlyOverrides)
Extract a coarse outline of a SCHEMATIC into a DOCUMENT_GEOMETRY for use as background context in DIF...
CATEGORY CategoryFor(CHANGE_KIND aKind)
Map a CHANGE_KIND to the visual category it belongs to.
void ForceFocus(wxWindow *aWindow)
Pass the current focus to the window.
Definition wxgtk/ui.cpp:127
CITER next(CITER it)
Definition ptree.cpp:120
void CheckLibSymbol(LIB_SYMBOL *aSymbol, std::vector< wxString > &aMessages, int aGridForPins, UNITS_PROVIDER *aUnitsProvider)
Check a library symbol to find incorrect settings.
wxString EscapeHTML(const wxString &aString)
Return a new wxString escaped for embedding in HTML.
KIGFX::COLOR4D reference
Default color for source-document context geometry.
Definition diff_scene.h:287
The full set of changes between two parsed documents of one type.
std::vector< ITEM_CHANGE > changes
Aggregate of background geometry extracted from one source document.
Definition diff_scene.h:163
One change record on a single item.
std::vector< ITEM_CHANGE > children
void CheckLibSymbol(LIB_SYMBOL *aSymbol, std::vector< wxString > &aMessages, int aGridForPins, UNITS_PROVIDER *aUnitsProvider)
Check a library symbol to find incorrect settings.
@ DATASHEET
name of datasheet
@ REFERENCE
Field Reference of part, i.e. "IC21".
wxString result
Test unit parsing edge cases and error handling.
@ SCH_SYMBOL_T
Definition typeinfo.h:168
@ SCH_MARKER_T
Definition typeinfo.h:154
Definition of file extensions used in Kicad.