KiCad PCB EDA Suite
Loading...
Searching...
No Matches
dialog_kicad_diff.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 The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 3
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, you may find one here:
18 * http://www.gnu.org/licenses/gpl-3.0.html
19 * or you may search the http://www.gnu.org website for the version 3 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
24#include "dialog_kicad_diff.h"
25
28
29#include <bitmaps.h>
30#include <eda_draw_frame.h>
31#include <layer_ids.h>
32#include <lseq.h>
33#include <set>
34#include <wx/bmpbuttn.h>
35#include <wx/button.h>
36#include <wx/checkbox.h>
37#include <wx/choice.h>
38#include <wx/menu.h>
39#include <wx/settings.h>
40#include <wx/srchctrl.h>
41#include <wx/sizer.h>
42#include <wx/splitter.h>
43#include <wx/stattext.h>
44#include <wx/wrapsizer.h>
45#include <wx/wupdlock.h>
46
47
48DIALOG_KICAD_DIFF::DIALOG_KICAD_DIFF( wxWindow* aParent, const wxString& aReferencePath,
49 const wxString& aComparisonPath, const KICAD_DIFF::DOCUMENT_DIFF& aDiff,
50 KICAD_DIFF::DOCUMENT_GEOMETRY aReferenceGeometry,
51 KICAD_DIFF::DOCUMENT_GEOMETRY aComparisonGeometry, SHEET_SWITCHER aSheetSwitcher,
52 KIID_PATH aInitialSheet ) :
53 DIALOG_KICAD_DIFF_BASE( aParent ),
54 m_diff( aDiff ),
55 m_sheetSwitcher( std::move( aSheetSwitcher ) ),
56 m_currentCanvasSheet( std::move( aInitialSheet ) )
57{
58 SetEscapeId( wxID_OK );
59
60 m_treeChanges->Bind( wxEVT_TREE_ITEM_MENU, &DIALOG_KICAD_DIFF::OnTreeItemMenu, this );
61
62 m_pathReference->SetLabel( aReferencePath );
63 m_pathComparison->SetLabel( aComparisonPath );
64
65 // Two property columns: name | before -> after
66 m_listProperties->AppendColumn( _( "Property" ), wxLIST_FORMAT_LEFT, 180 );
67 m_listProperties->AppendColumn( _( "Before" ), wxLIST_FORMAT_LEFT, 160 );
68 m_listProperties->AppendColumn( _( "After" ), wxLIST_FORMAT_LEFT, 160 );
69
70 // Splice a search box above the change tree. Empty filter = show all.
71 if( wxSizer* treeSizer = m_panelTree->GetSizer() )
72 {
73 wxSearchCtrl* search = new wxSearchCtrl( m_panelTree, wxID_ANY );
74 OptOut( search );
75 search->SetDescriptiveText( _( "Filter changes…" ) );
76 search->ShowCancelButton( true );
77
78 auto onSearch = [this, search]( wxCommandEvent& aEvent )
79 {
80 // BuildChangeTreeGroups lowercases internally; keep the
81 // raw input here so a future surface displaying the
82 // active filter (e.g. tooltip) preserves capitalization.
83 m_searchFilter = search->GetValue();
84 buildTree();
85 aEvent.Skip();
86 };
87
88 // wxSearchCtrl's cancel button fires its own event on some ports
89 // (notably macOS); binding both keeps the filter cleared whichever
90 // path the user takes to empty the box.
91 search->Bind( wxEVT_TEXT, onSearch );
92 search->Bind( wxEVT_SEARCHCTRL_CANCEL_BTN, onSearch );
93
94 treeSizer->Insert( 0, search, wxSizerFlags().Expand().Border( wxLEFT | wxRIGHT | wxTOP, 4 ) );
95 m_panelTree->Layout();
96 }
97
98 // Splice the thumbnail into the detail sizer between the summary label
99 // and the property list. Only construct when we can attach.
100 if( wxSizer* detailSizer = m_panelDetail->GetSizer() )
101 {
102 // Board on top, property list below, with a draggable sash. Gravity 1
103 // gives extra height to the board when the dialog grows.
104 wxSplitterWindow* detailSplitter =
105 new wxSplitterWindow( m_panelDetail, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSP_LIVE_UPDATE );
106 detailSplitter->SetSashGravity( 1.0 );
107 detailSplitter->SetMinimumPaneSize( 40 );
108
110
111 if( EDA_DRAW_FRAME* frame = dynamic_cast<EDA_DRAW_FRAME*>( aParent ) )
112 {
113 if( frame->GetCanvas() )
114 backend = frame->GetCanvas()->GetBackend();
115 }
116
117 m_canvas = new WIDGET_DIFF_CANVAS( detailSplitter, backend );
118
119 if( m_sheetSwitcher )
121
124
125 scene.referenceGeometry = std::move( aReferenceGeometry );
126 scene.comparisonGeometry = std::move( aComparisonGeometry );
128
129 const LSET geometryLayers = KICAD_DIFF::GeometryLayerSet( scene.referenceGeometry )
131
132 std::map<PCB_LAYER_ID, wxString> layerNames = scene.referenceGeometry.layerNames;
133
134 for( const auto& [layer, name] : scene.comparisonGeometry.layerNames )
135 layerNames[layer] = name;
136
137 m_filterSizer = new wxBoxSizer( wxHORIZONTAL );
138
139 // A board can have dozens of layers, so the checkboxes wrap rather than widen the dialog
140 wxWrapSizer* checkboxSizer = new wxWrapSizer( wxHORIZONTAL );
141
142 const std::array<std::pair<KICAD_DIFF::CATEGORY, wxString>, 4> categories{ {
143 { KICAD_DIFF::CATEGORY::ADDED, _( "Added" ) },
144 { KICAD_DIFF::CATEGORY::REMOVED, _( "Removed" ) },
145 { KICAD_DIFF::CATEGORY::MODIFIED, _( "Modified" ) },
146 { KICAD_DIFF::CATEGORY::CONFLICT, _( "Conflict" ) },
147 } };
148
149 for( const auto& [cat, label] : categories )
150 {
151 wxCheckBox* cb = new wxCheckBox( m_panelDetail, wxID_ANY, label );
152 cb->SetValue( true );
153 cb->Bind( wxEVT_CHECKBOX,
154 [this, cat]( wxCommandEvent& aEvent )
155 {
156 m_canvas->SetCategoryVisible( cat, aEvent.IsChecked() );
157 buildTree();
158 } );
159 checkboxSizer->Add( cb, wxSizerFlags().Border( wxRIGHT, 8 ).Centre() );
160 }
161
162 if( geometryLayers.any() )
163 {
164 checkboxSizer->Add( new wxStaticText( m_panelDetail, wxID_ANY, _( "Layers:" ) ),
165 wxSizerFlags().Border( wxLEFT | wxRIGHT, 4 ).Centre() );
166
167 for( PCB_LAYER_ID layer : geometryLayers.UIOrder() )
168 {
169 auto nameIt = layerNames.find( layer );
170 const wxString label = nameIt != layerNames.end() ? nameIt->second : LayerName( layer );
171 wxCheckBox* cb = new wxCheckBox( m_panelDetail, wxID_ANY, label );
172 cb->SetValue( true );
173 cb->Bind( wxEVT_CHECKBOX,
174 [this, layer]( wxCommandEvent& aEvent )
175 {
176 m_canvas->SetLayerVisible( layer, aEvent.IsChecked() );
177 } );
178 checkboxSizer->Add( cb, wxSizerFlags().Border( wxRIGHT, 8 ).Centre() );
179 }
180 }
181
182 m_filterSizer->Add( checkboxSizer, wxSizerFlags( 1 ).Centre() );
183
184 // Toggle the property list so the board can use the full detail panel.
185
186 wxCheckBox* propsToggle = new wxCheckBox( m_panelDetail, wxID_ANY, _( "Properties" ) );
187 propsToggle->SetValue( true );
188 propsToggle->Bind( wxEVT_CHECKBOX,
189 [this, detailSplitter]( wxCommandEvent& aEvent )
190 {
191 if( aEvent.IsChecked() && !detailSplitter->IsSplit() )
192 detailSplitter->SplitHorizontally( m_canvas, m_listProperties );
193 else if( !aEvent.IsChecked() && detailSplitter->IsSplit() )
194 detailSplitter->Unsplit( m_listProperties );
195 } );
196 m_filterSizer->Add( propsToggle, wxSizerFlags().Border( wxRIGHT, 8 ).Centre() );
197
198 // Move the property list out of the detail sizer and under the splitter
199 // so the sash can resize it. A small min height lets it shrink freely.
200 detailSizer->Detach( m_listProperties );
201 m_listProperties->Reparent( detailSplitter );
202 m_listProperties->SetMinSize( wxSize( -1, 40 ) );
203 detailSplitter->SplitHorizontally( m_canvas, m_listProperties, -200 );
204
205 propsToggle->SetValue( detailSplitter->IsSplit() );
206
207 size_t insertAt = std::min<size_t>( 1, detailSizer->GetItemCount() );
208 detailSizer->Insert( insertAt++, m_filterSizer, wxSizerFlags().Expand().Border( wxLEFT | wxRIGHT | wxTOP, 4 ) );
209 detailSizer->Insert( insertAt, detailSplitter, wxSizerFlags( 1 ).Expand().Border( wxALL, 4 ) );
210 m_panelDetail->Layout();
211
212 m_canvas->SetScene( std::move( scene ) );
213
214 m_canvas->SetPickHandler(
215 [this]( const std::optional<KIID_PATH>& aChangeId )
216 {
217 if( aChangeId )
218 {
219 m_suppressCenter = true;
220 selectChangeById( *aChangeId );
221 m_suppressCenter = false;
222 }
223 else if( m_treeChanges )
224 {
225 m_treeChanges->UnselectAll();
226 showChange( nullptr );
227 }
228 } );
229 }
230
231 buildTree();
232
233 if( m_diff.Empty() )
234 m_labelSummary->SetLabel( _( "No differences detected." ) );
235 else
236 m_labelSummary->SetLabel( wxString::Format(
237 wxPLURAL( "%zu change detected.", "%zu changes detected.", m_diff.Size() ), m_diff.Size() ) );
238
239 Layout();
240}
241
242
243void DIALOG_KICAD_DIFF::OnClose( wxCloseEvent& aEvent )
244{
245 EndModal( wxID_OK );
246}
247
248
249void DIALOG_KICAD_DIFF::OnOK( wxCommandEvent& aEvent )
250{
251 EndModal( wxID_OK );
252}
253
254
256{
257 wxTreeItemId sel = aEvent.GetItem();
258
259 if( !sel.IsOk() )
260 {
261 showChange( nullptr );
262 return;
263 }
264
265 auto it = m_changeByTreeId.find( reinterpret_cast<wxUIntPtr>( sel.GetID() ) );
266
267 if( it == m_changeByTreeId.end() )
268 {
269 showChange( nullptr ); // group node — no per-item details
270 return;
271 }
272
273 showChange( it->second );
274}
275
276
278{
279 // Snapshot the selected change so it can be restored after rebuild —
280 // wxTreeItemIds don't survive DeleteAllItems, so we re-find the node
281 // by its underlying KIID_PATH below.
282 std::optional<KIID_PATH> selectedId;
283
284 if( wxTreeItemId sel = m_treeChanges->GetSelection(); sel.IsOk() )
285 {
286 auto it = m_changeByTreeId.find( reinterpret_cast<wxUIntPtr>( sel.GetID() ) );
287
288 if( it != m_changeByTreeId.end() && it->second )
289 selectedId = it->second->id;
290 }
291
292 wxWindowUpdateLocker updateLock( m_treeChanges );
293
294 m_treeChanges->DeleteAllItems();
295 m_changeByTreeId.clear();
296
297 wxTreeItemId root = m_treeChanges->AddRoot( wxS( "root" ) );
298
299 // Per-category visibility comes from the canvas's checkbox row when
300 // present (the dialog hosts both a thumbnail canvas and the tree).
301 // With no canvas (degenerate test path) treat every category as visible.
302 std::array<bool, KICAD_DIFF::CATEGORY_COUNT> visibleCats{};
303 visibleCats.fill( true );
304
305 if( m_canvas )
306 {
307 for( std::size_t i = 0; i < KICAD_DIFF::CATEGORY_COUNT; ++i )
308 {
309 visibleCats[i] = m_canvas->IsCategoryVisible( static_cast<KICAD_DIFF::CATEGORY>( i ) );
310 }
311 }
312
315 {
316 wxTreeItemId groupNode = m_treeChanges->AppendItem( root, group.groupLabel );
317
318 for( const KICAD_DIFF::CHANGE_TREE_ENTRY& entry : group.entries )
319 {
320 wxTreeItemId itemNode = m_treeChanges->AppendItem( groupNode, entry.itemLabel );
321 m_changeByTreeId[reinterpret_cast<wxUIntPtr>( itemNode.GetID() )] = entry.change;
322 }
323
324 m_treeChanges->Expand( groupNode );
325 }
326
327 if( selectedId && !selectChangeById( *selectedId ) )
328 showChange( nullptr );
329
331}
332
333
334void DIALOG_KICAD_DIFF::OnTreeItemMenu( wxTreeEvent& aEvent )
335{
336 wxTreeItemId item = aEvent.GetItem();
337
338 if( !item.IsOk() )
339 return;
340
341 auto it = m_changeByTreeId.find( reinterpret_cast<wxUIntPtr>( item.GetID() ) );
342
343 if( it == m_changeByTreeId.end() || !it->second )
344 return; // group node, nothing to hide
345
346 const std::vector<KIID_PATH> ids = changeRowIds( *it->second );
347 const bool hidden = m_hiddenChanges.count( it->second->id ) > 0;
348
349 enum
350 {
351 ID_HIDE = wxID_HIGHEST + 1,
352 ID_SHOW,
353 ID_SHOW_ALL
354 };
355
356 wxMenu menu;
357
358 if( hidden )
359 menu.Append( ID_SHOW, _( "Show Change" ) );
360 else
361 menu.Append( ID_HIDE, _( "Hide Change" ) );
362
363 menu.Append( ID_SHOW_ALL, _( "Show All Hidden" ) );
364 menu.Enable( ID_SHOW_ALL, !m_hiddenChanges.empty() );
365
366 switch( m_treeChanges->GetPopupMenuSelectionFromUser( menu ) )
367 {
368 case ID_HIDE: m_hiddenChanges.insert( ids.begin(), ids.end() ); break;
369 case ID_SHOW:
370 for( const KIID_PATH& id : ids )
371 m_hiddenChanges.erase( id );
372 break;
373 case ID_SHOW_ALL: m_hiddenChanges.clear(); break;
374 default: return;
375 }
376
377 if( m_canvas )
378 m_canvas->SetHiddenChanges( m_hiddenChanges );
379
381}
382
383
384std::vector<KIID_PATH> DIALOG_KICAD_DIFF::changeRowIds( const KICAD_DIFF::ITEM_CHANGE& aChange ) const
385{
386 if( !KICAD_DIFF::IsRoutingNetChange( aChange ) )
387 return { aChange.id };
388
389 std::vector<KIID_PATH> ids;
390
391 std::function<void( const std::vector<KICAD_DIFF::ITEM_CHANGE>& )> walk =
392 [&]( const std::vector<KICAD_DIFF::ITEM_CHANGE>& aChanges )
393 {
394 for( const KICAD_DIFF::ITEM_CHANGE& c : aChanges )
395 {
396 if( KICAD_DIFF::IsRoutingNetChange( c ) && c.kind == aChange.kind && c.refdes == aChange.refdes )
397 {
398 ids.push_back( c.id );
399 }
400
401 walk( c.children );
402 }
403 };
404
405 walk( m_diff.changes );
406 return ids;
407}
408
409
411{
412 const wxColour grey = wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT );
413 const wxColour normal = wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOWTEXT );
414
415 for( const auto& [key, change] : m_changeByTreeId )
416 {
417 if( !change )
418 continue;
419
420 wxTreeItemId node( reinterpret_cast<void*>( key ) );
421 const bool hidden = m_hiddenChanges.count( change->id ) > 0;
422
423 m_treeChanges->SetItemTextColour( node, hidden ? grey : normal );
424 }
425}
426
427
428void DIALOG_KICAD_DIFF::SetRevisionChooser( const std::vector<wxString>& aLabels, int aSelected,
429 REVISION_HANDLER aOnChange )
430{
431 wxSizer* top = GetSizer();
432
433 if( !top || aLabels.empty() )
434 return;
435
436 m_revisionHandler = std::move( aOnChange );
437
438 wxBoxSizer* row = new wxBoxSizer( wxHORIZONTAL );
439 row->Add( new wxStaticText( this, wxID_ANY, _( "Revision:" ) ), wxSizerFlags().Border( wxRIGHT, 4 ).Centre() );
440
441 wxArrayString choices;
442
443 for( const wxString& label : aLabels )
444 choices.Add( label );
445
446 m_revisionChoice = new wxChoice( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, choices );
447 m_revisionChoice->SetSelection( aSelected );
448 row->Add( m_revisionChoice, wxSizerFlags( 1 ).Centre() );
449
450 wxButton* olderBtn = new wxButton( this, wxID_ANY, _( "Older" ), wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT );
451 wxButton* newerBtn = new wxButton( this, wxID_ANY, _( "Newer" ), wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT );
452 row->Add( olderBtn, wxSizerFlags().Border( wxLEFT, 4 ).Centre() );
453 row->Add( newerBtn, wxSizerFlags().Border( wxLEFT, 2 ).Centre() );
454
455 auto fire = [this]( int aIndex )
456 {
457 if( aIndex < 0 || aIndex >= static_cast<int>( m_revisionChoice->GetCount() ) )
458 return;
459
460 m_revisionChoice->SetSelection( aIndex );
461
463 m_revisionHandler( aIndex );
464 };
465
466 m_revisionChoice->Bind( wxEVT_CHOICE,
467 [fire]( wxCommandEvent& aEvent )
468 {
469 fire( aEvent.GetSelection() );
470 } );
471
472 // Snapshots are newest-first, so older steps up the index and newer down.
473 olderBtn->Bind( wxEVT_BUTTON,
474 [this, fire]( wxCommandEvent& )
475 {
476 fire( m_revisionChoice->GetSelection() + 1 );
477 } );
478 newerBtn->Bind( wxEVT_BUTTON,
479 [this, fire]( wxCommandEvent& )
480 {
481 fire( m_revisionChoice->GetSelection() - 1 );
482 } );
483
484 top->Insert( 0, row, wxSizerFlags().Expand().Border( wxALL, 4 ) );
485 Layout();
486}
487
488
490{
491 m_upHandler = std::move( aHandler );
492
493 if( !m_filterSizer || m_btnUp )
494 return;
495
496 m_btnUp = new wxBitmapButton( m_panelDetail, wxID_ANY, KiBitmapBundle( BITMAPS::up ) );
497 m_btnUp->SetToolTip( _( "Navigate up one sheet in the hierarchy" ) );
498 m_btnUp->Enable( false );
499
500 m_btnUp->Bind( wxEVT_BUTTON,
501 [this]( wxCommandEvent& )
502 {
503 if( m_upHandler )
504 m_upHandler();
505 } );
506
507 m_filterSizer->Insert( 0, m_btnUp, wxSizerFlags().Border( wxRIGHT, 8 ).Centre() );
508 m_panelDetail->Layout();
509}
510
511
512void DIALOG_KICAD_DIFF::EnableUp( bool aEnable )
513{
514 if( m_btnUp )
515 m_btnUp->Enable( aEnable );
516}
517
518
519void DIALOG_KICAD_DIFF::Reload( const wxString& aReferencePath, const wxString& aComparisonPath,
521 KICAD_DIFF::DOCUMENT_GEOMETRY aComparisonGeometry, SHEET_SWITCHER aSheetSwitcher,
522 KIID_PATH aInitialSheet )
523{
524 m_pathReference->SetLabel( aReferencePath );
525 m_pathComparison->SetLabel( aComparisonPath );
526
527 m_diff = std::move( aDiff );
528
529 // Outlives the canvas reconfigure below: it owns cloned items the view still references.
530 SHEET_SWITCHER previousSwitcher = std::move( m_sheetSwitcher );
531 m_sheetSwitcher = std::move( aSheetSwitcher );
532 m_currentCanvasSheet = std::move( aInitialSheet );
533
534 if( m_canvas )
535 {
536 // Hold rendering so reconfiguring the context and setting the new scene
537 // produce a single repaint instead of flashing an intermediate frame.
538 // EndUpdate must run even on a throw, or rendering stays frozen.
539 m_canvas->BeginUpdate();
540
541 try
542 {
543 m_canvas->HighlightChange( std::nullopt );
544
545 if( m_sheetSwitcher )
547
550
551 scene.referenceGeometry = std::move( aReferenceGeometry );
552 scene.comparisonGeometry = std::move( aComparisonGeometry );
554
555 m_canvas->SetScene( std::move( scene ) );
556 }
557 catch( ... )
558 {
559 m_canvas->EndUpdate();
560 throw;
561 }
562
563 m_canvas->EndUpdate();
564 }
565
566 buildTree();
567
568 if( m_diff.Empty() )
569 m_labelSummary->SetLabel( _( "No differences detected." ) );
570 else
571 m_labelSummary->SetLabel( wxString::Format(
572 wxPLURAL( "%zu change detected.", "%zu changes detected.", m_diff.Size() ), m_diff.Size() ) );
573
574 showChange( nullptr );
575 Layout();
576}
577
578
580{
581 if( !m_canvas || !m_sheetSwitcher )
582 return;
583
584 if( aSheetPath == m_currentCanvasSheet )
585 return;
586
587 m_currentCanvasSheet = aSheetPath;
588 m_sheetSwitcher( *m_canvas, aSheetPath );
589
590 if( m_treeChanges )
591 m_treeChanges->UnselectAll();
592
593 showChange( nullptr );
594}
595
596
598{
599 m_listProperties->DeleteAllItems();
600
602 m_changeSelectedFn( aChange ? aChange->id : KIID_PATH() );
603
604 if( !aChange )
605 {
606 m_labelSummary->SetLabel( _( "Select a change in the tree to view details" ) );
607
608 if( m_canvas )
609 m_canvas->HighlightChange( std::nullopt );
610
611 return;
612 }
613
614 m_labelSummary->SetLabel(
615 wxString::Format( wxS( "%s — %s" ), KICAD_DIFF::ChangeKindLabel( aChange->kind ), aChange->typeName ) );
616
617 if( m_canvas )
618 {
619 // Only auto-switch when the change carries a sheet prefix. Scoped
620 // diffs emit single-element ids with no sheet path.
621 if( aChange->id.size() > 1 )
622 {
623 KIID_PATH sheetPath = aChange->id;
624 sheetPath.pop_back();
625
626 if( sheetPath != m_currentCanvasSheet && m_sheetSwitcher )
627 {
628 m_currentCanvasSheet = sheetPath;
629 m_sheetSwitcher( *m_canvas, sheetPath );
630 }
631 }
632
633 m_canvas->HighlightChange( aChange->id );
634
635 if( !m_suppressCenter )
636 m_canvas->CenterOnHighlight();
637 }
638
639 long row = 0;
640 std::set<wxString> seen;
641 const EDA_UNITS units = GetUserUnits();
643
644 auto emitDelta = [&]( const wxString& aPrefix, const KICAD_DIFF::PROPERTY_DELTA& d )
645 {
646 const wxString label = aPrefix.IsEmpty() ? d.name : aPrefix + wxS( " / " ) + d.name;
647 const wxString beforeStr = d.before.ToDisplayString( units, iuScale );
648 const wxString afterStr = d.after.ToDisplayString( units, iuScale );
649 const wxString key = label + wxS( "|" ) + beforeStr + wxS( "|" ) + afterStr;
650
651 if( !seen.insert( key ).second )
652 return;
653
654 long idx = m_listProperties->InsertItem( row++, label );
655 m_listProperties->SetItem( idx, 1, beforeStr );
656 m_listProperties->SetItem( idx, 2, afterStr );
657 };
658
659 for( const KICAD_DIFF::PROPERTY_DELTA& d : aChange->properties )
660 emitDelta( wxEmptyString, d );
661
662 // When the clicked item carries its real diffs in children (e.g. a
663 // FOOTPRINT whose pad or field changed), surface those here so the panel
664 // is not empty for the parent.
665 std::function<void( const wxString&, const KICAD_DIFF::ITEM_CHANGE& )> walkChildren =
666 [&]( const wxString& aPrefix, const KICAD_DIFF::ITEM_CHANGE& aC )
667 {
668 for( const KICAD_DIFF::ITEM_CHANGE& child : aC.children )
669 {
670 wxString prefix = child.typeName;
671
672 if( child.refdes && !child.refdes->IsEmpty() )
673 prefix += wxS( " " ) + *child.refdes;
674
675 if( !aPrefix.IsEmpty() )
676 prefix = aPrefix + wxS( " / " ) + prefix;
677
678 for( const KICAD_DIFF::PROPERTY_DELTA& d : child.properties )
679 emitDelta( prefix, d );
680
681 walkChildren( prefix, child );
682 }
683 };
684
685 walkChildren( wxEmptyString, *aChange );
686}
687
688
690{
691 for( const auto& [treeIdNum, change] : m_changeByTreeId )
692 {
693 if( change && change->id == aChangeId )
694 {
695 wxTreeItemId treeId( reinterpret_cast<void*>( treeIdNum ) );
696
697 m_treeChanges->EnsureVisible( treeId );
698 m_treeChanges->SelectItem( treeId );
699 return true;
700 }
701 }
702
703 return false;
704}
const char * name
wxBitmapBundle KiBitmapBundle(BITMAPS aBitmap, int aMinHeight)
Definition bitmap.cpp:106
DIALOG_KICAD_DIFF_BASE(wxWindow *parent, wxWindowID id=wxID_ANY, const wxString &title=_("Compare Files"), const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxSize(900, 650), long style=wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER)
std::vector< KIID_PATH > changeRowIds(const KICAD_DIFF::ITEM_CHANGE &aChange) const
Change ids a tree row stands for.
SHEET_SWITCHER m_sheetSwitcher
void OnTreeSelectionChanged(wxTreeEvent &aEvent) override
std::set< KIID_PATH > m_hiddenChanges
Changes the user has muted via the tree's right-click menu.
void showChange(const KICAD_DIFF::ITEM_CHANGE *aChange)
Populate the property list for the change associated with the selected tree node, or clear the list i...
std::function< void(int aIndex)> REVISION_HANDLER
WIDGET_DIFF_CANVAS * m_canvas
GAL-backed canvas showing the DIFF_SCENE shape rectangles.
std::function< void()> UP_HANDLER
std::map< wxUIntPtr, const KICAD_DIFF::ITEM_CHANGE * > m_changeByTreeId
Maps tree item IDs to the underlying change record so selection can find the data without walking the...
void OnOK(wxCommandEvent &aEvent) override
void buildTree()
Populate the tree from m_diff. Groups by CHANGE_KIND.
void SetUpHandler(UP_HANDLER aHandler)
std::function< void(WIDGET_DIFF_CANVAS &, const KIID_PATH &)> SHEET_SWITCHER
wxString m_searchFilter
Lowercased free-text filter applied to typeName / refdes.
void applyHiddenToTree()
Grey out tree rows whose change is in m_hiddenChanges, restore the rest.
DIALOG_KICAD_DIFF(wxWindow *aParent, const wxString &aReferencePath, const wxString &aComparisonPath, const KICAD_DIFF::DOCUMENT_DIFF &aDiff, KICAD_DIFF::DOCUMENT_GEOMETRY aReferenceGeometry={}, KICAD_DIFF::DOCUMENT_GEOMETRY aComparisonGeometry={}, SHEET_SWITCHER aSheetSwitcher={}, KIID_PATH aInitialSheet={})
bool selectChangeById(const KIID_PATH &aChangeId)
Select the tree row whose change has the given KIID_PATH.
KICAD_DIFF::DOCUMENT_DIFF m_diff
wxBoxSizer * m_filterSizer
void OnClose(wxCloseEvent &aEvent) override
REVISION_HANDLER m_revisionHandler
void OnTreeItemMenu(wxTreeEvent &aEvent)
void SwitchCanvasToSheet(const KIID_PATH &aSheetPath)
CHANGE_SELECTED_FN m_changeSelectedFn
void EnableUp(bool aEnable)
bool m_suppressCenter
True while a canvas click is propagating into the tree, so the tree- selection handler skips re-cente...
void SetRevisionChooser(const std::vector< wxString > &aLabels, int aSelected, REVISION_HANDLER aOnChange)
Add a revision dropdown (with prev/next) at the top.
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.
void OptOut(wxWindow *aWindow)
Opt out of control state saving.
EDA_UNITS GetUserUnits() const
The base class for create windows for drawing purpose.
static constexpr GAL_TYPE GAL_FALLBACK
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
LSEQ UIOrder() const
Return the copper, technical and user layers in the order shown in layer widget.
Definition lset.cpp:739
GAL-backed canvas for visualizing a KICAD_DIFF::DIFF_SCENE.
#define _(s)
EDA_UNITS
Definition eda_units.h:44
wxString LayerName(int aLayer)
Returns the default display name for a given layer.
Definition layer_id.cpp:31
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
constexpr std::size_t CATEGORY_COUNT
Definition diff_scene.h:75
DIFF_SCENE BuildScene(const DOCUMENT_DIFF &aDiff, const DIFF_COLOR_THEME &aTheme)
Build a DIFF_SCENE from a DOCUMENT_DIFF, populating the shape lists and computing the union bbox.
LSET GeometryLayerSet(const DOCUMENT_GEOMETRY &aGeometry)
Return the union of every non-empty layer set carried by the geometry.
const EDA_IU_SCALE & IuScaleForDocKind(DOC_KIND aKind)
Internal-unit scale a document kind's coordinates are expressed in.
Definition diff_scene.h:256
std::vector< CHANGE_TREE_GROUP > BuildChangeTreeGroups(const DOCUMENT_DIFF &aDiff, const wxString &aSearchFilter, const std::array< bool, CATEGORY_COUNT > &aVisibleCategories)
Group the changes in a DOCUMENT_DIFF by kind, apply category and search filters, and return the resul...
wxString ChangeKindLabel(CHANGE_KIND aKind)
Human-readable label for a CHANGE_KIND (e.g.
void ExpandBBoxToGeometry(DIFF_SCENE &aScene)
Grow the scene's documentBBox to also include the extent of any background geometry.
bool IsRoutingNetChange(const ITEM_CHANGE &aChange)
Presentation predicate for PCB routing changes that should be displayed as one net-level entry/shape.
CATEGORY
Visual category each ITEM_CHANGE belongs to in the scene.
Definition diff_scene.h:52
DOC_KIND DocKindFromDocType(const wxString &aDocType)
Map a DOCUMENT_DIFF::docType tag written by the differs ("kicad_pcb", "kicad_sch",...
STL namespace.
One row in a grouped change tree — an ITEM_CHANGE plus its display label (typeName + optional refdes ...
const ITEM_CHANGE * change
wxString itemLabel
One bucket in a grouped change tree — a CHANGE_KIND, a human- readable group label,...
Diff colors with defaults for light backgrounds.
Definition diff_scene.h:278
DOCUMENT_GEOMETRY referenceGeometry
Background geometry from the two source documents.
Definition diff_scene.h:242
DOCUMENT_GEOMETRY comparisonGeometry
Definition diff_scene.h:243
The full set of changes between two parsed documents of one type.
Aggregate of background geometry extracted from one source document.
Definition diff_scene.h:163
std::map< PCB_LAYER_ID, wxString > layerNames
Board layer names, including user-assigned ones. Empty for documents without a board.
Definition diff_scene.h:169
One change record on a single item.
std::vector< PROPERTY_DELTA > properties
std::optional< wxString > refdes
std::vector< ITEM_CHANGE > children
Single (name, before, after) triple for one mutated property on an item.
KIBIS top(path, &reporter)