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/wupdlock.h>
45
46
47DIALOG_KICAD_DIFF::DIALOG_KICAD_DIFF( wxWindow* aParent, const wxString& aReferencePath,
48 const wxString& aComparisonPath, const KICAD_DIFF::DOCUMENT_DIFF& aDiff,
49 KICAD_DIFF::DOCUMENT_GEOMETRY aReferenceGeometry,
50 KICAD_DIFF::DOCUMENT_GEOMETRY aComparisonGeometry, SHEET_SWITCHER aSheetSwitcher,
51 KIID_PATH aInitialSheet ) :
52 DIALOG_KICAD_DIFF_BASE( aParent ),
53 m_diff( aDiff ),
54 m_sheetSwitcher( std::move( aSheetSwitcher ) ),
55 m_currentCanvasSheet( std::move( aInitialSheet ) )
56{
57 SetEscapeId( wxID_OK );
58
59 m_treeChanges->Bind( wxEVT_TREE_ITEM_MENU, &DIALOG_KICAD_DIFF::OnTreeItemMenu, this );
60
61 m_pathReference->SetLabel( aReferencePath );
62 m_pathComparison->SetLabel( aComparisonPath );
63
64 // Two property columns: name | before -> after
65 m_listProperties->AppendColumn( _( "Property" ), wxLIST_FORMAT_LEFT, 180 );
66 m_listProperties->AppendColumn( _( "Before" ), wxLIST_FORMAT_LEFT, 160 );
67 m_listProperties->AppendColumn( _( "After" ), wxLIST_FORMAT_LEFT, 160 );
68
69 // Splice a search box above the change tree. Empty filter = show all.
70 if( wxSizer* treeSizer = m_panelTree->GetSizer() )
71 {
72 wxSearchCtrl* search = new wxSearchCtrl( m_panelTree, wxID_ANY );
73 search->SetDescriptiveText( _( "Filter changes…" ) );
74 search->ShowCancelButton( true );
75
76 auto onSearch = [this, search]( wxCommandEvent& aEvent )
77 {
78 // BuildChangeTreeGroups lowercases internally; keep the
79 // raw input here so a future surface displaying the
80 // active filter (e.g. tooltip) preserves capitalization.
81 m_searchFilter = search->GetValue();
82 buildTree();
83 aEvent.Skip();
84 };
85
86 // wxSearchCtrl's cancel button fires its own event on some ports
87 // (notably macOS); binding both keeps the filter cleared whichever
88 // path the user takes to empty the box.
89 search->Bind( wxEVT_TEXT, onSearch );
90 search->Bind( wxEVT_SEARCHCTRL_CANCEL_BTN, onSearch );
91
92 treeSizer->Insert( 0, search, wxSizerFlags().Expand().Border( wxLEFT | wxRIGHT | wxTOP, 4 ) );
93 m_panelTree->Layout();
94 }
95
96 // Splice the thumbnail into the detail sizer between the summary label
97 // and the property list. Only construct when we can attach.
98 if( wxSizer* detailSizer = m_panelDetail->GetSizer() )
99 {
100 // Board on top, property list below, with a draggable sash. Gravity 1
101 // gives extra height to the board when the dialog grows.
102 wxSplitterWindow* detailSplitter =
103 new wxSplitterWindow( m_panelDetail, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSP_LIVE_UPDATE );
104 detailSplitter->SetSashGravity( 1.0 );
105 detailSplitter->SetMinimumPaneSize( 40 );
106
108
109 if( EDA_DRAW_FRAME* frame = dynamic_cast<EDA_DRAW_FRAME*>( aParent ) )
110 {
111 if( frame->GetCanvas() )
112 backend = frame->GetCanvas()->GetBackend();
113 }
114
115 m_canvas = new WIDGET_DIFF_CANVAS( detailSplitter, backend );
116
117 if( m_sheetSwitcher )
119
122
123 scene.referenceGeometry = std::move( aReferenceGeometry );
124 scene.comparisonGeometry = std::move( aComparisonGeometry );
126
127 const LSET geometryLayers = KICAD_DIFF::GeometryLayerSet( scene.referenceGeometry )
129
130 m_filterSizer = new wxBoxSizer( wxHORIZONTAL );
131
132 const std::array<std::pair<KICAD_DIFF::CATEGORY, wxString>, 4> categories{ {
133 { KICAD_DIFF::CATEGORY::ADDED, _( "Added" ) },
134 { KICAD_DIFF::CATEGORY::REMOVED, _( "Removed" ) },
135 { KICAD_DIFF::CATEGORY::MODIFIED, _( "Modified" ) },
136 { KICAD_DIFF::CATEGORY::CONFLICT, _( "Conflict" ) },
137 } };
138
139 for( const auto& [cat, label] : categories )
140 {
141 wxCheckBox* cb = new wxCheckBox( m_panelDetail, wxID_ANY, label );
142 cb->SetValue( true );
143 cb->Bind( wxEVT_CHECKBOX,
144 [this, cat]( wxCommandEvent& aEvent )
145 {
146 m_canvas->SetCategoryVisible( cat, aEvent.IsChecked() );
147 buildTree();
148 } );
149 m_filterSizer->Add( cb, wxSizerFlags().Border( wxRIGHT, 8 ).Centre() );
150 }
151
152 if( geometryLayers.any() )
153 {
154 m_filterSizer->Add( new wxStaticText( m_panelDetail, wxID_ANY, _( "Layers:" ) ),
155 wxSizerFlags().Border( wxLEFT | wxRIGHT, 4 ).Centre() );
156
157 for( PCB_LAYER_ID layer : geometryLayers.UIOrder() )
158 {
159 wxCheckBox* cb = new wxCheckBox( m_panelDetail, wxID_ANY, LayerName( layer ) );
160 cb->SetValue( true );
161 cb->Bind( wxEVT_CHECKBOX,
162 [this, layer]( wxCommandEvent& aEvent )
163 {
164 m_canvas->SetLayerVisible( layer, aEvent.IsChecked() );
165 } );
166 m_filterSizer->Add( cb, wxSizerFlags().Border( wxRIGHT, 8 ).Centre() );
167 }
168 }
169
170 // Toggle the property list so the board can use the full detail panel.
171 m_filterSizer->AddStretchSpacer();
172
173 wxCheckBox* propsToggle = new wxCheckBox( m_panelDetail, wxID_ANY, _( "Properties" ) );
174 propsToggle->SetValue( true );
175 propsToggle->Bind( wxEVT_CHECKBOX,
176 [this, detailSplitter]( wxCommandEvent& aEvent )
177 {
178 if( aEvent.IsChecked() && !detailSplitter->IsSplit() )
179 detailSplitter->SplitHorizontally( m_canvas, m_listProperties );
180 else if( !aEvent.IsChecked() && detailSplitter->IsSplit() )
181 detailSplitter->Unsplit( m_listProperties );
182 } );
183 m_filterSizer->Add( propsToggle, wxSizerFlags().Border( wxRIGHT, 8 ).Centre() );
184
185 // Move the property list out of the detail sizer and under the splitter
186 // so the sash can resize it. A small min height lets it shrink freely.
187 detailSizer->Detach( m_listProperties );
188 m_listProperties->Reparent( detailSplitter );
189 m_listProperties->SetMinSize( wxSize( -1, 40 ) );
190 detailSplitter->SplitHorizontally( m_canvas, m_listProperties, -200 );
191
192 propsToggle->SetValue( detailSplitter->IsSplit() );
193
194 size_t insertAt = std::min<size_t>( 1, detailSizer->GetItemCount() );
195 detailSizer->Insert( insertAt++, m_filterSizer, wxSizerFlags().Expand().Border( wxLEFT | wxRIGHT | wxTOP, 4 ) );
196 detailSizer->Insert( insertAt, detailSplitter, wxSizerFlags( 1 ).Expand().Border( wxALL, 4 ) );
197 m_panelDetail->Layout();
198
199 m_canvas->SetScene( std::move( scene ) );
200
201 m_canvas->SetPickHandler(
202 [this]( const std::optional<KIID_PATH>& aChangeId )
203 {
204 if( aChangeId )
205 {
206 m_suppressCenter = true;
207 selectChangeById( *aChangeId );
208 m_suppressCenter = false;
209 }
210 else if( m_treeChanges )
211 {
212 m_treeChanges->UnselectAll();
213 showChange( nullptr );
214 }
215 } );
216 }
217
218 buildTree();
219
220 if( m_diff.Empty() )
221 m_labelSummary->SetLabel( _( "No differences detected." ) );
222 else
223 m_labelSummary->SetLabel( wxString::Format( _( "%zu change(s) detected." ), m_diff.Size() ) );
224
225 Layout();
226}
227
228
229void DIALOG_KICAD_DIFF::OnClose( wxCloseEvent& aEvent )
230{
231 EndModal( wxID_OK );
232}
233
234
235void DIALOG_KICAD_DIFF::OnOK( wxCommandEvent& aEvent )
236{
237 EndModal( wxID_OK );
238}
239
240
242{
243 wxTreeItemId sel = aEvent.GetItem();
244
245 if( !sel.IsOk() )
246 {
247 showChange( nullptr );
248 return;
249 }
250
251 auto it = m_changeByTreeId.find( reinterpret_cast<wxUIntPtr>( sel.GetID() ) );
252
253 if( it == m_changeByTreeId.end() )
254 {
255 showChange( nullptr ); // group node — no per-item details
256 return;
257 }
258
259 showChange( it->second );
260}
261
262
264{
265 // Snapshot the selected change so it can be restored after rebuild —
266 // wxTreeItemIds don't survive DeleteAllItems, so we re-find the node
267 // by its underlying KIID_PATH below.
268 std::optional<KIID_PATH> selectedId;
269
270 if( wxTreeItemId sel = m_treeChanges->GetSelection(); sel.IsOk() )
271 {
272 auto it = m_changeByTreeId.find( reinterpret_cast<wxUIntPtr>( sel.GetID() ) );
273
274 if( it != m_changeByTreeId.end() && it->second )
275 selectedId = it->second->id;
276 }
277
278 wxWindowUpdateLocker updateLock( m_treeChanges );
279
280 m_treeChanges->DeleteAllItems();
281 m_changeByTreeId.clear();
282
283 wxTreeItemId root = m_treeChanges->AddRoot( wxS( "root" ) );
284
285 // Per-category visibility comes from the canvas's checkbox row when
286 // present (the dialog hosts both a thumbnail canvas and the tree).
287 // With no canvas (degenerate test path) treat every category as visible.
288 std::array<bool, KICAD_DIFF::CATEGORY_COUNT> visibleCats{};
289 visibleCats.fill( true );
290
291 if( m_canvas )
292 {
293 for( std::size_t i = 0; i < KICAD_DIFF::CATEGORY_COUNT; ++i )
294 {
295 visibleCats[i] = m_canvas->IsCategoryVisible( static_cast<KICAD_DIFF::CATEGORY>( i ) );
296 }
297 }
298
301 {
302 wxTreeItemId groupNode = m_treeChanges->AppendItem( root, group.groupLabel );
303
304 for( const KICAD_DIFF::CHANGE_TREE_ENTRY& entry : group.entries )
305 {
306 wxTreeItemId itemNode = m_treeChanges->AppendItem( groupNode, entry.itemLabel );
307 m_changeByTreeId[reinterpret_cast<wxUIntPtr>( itemNode.GetID() )] = entry.change;
308 }
309
310 m_treeChanges->Expand( groupNode );
311 }
312
313 if( selectedId && !selectChangeById( *selectedId ) )
314 showChange( nullptr );
315
317}
318
319
320void DIALOG_KICAD_DIFF::OnTreeItemMenu( wxTreeEvent& aEvent )
321{
322 wxTreeItemId item = aEvent.GetItem();
323
324 if( !item.IsOk() )
325 return;
326
327 auto it = m_changeByTreeId.find( reinterpret_cast<wxUIntPtr>( item.GetID() ) );
328
329 if( it == m_changeByTreeId.end() || !it->second )
330 return; // group node, nothing to hide
331
332 const std::vector<KIID_PATH> ids = changeRowIds( *it->second );
333 const bool hidden = m_hiddenChanges.count( it->second->id ) > 0;
334
335 enum
336 {
337 ID_HIDE = wxID_HIGHEST + 1,
338 ID_SHOW,
339 ID_SHOW_ALL
340 };
341
342 wxMenu menu;
343
344 if( hidden )
345 menu.Append( ID_SHOW, _( "Show Change" ) );
346 else
347 menu.Append( ID_HIDE, _( "Hide Change" ) );
348
349 menu.Append( ID_SHOW_ALL, _( "Show All Hidden" ) );
350 menu.Enable( ID_SHOW_ALL, !m_hiddenChanges.empty() );
351
352 switch( m_treeChanges->GetPopupMenuSelectionFromUser( menu ) )
353 {
354 case ID_HIDE: m_hiddenChanges.insert( ids.begin(), ids.end() ); break;
355 case ID_SHOW:
356 for( const KIID_PATH& id : ids )
357 m_hiddenChanges.erase( id );
358 break;
359 case ID_SHOW_ALL: m_hiddenChanges.clear(); break;
360 default: return;
361 }
362
363 if( m_canvas )
364 m_canvas->SetHiddenChanges( m_hiddenChanges );
365
367}
368
369
370std::vector<KIID_PATH> DIALOG_KICAD_DIFF::changeRowIds( const KICAD_DIFF::ITEM_CHANGE& aChange ) const
371{
372 if( !KICAD_DIFF::IsRoutingNetChange( aChange ) )
373 return { aChange.id };
374
375 std::vector<KIID_PATH> ids;
376
377 std::function<void( const std::vector<KICAD_DIFF::ITEM_CHANGE>& )> walk =
378 [&]( const std::vector<KICAD_DIFF::ITEM_CHANGE>& aChanges )
379 {
380 for( const KICAD_DIFF::ITEM_CHANGE& c : aChanges )
381 {
382 if( KICAD_DIFF::IsRoutingNetChange( c ) && c.kind == aChange.kind && c.refdes == aChange.refdes )
383 {
384 ids.push_back( c.id );
385 }
386
387 walk( c.children );
388 }
389 };
390
391 walk( m_diff.changes );
392 return ids;
393}
394
395
397{
398 const wxColour grey = wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT );
399 const wxColour normal = wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOWTEXT );
400
401 for( const auto& [key, change] : m_changeByTreeId )
402 {
403 if( !change )
404 continue;
405
406 wxTreeItemId node( reinterpret_cast<void*>( key ) );
407 const bool hidden = m_hiddenChanges.count( change->id ) > 0;
408
409 m_treeChanges->SetItemTextColour( node, hidden ? grey : normal );
410 }
411}
412
413
414void DIALOG_KICAD_DIFF::SetRevisionChooser( const std::vector<wxString>& aLabels, int aSelected,
415 REVISION_HANDLER aOnChange )
416{
417 wxSizer* top = GetSizer();
418
419 if( !top || aLabels.empty() )
420 return;
421
422 m_revisionHandler = std::move( aOnChange );
423
424 wxBoxSizer* row = new wxBoxSizer( wxHORIZONTAL );
425 row->Add( new wxStaticText( this, wxID_ANY, _( "Revision:" ) ), wxSizerFlags().Border( wxRIGHT, 4 ).Centre() );
426
427 wxArrayString choices;
428
429 for( const wxString& label : aLabels )
430 choices.Add( label );
431
432 m_revisionChoice = new wxChoice( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, choices );
433 m_revisionChoice->SetSelection( aSelected );
434 row->Add( m_revisionChoice, wxSizerFlags( 1 ).Centre() );
435
436 wxButton* olderBtn = new wxButton( this, wxID_ANY, _( "Older" ), wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT );
437 wxButton* newerBtn = new wxButton( this, wxID_ANY, _( "Newer" ), wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT );
438 row->Add( olderBtn, wxSizerFlags().Border( wxLEFT, 4 ).Centre() );
439 row->Add( newerBtn, wxSizerFlags().Border( wxLEFT, 2 ).Centre() );
440
441 auto fire = [this]( int aIndex )
442 {
443 if( aIndex < 0 || aIndex >= static_cast<int>( m_revisionChoice->GetCount() ) )
444 return;
445
446 m_revisionChoice->SetSelection( aIndex );
447
449 m_revisionHandler( aIndex );
450 };
451
452 m_revisionChoice->Bind( wxEVT_CHOICE,
453 [fire]( wxCommandEvent& aEvent )
454 {
455 fire( aEvent.GetSelection() );
456 } );
457
458 // Snapshots are newest-first, so older steps up the index and newer down.
459 olderBtn->Bind( wxEVT_BUTTON,
460 [this, fire]( wxCommandEvent& )
461 {
462 fire( m_revisionChoice->GetSelection() + 1 );
463 } );
464 newerBtn->Bind( wxEVT_BUTTON,
465 [this, fire]( wxCommandEvent& )
466 {
467 fire( m_revisionChoice->GetSelection() - 1 );
468 } );
469
470 top->Insert( 0, row, wxSizerFlags().Expand().Border( wxALL, 4 ) );
471 Layout();
472}
473
474
476{
477 m_upHandler = std::move( aHandler );
478
479 if( !m_filterSizer || m_btnUp )
480 return;
481
482 m_btnUp = new wxBitmapButton( m_panelDetail, wxID_ANY, KiBitmapBundle( BITMAPS::up ) );
483 m_btnUp->SetToolTip( _( "Navigate up one sheet in the hierarchy" ) );
484 m_btnUp->Enable( false );
485
486 m_btnUp->Bind( wxEVT_BUTTON,
487 [this]( wxCommandEvent& )
488 {
489 if( m_upHandler )
490 m_upHandler();
491 } );
492
493 m_filterSizer->Insert( 0, m_btnUp, wxSizerFlags().Border( wxRIGHT, 8 ).Centre() );
494 m_panelDetail->Layout();
495}
496
497
498void DIALOG_KICAD_DIFF::EnableUp( bool aEnable )
499{
500 if( m_btnUp )
501 m_btnUp->Enable( aEnable );
502}
503
504
505void DIALOG_KICAD_DIFF::Reload( const wxString& aReferencePath, const wxString& aComparisonPath,
507 KICAD_DIFF::DOCUMENT_GEOMETRY aComparisonGeometry, SHEET_SWITCHER aSheetSwitcher,
508 KIID_PATH aInitialSheet )
509{
510 m_pathReference->SetLabel( aReferencePath );
511 m_pathComparison->SetLabel( aComparisonPath );
512
513 m_diff = std::move( aDiff );
514
515 // Outlives the canvas reconfigure below: it owns cloned items the view still references.
516 SHEET_SWITCHER previousSwitcher = std::move( m_sheetSwitcher );
517 m_sheetSwitcher = std::move( aSheetSwitcher );
518 m_currentCanvasSheet = std::move( aInitialSheet );
519
520 if( m_canvas )
521 {
522 // Hold rendering so reconfiguring the context and setting the new scene
523 // produce a single repaint instead of flashing an intermediate frame.
524 // EndUpdate must run even on a throw, or rendering stays frozen.
525 m_canvas->BeginUpdate();
526
527 try
528 {
529 m_canvas->HighlightChange( std::nullopt );
530
531 if( m_sheetSwitcher )
533
536
537 scene.referenceGeometry = std::move( aReferenceGeometry );
538 scene.comparisonGeometry = std::move( aComparisonGeometry );
540
541 m_canvas->SetScene( std::move( scene ) );
542 }
543 catch( ... )
544 {
545 m_canvas->EndUpdate();
546 throw;
547 }
548
549 m_canvas->EndUpdate();
550 }
551
552 buildTree();
553
554 if( m_diff.Empty() )
555 m_labelSummary->SetLabel( _( "No differences detected." ) );
556 else
557 m_labelSummary->SetLabel( wxString::Format( _( "%zu change(s) detected." ), m_diff.Size() ) );
558
559 showChange( nullptr );
560 Layout();
561}
562
563
565{
566 if( !m_canvas || !m_sheetSwitcher )
567 return;
568
569 if( aSheetPath == m_currentCanvasSheet )
570 return;
571
572 m_currentCanvasSheet = aSheetPath;
573 m_sheetSwitcher( *m_canvas, aSheetPath );
574
575 if( m_treeChanges )
576 m_treeChanges->UnselectAll();
577
578 showChange( nullptr );
579}
580
581
583{
584 m_listProperties->DeleteAllItems();
585
587 m_changeSelectedFn( aChange ? aChange->id : KIID_PATH() );
588
589 if( !aChange )
590 {
591 m_labelSummary->SetLabel( _( "Select a change in the tree to view details" ) );
592
593 if( m_canvas )
594 m_canvas->HighlightChange( std::nullopt );
595
596 return;
597 }
598
599 m_labelSummary->SetLabel(
600 wxString::Format( wxS( "%s — %s" ), KICAD_DIFF::ChangeKindLabel( aChange->kind ), aChange->typeName ) );
601
602 if( m_canvas )
603 {
604 // Only auto-switch when the change carries a sheet prefix. Scoped
605 // diffs emit single-element ids with no sheet path.
606 if( aChange->id.size() > 1 )
607 {
608 KIID_PATH sheetPath = aChange->id;
609 sheetPath.pop_back();
610
611 if( sheetPath != m_currentCanvasSheet && m_sheetSwitcher )
612 {
613 m_currentCanvasSheet = sheetPath;
614 m_sheetSwitcher( *m_canvas, sheetPath );
615 }
616 }
617
618 m_canvas->HighlightChange( aChange->id );
619
620 if( !m_suppressCenter )
621 m_canvas->CenterOnHighlight();
622 }
623
624 long row = 0;
625 std::set<wxString> seen;
626
627 auto emitDelta = [&]( const wxString& aPrefix, const KICAD_DIFF::PROPERTY_DELTA& d )
628 {
629 const wxString label = aPrefix.IsEmpty() ? d.name : aPrefix + wxS( " / " ) + d.name;
630 const wxString beforeStr = d.before.ToDisplayString();
631 const wxString afterStr = d.after.ToDisplayString();
632 const wxString key = label + wxS( "|" ) + beforeStr + wxS( "|" ) + afterStr;
633
634 if( !seen.insert( key ).second )
635 return;
636
637 long idx = m_listProperties->InsertItem( row++, label );
638 m_listProperties->SetItem( idx, 1, beforeStr );
639 m_listProperties->SetItem( idx, 2, afterStr );
640 };
641
642 for( const KICAD_DIFF::PROPERTY_DELTA& d : aChange->properties )
643 emitDelta( wxEmptyString, d );
644
645 // When the clicked item carries its real diffs in children (e.g. a
646 // FOOTPRINT whose pad or field changed), surface those here so the panel
647 // is not empty for the parent.
648 std::function<void( const wxString&, const KICAD_DIFF::ITEM_CHANGE& )> walkChildren =
649 [&]( const wxString& aPrefix, const KICAD_DIFF::ITEM_CHANGE& aC )
650 {
651 for( const KICAD_DIFF::ITEM_CHANGE& child : aC.children )
652 {
653 wxString prefix = child.typeName;
654
655 if( child.refdes && !child.refdes->IsEmpty() )
656 prefix += wxS( " " ) + *child.refdes;
657
658 if( !aPrefix.IsEmpty() )
659 prefix = aPrefix + wxS( " / " ) + prefix;
660
661 for( const KICAD_DIFF::PROPERTY_DELTA& d : child.properties )
662 emitDelta( prefix, d );
663
664 walkChildren( prefix, child );
665 }
666 };
667
668 walkChildren( wxEmptyString, *aChange );
669}
670
671
673{
674 for( const auto& [treeIdNum, change] : m_changeByTreeId )
675 {
676 if( change && change->id == aChangeId )
677 {
678 wxTreeItemId treeId( reinterpret_cast<void*>( treeIdNum ) );
679
680 m_treeChanges->EnsureVisible( treeId );
681 m_treeChanges->SelectItem( treeId );
682 return true;
683 }
684 }
685
686 return false;
687}
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.
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)
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.
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
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,...
DOCUMENT_GEOMETRY referenceGeometry
Background geometry from the two source documents.
Definition diff_scene.h:239
DOCUMENT_GEOMETRY comparisonGeometry
Definition diff_scene.h:240
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
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)