KiCad PCB EDA Suite
Loading...
Searching...
No Matches
hierarchy_pane.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) 2004 Jean-Pierre Charras, jp.charras at wanadoo.fr
5 * Copyright (C) 2008 Wayne Stambaugh <[email protected]>
6 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <https://www.gnu.org/licenses/>.
20 */
21
22#include <sch_render_settings.h>
23#include <bitmaps.h>
24#include <sch_edit_frame.h>
25#include <sch_commit.h>
27#include <schematic.h>
28#include <gal/color4d.h>
29#include <layer_ids.h>
30#include <tool/tool_manager.h>
31#include <tools/sch_actions.h>
32#include <hierarchy_pane.h>
34#include <kiface_base.h>
35#include <wx/object.h>
36#include <wx/generic/textdlgg.h>
37#include <wx/menu.h>
38#include <wx/wupdlock.h>
39#include <wx/msgdlg.h>
40
44class TREE_ITEM_DATA : public wxTreeItemData
45{
46public:
48
49 TREE_ITEM_DATA( SCH_SHEET_PATH& sheet ) : wxTreeItemData(), m_SheetPath( sheet ) {}
50};
51
52
53// Need to use wxRTTI macros in order for OnCompareItems to work properly
54// See: https://docs.wxwidgets.org/3.1/classwx_tree_ctrl.html#ab90a465793c291ca7aa827a576b7d146
56
57
58int HIERARCHY_TREE::OnCompareItems( const wxTreeItemId& item1, const wxTreeItemId& item2 )
59{
60 SCH_SHEET_PATH* item1Path = &static_cast<TREE_ITEM_DATA*>( GetItemData( item1 ) )->m_SheetPath;
61 SCH_SHEET_PATH* item2Path = &static_cast<TREE_ITEM_DATA*>( GetItemData( item2 ) )->m_SheetPath;
62
63 return item1Path->ComparePageNum( *item2Path );
64}
65
66
68 WX_PANEL( aParent )
69{
70 wxASSERT( dynamic_cast<SCH_EDIT_FRAME*>( aParent ) );
71
72 m_frame = aParent;
73
74 wxBoxSizer* sizer = new wxBoxSizer( wxVERTICAL );
75 SetSizer( sizer );
76 m_tree = new HIERARCHY_TREE( this );
77
78 wxVector<wxBitmapBundle> images;
79 images.push_back( KiBitmapBundle( BITMAPS::tree_nosel ) );
80 images.push_back( KiBitmapBundle( BITMAPS::tree_sel ) );
81 m_tree->SetImages( images );
82
83 sizer->Add( m_tree, 1, wxEXPAND, wxBORDER_NONE );
84
85 m_events_bound = false;
86 m_contextMenuOpen = false;
87
88 PROJECT_LOCAL_SETTINGS& localSettings = m_frame->Prj().GetLocalSettings();
89
90 for( const wxString& path : localSettings.m_SchHierarchyCollapsed )
91 m_collapsedPaths.insert( path );
92
94
95 // Enable selection events
96 Bind( wxEVT_TREE_ITEM_ACTIVATED, &HIERARCHY_PANE::onSelectSheetPath, this );
97 Bind( wxEVT_TREE_SEL_CHANGED, &HIERARCHY_PANE::onSelectSheetPath, this );
98 Bind( wxEVT_TREE_ITEM_RIGHT_CLICK, &HIERARCHY_PANE::onTreeItemRightClick, this );
99 Bind( wxEVT_CHAR_HOOK, &HIERARCHY_PANE::onCharHook, this );
100 m_tree->Bind( wxEVT_TREE_END_LABEL_EDIT, &HIERARCHY_PANE::onTreeEditFinished, this );
101 m_tree->Bind( wxEVT_CONTEXT_MENU, &HIERARCHY_PANE::onContextMenu, this );
102 m_events_bound = true;
103}
104
105
107{
108 // Cancel any in-progress label edit before unbinding. Destroying the tree while
109 // a text control is open causes a focus-loss event that fires onTreeEditFinished
110 // after the schematic has been torn down.
111 if( m_tree->GetEditControl() )
112 m_tree->EndEditLabel( m_tree->GetSelection(), true );
113
114 Unbind( wxEVT_TREE_ITEM_ACTIVATED, &HIERARCHY_PANE::onSelectSheetPath, this );
115 Unbind( wxEVT_TREE_SEL_CHANGED, &HIERARCHY_PANE::onSelectSheetPath, this );
116 Unbind( wxEVT_TREE_ITEM_RIGHT_CLICK, &HIERARCHY_PANE::onTreeItemRightClick, this );
117 Unbind( wxEVT_CHAR_HOOK, &HIERARCHY_PANE::onCharHook, this );
118 m_tree->Unbind( wxEVT_TREE_END_LABEL_EDIT, &HIERARCHY_PANE::onTreeEditFinished, this );
119 m_tree->Unbind( wxEVT_CONTEXT_MENU, &HIERARCHY_PANE::onContextMenu, this );
120}
121
122
123void HIERARCHY_PANE::buildHierarchyTree( SCH_SHEET_PATH* aList, const wxTreeItemId& aParent )
124{
125 std::vector<SCH_ITEM*> sheetChildren;
126 aList->LastScreen()->GetSheets( &sheetChildren );
127
128 for( SCH_ITEM* aItem : sheetChildren )
129 {
130 SCH_SHEET* sheet = static_cast<SCH_SHEET*>( aItem );
131 aList->push_back( sheet );
132
133 wxString sheetNameBase = sheet->GetField( FIELD_T::SHEET_NAME )->GetShownText( FOR_GUI );
134
135 // If the sheet name is empty, use the filename (without extension) as fallback
136 if( sheetNameBase.IsEmpty() )
137 {
138 wxFileName fn( sheet->GetFileName() );
139 sheetNameBase = fn.GetName();
140 }
141
142 wxString sheetName = formatPageString( sheetNameBase, aList->GetPageNumber() );
143 wxTreeItemId child = m_tree->AppendItem( aParent, sheetName, 0, 1 );
144 m_tree->SetItemData( child, new TREE_ITEM_DATA( *aList ) );
145
146 buildHierarchyTree( aList, child );
147 aList->pop_back();
148 }
149
150 m_tree->SortChildren( aParent );
151}
152
153
155{
156 bool eventsWereBound = m_events_bound;
157
158 if( eventsWereBound )
159 {
160 // Disable selection events
161 Unbind( wxEVT_TREE_ITEM_ACTIVATED, &HIERARCHY_PANE::onSelectSheetPath, this );
162 Unbind( wxEVT_TREE_SEL_CHANGED, &HIERARCHY_PANE::onSelectSheetPath, this );
163 Unbind( wxEVT_TREE_ITEM_RIGHT_CLICK, &HIERARCHY_PANE::onTreeItemRightClick, this );
164 m_tree->Unbind( wxEVT_CONTEXT_MENU, &HIERARCHY_PANE::onContextMenu, this );
165
166 m_events_bound = false;
167 }
168
169 std::function<void( const wxTreeItemId& )> recursiveDescent =
170 [&]( const wxTreeItemId& id )
171 {
172 wxCHECK_RET( id.IsOk(), wxT( "Invalid tree item" ) );
173
174 TREE_ITEM_DATA* itemData = static_cast<TREE_ITEM_DATA*>( m_tree->GetItemData( id ) );
175
176 // Skip items without data (e.g., project root node)
177 if( !itemData )
178 {
179 wxTreeItemIdValue cookie;
180 wxTreeItemId child = m_tree->GetFirstChild( id, cookie );
181
182 while( child.IsOk() )
183 {
184 recursiveDescent( child );
185 child = m_tree->GetNextChild( id, cookie );
186 }
187
188 return;
189 }
190
191 if( itemData->m_SheetPath == m_frame->GetCurrentSheet() )
192 {
193 wxTreeItemId parent = m_tree->GetItemParent( id );
194
195 if( parent.IsOk() )
196 {
197 // AT least on MSW, wxTreeCtrl::IsExpanded(item) and wxTreeCtrl::Expand(item)
198 // can be called only if item is visible.
199 // Otherwise wxWidgets alerts are thrown and Expand() say the item is invisible
200 if( m_tree->IsVisible( parent ) && !m_tree->IsExpanded( parent ) )
201 m_tree->Expand( parent );
202 }
203
204 if( !m_tree->IsVisible( id ) )
205 m_tree->EnsureVisible( id );
206
207 m_tree->SetItemBold( id, true );
208 m_tree->SetFocusedItem( id );
209 }
210 else
211 {
212 m_tree->SetItemBold( id, false );
213 }
214
215 wxTreeItemIdValue cookie;
216 wxTreeItemId child = m_tree->GetFirstChild( id, cookie );
217
218 while( child.IsOk() )
219 {
220 recursiveDescent( child );
221 child = m_tree->GetNextChild( id, cookie );
222 }
223 };
224
225 recursiveDescent( m_tree->GetRootItem() );
226
227 if( eventsWereBound )
228 {
229 // Enable selection events
230 Bind( wxEVT_TREE_ITEM_ACTIVATED, &HIERARCHY_PANE::onSelectSheetPath, this );
231 Bind( wxEVT_TREE_SEL_CHANGED, &HIERARCHY_PANE::onSelectSheetPath, this );
232 Bind( wxEVT_TREE_ITEM_RIGHT_CLICK, &HIERARCHY_PANE::onTreeItemRightClick, this );
233 m_tree->Bind( wxEVT_CONTEXT_MENU, &HIERARCHY_PANE::onContextMenu, this );
234
235 m_events_bound = true;
236 }
237}
238
239
241{
242 wxWindowUpdateLocker updateLock( this );
243
244 // If hierarchy hasn't been built yet (e.g., during frame construction before schematic
245 // is loaded), just return. The tree will be updated later when the schematic is loaded.
246 if( !m_frame->Schematic().HasHierarchy() )
247 return;
248
249 bool eventsWereBound = m_events_bound;
250
251 if( eventsWereBound )
252 {
253 // Disable selection events
254 Unbind( wxEVT_TREE_ITEM_ACTIVATED, &HIERARCHY_PANE::onSelectSheetPath, this );
255 Unbind( wxEVT_TREE_SEL_CHANGED, &HIERARCHY_PANE::onSelectSheetPath, this );
256 Unbind( wxEVT_TREE_ITEM_RIGHT_CLICK, &HIERARCHY_PANE::onTreeItemRightClick, this );
257 m_tree->Unbind( wxEVT_CONTEXT_MENU, &HIERARCHY_PANE::onContextMenu, this );
258
259 m_events_bound = false;
260 }
261
262 SCH_SHEET_LIST hierarchy = m_frame->Schematic().Hierarchy();
263 std::set<wxString> collapsedNodes = m_collapsedPaths;
264
265 std::function<void( const wxTreeItemId& )> getCollapsedNodes =
266 [&]( const wxTreeItemId& id )
267 {
268 wxCHECK_RET( id.IsOk(), wxT( "Invalid tree item" ) );
269
270 TREE_ITEM_DATA* itemData = static_cast<TREE_ITEM_DATA*>( m_tree->GetItemData( id ) );
271
272 // Skip items without data (e.g., project root node)
273 if( itemData && m_tree->ItemHasChildren( id ) && !m_tree->IsExpanded( id )
274 && hierarchy.HasPath( itemData->m_SheetPath.Path() ) )
275 {
276 collapsedNodes.emplace( itemData->m_SheetPath.PathAsString() );
277 return;
278 }
279
280 wxTreeItemIdValue cookie;
281 wxTreeItemId child = m_tree->GetFirstChild( id, cookie );
282
283 while( child.IsOk() )
284 {
285 getCollapsedNodes( child );
286 child = m_tree->GetNextChild( id, cookie );
287 }
288 };
289
290 // If we are clearing the tree, don't try to get collapsed nodes as they
291 // might be deleted
292 if( !aClear && !m_tree->IsEmpty() )
293 {
294 collapsedNodes.clear();
295 getCollapsedNodes( m_tree->GetRootItem() );
296 }
297
298 m_tree->DeleteAllItems();
299
300 // Create project root node (not associated with virtual root sheet)
301 wxTreeItemId projectRoot = m_tree->AddRoot( getRootString(), 0, 1 );
302 // Don't set item data for the project root - it doesn't correspond to a real sheet
303
304 // Get all top-level sheets
305 std::vector<SCH_SHEET*> topLevelSheets = m_frame->Schematic().GetTopLevelSheets();
306
307 // For each top-level sheet, build its hierarchy under the project root
308 for( SCH_SHEET* sheet : topLevelSheets )
309 {
310 if( sheet )
311 {
312 m_list.clear();
313 m_list.push_back( sheet );
314
315 wxString sheetNameBase = sheet->GetShownName( FOR_GUI );
316
317 // If the sheet name is empty, use the filename (without extension) as fallback
318 if( sheetNameBase.IsEmpty() && sheet->GetScreen() )
319 {
320 wxFileName fn( sheet->GetScreen()->GetFileName() );
321 sheetNameBase = fn.GetName();
322 }
323
324 // Create tree item for this top-level sheet
325 wxString sheetName = formatPageString( sheetNameBase, m_list.GetPageNumber() );
326 wxTreeItemId topLevelItem = m_tree->AppendItem( projectRoot, sheetName, 0, 1 );
327 m_tree->SetItemData( topLevelItem, new TREE_ITEM_DATA( m_list ) );
328
329 // Build hierarchy for this top-level sheet
330 buildHierarchyTree( &m_list, topLevelItem );
331 }
332 }
333
335
336 m_tree->ExpandAll();
337
338 std::function<void( const wxTreeItemId& )> collapseNodes =
339 [&]( const wxTreeItemId& id )
340 {
341 wxCHECK_RET( id.IsOk(), wxT( "Invalid tree item" ) );
342
343 TREE_ITEM_DATA* itemData =
344 static_cast<TREE_ITEM_DATA*>( m_tree->GetItemData( id ) );
345
346 if( id != projectRoot && itemData &&
347 collapsedNodes.find( itemData->m_SheetPath.PathAsString() ) != collapsedNodes.end() )
348 {
349 m_tree->Collapse( id );
350 return;
351 }
352
353 wxTreeItemIdValue cookie;
354 wxTreeItemId child = m_tree->GetFirstChild( id, cookie );
355
356 while( child.IsOk() )
357 {
358 collapseNodes( child );
359 child = m_tree->GetNextChild( id, cookie );
360 }
361 };
362
363 collapseNodes( projectRoot );
364 m_collapsedPaths = std::move( collapsedNodes );
365
366 if( !m_highlightedNet.IsEmpty() )
368
369 if( eventsWereBound )
370 {
371 // Enable selection events
372 Bind( wxEVT_TREE_ITEM_ACTIVATED, &HIERARCHY_PANE::onSelectSheetPath, this );
373 Bind( wxEVT_TREE_SEL_CHANGED, &HIERARCHY_PANE::onSelectSheetPath, this );
374 Bind( wxEVT_TREE_ITEM_RIGHT_CLICK, &HIERARCHY_PANE::onTreeItemRightClick, this );
375 m_tree->Bind( wxEVT_CONTEXT_MENU, &HIERARCHY_PANE::onContextMenu, this );
376
377 m_events_bound = true;
378 }
379}
380
381
382void HIERARCHY_PANE::onSelectSheetPath( wxTreeEvent& aEvent )
383{
384 wxTreeItemId itemSel = m_tree->GetSelection();
385
386 if( !itemSel.IsOk() )
387 return;
388
389 TREE_ITEM_DATA* itemData = static_cast<TREE_ITEM_DATA*>( m_tree->GetItemData( itemSel ) );
390
391 if( !itemData )
392 return;
393
394 SetCursor( wxCURSOR_ARROWWAIT );
395 m_frame->GetToolManager()->RunAction<SCH_SHEET_PATH*>( SCH_ACTIONS::changeSheet, &itemData->m_SheetPath );
396 SetCursor( wxCURSOR_ARROW );
397}
398
399
401{
402 // Update the labels of the hierarchical tree of the schematic.
403 // Must be called only for an up to date tree, to update displayed labels after
404 // a sheet name or a sheet number change.
405
406 std::function<void( const wxTreeItemId& )> updateLabel =
407 [&]( const wxTreeItemId& id )
408 {
409 TREE_ITEM_DATA* itemData = static_cast<TREE_ITEM_DATA*>( m_tree->GetItemData( id ) );
410
411 if( !itemData ) // happens if not shown in wxTreeCtrl m_tree (virtual sheet)
412 return;
413
414 SCH_SHEET* sheet = itemData->m_SheetPath.Last();
415 wxString sheetNameBase = sheet->GetField( FIELD_T::SHEET_NAME )->GetShownText( FOR_GUI );
416 wxString sheetName = formatPageString( sheetNameBase, itemData->m_SheetPath.GetPageNumber() );
417
418 if( m_tree->GetItemText( id ) != sheetName )
419 m_tree->SetItemText( id, sheetName );
420 };
421
422 wxTreeItemId rootId = m_tree->GetRootItem();
423 updateLabel( rootId );
424
425 std::function<void( const wxTreeItemId& )> recursiveDescent =
426 [&]( const wxTreeItemId& id )
427 {
428 wxCHECK_RET( id.IsOk(), wxT( "Invalid tree item" ) );
429 wxTreeItemIdValue cookie;
430 wxTreeItemId child = m_tree->GetFirstChild( id, cookie );
431
432 while( child.IsOk() )
433 {
434 updateLabel( child );
435 recursiveDescent( child );
436 child = m_tree->GetNextChild( id, cookie );
437 }
438 };
439
440 recursiveDescent( rootId );
441}
442
443
444std::vector<wxString> HIERARCHY_PANE::GetCollapsedPaths() const
445{
446 std::vector<wxString> collapsed;
447
448 if( m_tree->IsEmpty() )
449 return collapsed;
450
451 std::function<void( const wxTreeItemId& )> collect =
452 [&]( const wxTreeItemId& id )
453 {
454 wxCHECK_RET( id.IsOk(), wxT( "Invalid tree item" ) );
455
456 TREE_ITEM_DATA* itemData = static_cast<TREE_ITEM_DATA*>( m_tree->GetItemData( id ) );
457
458 if( id != m_tree->GetRootItem() && m_tree->ItemHasChildren( id )
459 && !m_tree->IsExpanded( id ) )
460 {
461 collapsed.push_back( itemData->m_SheetPath.PathAsString() );
462 return;
463 }
464
465 wxTreeItemIdValue cookie;
466 wxTreeItemId child = m_tree->GetFirstChild( id, cookie );
467
468 while( child.IsOk() )
469 {
470 collect( child );
471 child = m_tree->GetNextChild( id, cookie );
472 }
473 };
474
475 collect( m_tree->GetRootItem() );
476 return collapsed;
477}
478
479
480void HIERARCHY_PANE::onTreeItemRightClick( wxTreeEvent& aEvent )
481{
482 // wxEVT_CONTEXT_MENU fires after wxEVT_TREE_ITEM_RIGHT_CLICK for the same right-click,
483 // so set a guard to prevent showing the context menu twice.
484 m_contextMenuOpen = true;
485 onRightClick( aEvent.GetItem() );
486 m_contextMenuOpen = false;
487}
488
489
490void HIERARCHY_PANE::onContextMenu( wxContextMenuEvent& aEvent )
491{
492 // wxEVT_CONTEXT_MENU fires after wxEVT_TREE_ITEM_RIGHT_CLICK for the same right-click.
493 // Skip if the tree item handler already showed the menu.
495 return;
496
497 // Handle right-click in empty space
498 onRightClick( wxTreeItemId() );
499}
500
501
503{
504 // Adding or removing a top-level sheet drops the connection graph, and the emptied graph
505 // reads as minor, so no cleanup is needed to get a full rebuild out of this
507
508 m_frame->RecalculateConnections( &dummy, NO_CLEANUP );
509 m_frame->UpdateHierarchyNavigator();
510
511 // Removing the displayed sheet moves the current sheet without telling the canvas, which
512 // otherwise keeps drawing the deleted screen and the surviving page looks empty
513 if( m_frame->GetCurrentSheet() != aPreviousSheet )
514 m_frame->DisplayCurrentSheet();
515
516 m_frame->OnModify();
517}
518
519
520void HIERARCHY_PANE::onRightClick( wxTreeItemId aItem )
521{
522 wxMenu ctxMenu;
523 TREE_ITEM_DATA* itemData = nullptr;
524 bool isProjectRoot = false;
525
526 if( !aItem.IsOk() )
527 aItem = m_tree->GetSelection();
528
529 if( aItem.IsOk() )
530 {
531 itemData = static_cast<TREE_ITEM_DATA*>( m_tree->GetItemData( aItem ) );
532 isProjectRoot = ( m_tree->GetRootItem() == aItem.GetID() );
533 }
534
535 if( itemData )
536 {
537 ctxMenu.Append( EDIT_PAGE_NUMBER, _( "Edit Page Number" ) );
538 // The root item cannot be renamed
539 if( !isProjectRoot )
540 {
541 ctxMenu.Append( RENAME, _( "Rename" ), _( "Change name of this sheet" ) );
542
543 // Allow deleting top-level sheets (but not sub-sheets)
544 if( itemData->m_SheetPath.size() == 1 )
545 {
546 ctxMenu.Append( DELETE_TOP_LEVEL_SHEET, _( "Delete Top-Level Sheet" ),
547 _( "Remove this top-level sheet from the project" ) );
548 }
549 }
550
551 ctxMenu.AppendSeparator();
552 }
553
554 // Always allow creating new top-level sheets (root is hidden with wxTR_HIDE_ROOT)
555 ctxMenu.Append( NEW_TOP_LEVEL_SHEET, _( "New Top-Level Sheet" ), _( "Create a new top-level sheet" ) );
556 ctxMenu.AppendSeparator();
557
558 ctxMenu.Append( EXPAND_ALL, ACTIONS::expandAll.GetMenuItem() );
559 ctxMenu.Append( COLLAPSE_ALL, ACTIONS::collapseAll.GetMenuItem() );
560
561
562 int selected = GetPopupMenuSelectionFromUser( ctxMenu );
563
564 switch( selected )
565 {
567 {
568 // Create a new top-level sheet
569 wxTextEntryDialog dlg( m_frame, _( "Enter name for new top-level sheet:" ),
570 _( "New Top-Level Sheet" ),
571 _( "Untitled" ) );
572
573 if( dlg.ShowModal() == wxID_OK )
574 {
575 wxString newName = dlg.GetValue();
576
577 if( !newName.IsEmpty() )
578 {
579 SCH_SHEET_PATH previousSheet = m_frame->GetCurrentSheet();
580
581 // Create new sheet and screen
582 SCH_SHEET* newSheet = new SCH_SHEET( &m_frame->Schematic() );
583 SCH_SCREEN* newScreen = new SCH_SCREEN( &m_frame->Schematic() );
584
585 newSheet->SetScreen( newScreen );
586 newSheet->GetField( FIELD_T::SHEET_NAME )->SetText( newName );
587
588 // Generate a unique filename
589 wxString filename = newName;
590 filename.Replace( " ", "_" );
591 filename = filename.Lower();
592
593 if( !filename.EndsWith( ".kicad_sch" ) )
594 filename += ".kicad_sch";
595
596 newSheet->SetFileName( filename );
597 newScreen->SetFileName( filename );
598
599 // Find the lowest unused page number
600 SCH_SHEET_LIST hierarchy = m_frame->Schematic().Hierarchy();
601 int nextPage = 1;
602 wxString pageStr;
603
604 do
605 {
606 pageStr = wxString::Format( "%d", nextPage++ );
607 } while( hierarchy.PageNumberExists( pageStr ) );
608
609 m_frame->Schematic().AddTopLevelSheet( newSheet );
610
611 SCH_SHEET_PATH newSheetPath;
612 newSheetPath.push_back( newSheet );
613 newSheetPath.SetPageNumber( pageStr );
614
615 resyncAfterTopLevelSheetChange( previousSheet );
616 }
617 }
618 break;
619 }
620
622 {
623 if( itemData && itemData->m_SheetPath.size() == 1 )
624 {
625 SCH_SHEET* sheet = itemData->m_SheetPath.Last();
626
627 // Confirm deletion
628 wxString msg = wxString::Format( _( "Delete top-level sheet '%s'?\n\nThis cannot be undone." ),
629 sheet->GetName() );
630
631 if( wxMessageBox( msg, _( "Delete Top-Level Sheet" ), wxYES_NO | wxICON_QUESTION, m_frame ) == wxYES )
632 {
633 // Don't allow deleting the last top-level sheet
634 if( m_frame->Schematic().GetTopLevelSheets().size() <= 1 )
635 {
636 wxMessageBox( _( "Cannot delete the last top-level sheet." ), _( "Delete Top-Level Sheet" ),
637 wxOK | wxICON_ERROR, m_frame );
638 break;
639 }
640
641 SCH_SHEET_PATH previousSheet = m_frame->GetCurrentSheet();
642
643 // Remove from schematic
644 if( m_frame->Schematic().RemoveTopLevelSheet( sheet ) )
645 resyncAfterTopLevelSheetChange( previousSheet );
646 }
647 }
648 break;
649 }
650
651 case EDIT_PAGE_NUMBER:
652 {
653 wxString msg;
654 wxString sheetPath = itemData->m_SheetPath.PathHumanReadable( false, true );
655 wxString pageNumber = itemData->m_SheetPath.GetPageNumber();
656
657 msg.Printf( _( "Enter page number for sheet path %s" ),
658 ( sheetPath.Length() > 20 ) ? wxS( " \n" ) + sheetPath + wxT( ": " )
659 : sheetPath + wxT( ": " ) );
660
661 wxTextEntryDialog dlg( m_frame, msg, _( "Edit Sheet Page Number" ), pageNumber );
662
663 dlg.SetTextValidator( wxFILTER_ALPHANUMERIC ); // No white space.
664
665 if( dlg.ShowModal() == wxID_OK && dlg.GetValue() != itemData->m_SheetPath.GetPageNumber() )
666 {
667 SCH_COMMIT commit( m_frame );
668 SCH_SCREEN* modifyScreen = nullptr;
669
670 if( itemData->m_SheetPath.size() == 1 )
671 {
672 modifyScreen = m_frame->Schematic().Root().GetScreen();
673 }
674 else
675 {
676 SCH_SHEET_PATH parentPath = itemData->m_SheetPath;
677 parentPath.pop_back();
678 modifyScreen = parentPath.LastScreen();
679 }
680
681 commit.Modify( itemData->m_SheetPath.Last(), modifyScreen );
682
683 itemData->m_SheetPath.SetPageNumber( dlg.GetValue() );
684
685 if( itemData->m_SheetPath == m_frame->GetCurrentSheet() )
686 {
687 if( m_frame->GetScreen() )
688 {
689 m_frame->GetScreen()->SetPageNumber( dlg.GetValue() );
690 m_frame->OnPageSettingsChange();
691 }
692 }
693
694 commit.Push( _( "Change Page Number" ) );
695
697 }
698
699 break;
700 }
701 case EXPAND_ALL:
702 m_tree->ExpandAll();
703 break;
704 case COLLAPSE_ALL:
705 m_tree->CollapseAll();
706 break;
707 case RENAME:
708 m_tree->SetItemText( aItem, itemData->m_SheetPath.Last()->GetName() );
709 m_tree->EditLabel( aItem );
711 break;
712 }
713}
714
715
716void HIERARCHY_PANE::onTreeEditFinished( wxTreeEvent& event )
717{
718 // The frame is shutting down — schematic and current sheet state are no longer safe to access.
719 if( m_frame->IsClosing() )
720 {
721 event.Veto();
722 return;
723 }
724
725 TREE_ITEM_DATA* data = static_cast<TREE_ITEM_DATA*>( m_tree->GetItemData( event.GetItem() ) );
726 wxString newName = event.GetLabel();
727
728 if( data && data->m_SheetPath.Last() )
729 {
730 // The event needs to be rejected otherwise the SetItemText call below
731 // will be ineffective (the treeview item will hold the editor's content)
732 event.Veto();
733
734 // The editor holds only the page name as a text, while normally
735 // the tree items displaying it suffixed with the page number
736 if( !newName.IsEmpty() && data->m_SheetPath.Last()->GetName() != newName )
737 {
738 // The commit rebuilds the tree, which cannot happen inside this edit event; resolve the
739 // path again later since the schematic may be replaced first
740 CallAfter( [this, path = data->m_SheetPath.Path(), newName]()
741 {
742 if( m_frame->IsClosing() )
743 return;
744
745 std::optional<SCH_SHEET_PATH> current =
746 m_frame->Schematic().Hierarchy().GetSheetPathByKIIDPath( path );
747
748 if( current )
749 renameSheet( *current, newName );
750 } );
751 return;
752 }
753
754 m_tree->SetItemText( event.GetItem(), formatPageString( data->m_SheetPath.Last()->GetName(),
755 data->m_SheetPath.GetPageNumber() ) );
757 }
758}
759
760
761void HIERARCHY_PANE::renameSheet( const SCH_SHEET_PATH& aPath, const wxString& aNewName )
762{
763 SCH_COMMIT commit( m_frame );
764 SCH_SCREEN* modifyScreen = nullptr;
765
766 // For top-level sheets (size == 1), modify on the virtual root's screen
767 // For sub-sheets, modify on the parent sheet's screen
768 if( aPath.size() == 1 )
769 {
770 modifyScreen = m_frame->Schematic().Root().GetScreen();
771 }
772 else
773 {
774 const SCH_SHEET* parentSheet = aPath.GetSheet( aPath.size() - 2 );
775
776 if( parentSheet )
777 modifyScreen = parentSheet->GetScreen();
778 }
779
780 if( !modifyScreen )
781 return;
782
783 commit.Modify( aPath.Last()->GetField( FIELD_T::SHEET_NAME ), modifyScreen );
784
785 aPath.Last()->SetName( aNewName );
786
787 renameIdenticalSheets( aPath, aNewName, &commit );
788
789 if( !commit.Empty() )
790 commit.Push( _( "Rename Sheet" ) );
791
792 if( aPath == m_frame->GetCurrentSheet() )
793 m_frame->OnPageSettingsChange();
794}
795
796
797void HIERARCHY_PANE::onCharHook( wxKeyEvent& aKeyStroke )
798{
799 int hotkey = aKeyStroke.GetKeyCode();
800
801 int mods = aKeyStroke.GetModifiers();
802
803 // the flag wxMOD_ALTGR is defined in wxWidgets as wxMOD_CONTROL|wxMOD_ALT
804 // So AltGr key cannot used as modifier key because it is the same as Alt key + Ctrl key.
805#if CAN_USE_ALTGR_KEY
806 if( wxmods & wxMOD_ALTGR )
807 mods |= MD_ALTGR;
808 else
809#endif
810 {
811 if( mods & wxMOD_CONTROL )
812 hotkey += MD_CTRL;
813
814 if( mods & wxMOD_ALT )
815 hotkey += MD_ALT;
816 }
817
818 if( mods & wxMOD_SHIFT )
819 hotkey += MD_SHIFT;
820
821#ifdef wxMOD_META
822 if( mods & wxMOD_META )
823 hotkey += MD_META;
824#endif
825
826#ifdef wxMOD_WIN
827 if( mods & wxMOD_WIN )
828 hotkey += MD_SUPER;
829#endif
830
831 if( hotkey == ACTIONS::expandAll.GetHotKey()
832 || hotkey == ACTIONS::expandAll.GetHotKeyAlt() )
833 {
834 m_tree->ExpandAll();
835 return;
836 }
837 else if( hotkey == ACTIONS::collapseAll.GetHotKey()
838 || hotkey == ACTIONS::collapseAll.GetHotKeyAlt() )
839 {
840 m_tree->CollapseAll();
841 return;
842 }
843 else
844 {
845 aKeyStroke.Skip();
846 }
847}
848
849
851{
852 // Pane may be repainting while schematic is in flux
853 if ( !m_frame->Schematic().IsValid() )
854 return _( "Schematic" );
855
856 // Return the project name for the root node
857 wxString projectName = m_frame->Schematic().Project().GetProjectName();
858
859 if( projectName.IsEmpty() )
860 projectName = _( "Schematic" );
861
862 return projectName;
863}
864
865
866wxString HIERARCHY_PANE::formatPageString( const wxString& aName, const wxString& aPage )
867{
868 return aName + wxT( " " ) + wxString::Format( _( "(page %s)" ), aPage );
869}
870
871
872void HIERARCHY_PANE::UpdateNetHighlight( const wxString& aNetName )
873{
874 m_highlightedNet = aNetName;
875
876 KIGFX::COLOR4D netColor = m_frame->GetRenderSettings()->GetLayerColor( LAYER_BRIGHTENED );
877 const wxColour markText = netColor.ToColour();
878
879 std::set<wxString> sheetsWithNet;
880
881 if( !aNetName.IsEmpty() && m_frame->Schematic().IsValid() )
882 {
883 for( const KIID_PATH& path : SCH_CONNECTIVITY::NAVIGATION_QUERY( m_frame->Schematic() ).NetSheets( aNetName ) )
884 sheetsWithNet.insert( path.AsString() );
885 }
886
887 std::function<void( const wxTreeItemId& )> recurse =
888 [&]( const wxTreeItemId& id )
889 {
890 wxCHECK_RET( id.IsOk(), wxT( "Invalid tree item" ) );
891
892 TREE_ITEM_DATA* data = static_cast<TREE_ITEM_DATA*>( m_tree->GetItemData( id ) );
893
894 if( data )
895 {
896 bool mark = sheetsWithNet.count( data->m_SheetPath.Path().AsString() ) > 0;
897 m_tree->SetItemTextColour( id, mark ? markText : wxNullColour );
898 }
899
900 wxTreeItemIdValue cookie;
901 wxTreeItemId child = m_tree->GetFirstChild( id, cookie );
902
903 while( child.IsOk() )
904 {
905 recurse( child );
906 child = m_tree->GetNextChild( id, cookie );
907 }
908 };
909
910 if( m_tree->GetRootItem().IsOk() )
911 recurse( m_tree->GetRootItem() );
912}
913
915{
916 std::function<void( const wxTreeItemId& )> recursiveDescent =
917 [&]( const wxTreeItemId& id )
918 {
919 wxCHECK_RET( id.IsOk(), wxT( "Invalid tree item" ) );
920
921 TREE_ITEM_DATA* itemData = static_cast<TREE_ITEM_DATA*>( m_tree->GetItemData( id ) );
922
923 // Skip items without data (e.g., project root node)
924 if( itemData && itemData->m_SheetPath.Cmp( path ) != 0 && itemData->m_SheetPath.Last() == path.Last() )
925 {
926 wxFont font = m_tree->GetItemFont( id );
927 font.SetUnderlined( highLighted );
928 m_tree->SetItemFont( id, font );
929 }
930
931 wxTreeItemIdValue cookie;
932 wxTreeItemId child = m_tree->GetFirstChild( id, cookie );
933
934 while( child.IsOk() )
935 {
936 recursiveDescent( child );
937 child = m_tree->GetNextChild( id, cookie );
938 }
939 };
940
941 recursiveDescent( m_tree->GetRootItem() );
942}
943
945 const wxString newName, SCH_COMMIT* commit )
946{
947 std::function<void( const wxTreeItemId& )> recursiveDescent =
948 [&]( const wxTreeItemId& id )
949 {
950 wxCHECK_RET( id.IsOk(), wxT( "Invalid tree item" ) );
951
952 TREE_ITEM_DATA* data = static_cast<TREE_ITEM_DATA*>( m_tree->GetItemData( id ) );
953
954 // Skip items without data (e.g., project root node)
955 if( !data )
956 {
957 wxTreeItemIdValue cookie;
958 wxTreeItemId child = m_tree->GetFirstChild( id, cookie );
959
960 while( child.IsOk() )
961 {
962 recursiveDescent( child );
963 child = m_tree->GetNextChild( id, cookie );
964 }
965
966 return;
967 }
968
969 // Check if this is an identical sheet that needs renaming (but not the renamed sheet itself)
970 if( data->m_SheetPath.Cmp( renamedSheet ) != 0
971 && data->m_SheetPath.Last() == renamedSheet.Last() )
972 {
973 SCH_SCREEN* modifyScreen = nullptr;
974
975 // For top-level sheets (size == 1), modify on the virtual root's screen
976 // For sub-sheets, modify on the parent sheet's screen
977 if( data->m_SheetPath.size() == 1 )
978 {
979 modifyScreen = m_frame->Schematic().Root().GetScreen();
980 }
981 else
982 {
983 const SCH_SHEET* parentSheet = data->m_SheetPath.GetSheet( data->m_SheetPath.size() - 2 );
984 if( parentSheet )
985 modifyScreen = parentSheet->GetScreen();
986 }
987
988 if( modifyScreen )
989 {
991 modifyScreen );
992
993 data->m_SheetPath.Last()->SetName( newName );
994
995 if( data->m_SheetPath == m_frame->GetCurrentSheet() )
996 {
997 m_frame->OnPageSettingsChange();
998 }
999
1000 m_tree->SetItemText( id, formatPageString( data->m_SheetPath.Last()->GetName(),
1001 data->m_SheetPath.GetPageNumber() ) );
1002 }
1003 }
1004
1005 wxTreeItemIdValue cookie;
1006 wxTreeItemId child = m_tree->GetFirstChild( id, cookie );
1007
1008 while( child.IsOk() )
1009 {
1010 recursiveDescent( child );
1011 child = m_tree->GetNextChild( id, cookie );
1012 }
1013 };
1014
1015 recursiveDescent( m_tree->GetRootItem() );
1016}
wxBitmapBundle KiBitmapBundle(BITMAPS aBitmap, int aMinHeight)
Definition bitmap.cpp:106
static TOOL_ACTION expandAll
Definition actions.h:86
static TOOL_ACTION collapseAll
Definition actions.h:87
bool Empty() const
Definition commit.h:145
COMMIT & Modify(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr, RECURSE_MODE aRecurse=RECURSE_MODE::NO_RECURSE)
Modify a given item in the model.
Definition commit.h:102
SCH_EDIT_FRAME * m_frame
void onRightClick(wxTreeItemId aItem)
HIERARCHY_TREE * m_tree
void UpdateHierarchyTree(bool aClear=false)
Update the hierarchical tree of the schematic.
void onSelectSheetPath(wxTreeEvent &aEvent)
Open the selected sheet and display the corresponding screen when a tree item is selected.
void UpdateLabelsHierarchyTree()
Update the labels of the hierarchical tree of the schematic.
wxString formatPageString(const wxString &aName, const wxString &aPage)
wxString getRootString()
SCH_SHEET_PATH m_list
void UpdateHierarchySelection()
Updates the tree's selection to match current page.
wxString m_highlightedNet
HIERARCHY_PANE(SCH_EDIT_FRAME *aParent)
std::set< wxString > m_collapsedPaths
void onCharHook(wxKeyEvent &aKeyStroke)
void renameIdenticalSheets(const SCH_SHEET_PATH &renamedSheet, const wxString newName, SCH_COMMIT *commit)
Rename all sheets in a hierarchial desing which has the same source renamed sheet.
void buildHierarchyTree(SCH_SHEET_PATH *aList, const wxTreeItemId &aParent)
Create the hierarchical tree of the schematic.
void resyncAfterTopLevelSheetChange(const SCH_SHEET_PATH &aPreviousSheet)
Bring the editor back in sync after a top-level sheet was added or removed.
void onContextMenu(wxContextMenuEvent &aEvent)
void onTreeItemRightClick(wxTreeEvent &aEvent)
std::vector< wxString > GetCollapsedPaths() const
Returns a list of sheet paths for nodes that are currently collapsed.
void onTreeEditFinished(wxTreeEvent &event)
void UpdateNetHighlight(const wxString &aNetName)
void setIdenticalSheetsHighlighted(const SCH_SHEET_PATH &path, bool highLighted=true)
When renaming the sheets in tree it is helpful to highlight the identical sheets which got renamed by...
void renameSheet(const SCH_SHEET_PATH &aPath, const wxString &aNewName)
Navigation hierarchy tree control.
int OnCompareItems(const wxTreeItemId &item1, const wxTreeItemId &item2) override
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:101
wxColour ToColour() const
Definition color4d.cpp:221
wxString AsString() const
Definition kiid.cpp:423
The project local settings are things that are attached to a particular project, but also might be pa...
std::vector< wxString > m_SchHierarchyCollapsed
Collapsed nodes in the schematic hierarchy navigator.
static TOOL_ACTION changeSheet
Commit for the schematic and symbol editors.
Definition sch_commit.h:54
void Push(const wxString &aMessage=wxEmptyString, int aCommitFlags=0) override
Execute the changes.
One UI operation's hierarchy index.
std::set< KIID_PATH > NetSheets(const wxString &aName) const
Schematic editor (Eeschema) main window.
wxString GetShownText(const SCH_SHEET_PATH *aPath, RESOLUTION_CONTEXT aContext, const wxString &aVariantName=wxEmptyString, int aDepth=0) const
void SetText(const wxString &aText) override
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition sch_item.h:170
void SetFileName(const wxString &aFileName)
Set the file name for this screen to aFileName.
void GetSheets(std::vector< SCH_ITEM * > *aItems) const
Similar to Items().OfType( SCH_SHEET_T ), but return the sheets in a deterministic order (L-R,...
A container for handling SCH_SHEET_PATH objects in a flattened hierarchy.
bool PageNumberExists(const wxString &aPageNumber) const
bool HasPath(const KIID_PATH &aPath) const
Handle access to a stack of flattened SCH_SHEET objects by way of a path for creating a flattened sch...
const SCH_SHEET * GetSheet(unsigned aIndex) const
int ComparePageNum(const SCH_SHEET_PATH &aSheetPathToTest) const
Compare sheets by their page number.
KIID_PATH Path() const
Get the sheet path as an KIID_PATH.
SCH_SCREEN * LastScreen()
int Cmp(const SCH_SHEET_PATH &aSheetPathToTest) const
Compare if this is the same sheet path as aSheetPathToTest.
wxString GetPageNumber() const
wxString PathHumanReadable(bool aUseShortRootName=true, bool aStripTrailingSeparator=false, bool aEscapeSheetNames=false) const
Return the sheet path in a human readable form made from the sheet names.
wxString PathAsString() const
Return the path of time stamps which do not changes even when editing sheet parameters.
void SetPageNumber(const wxString &aPageNumber)
Set the sheet instance user definable page number.
SCH_SHEET * Last() const
Return a pointer to the last SCH_SHEET of the list.
void push_back(SCH_SHEET *aSheet)
Forwarded method from std::vector.
size_t size() const
Forwarded method from std::vector.
void pop_back()
Forwarded method from std::vector.
Sheet symbol placed in a schematic, and is the entry point for a sub schematic.
Definition sch_sheet.h:48
void SetFileName(const wxString &aFilename)
Definition sch_sheet.h:398
wxString GetFileName() const
Return the filename corresponding to this sheet.
Definition sch_sheet.h:392
SCH_FIELD * GetField(FIELD_T aFieldType)
Return a mandatory field in this sheet.
wxString GetName() const
Definition sch_sheet.h:148
void SetName(const wxString &aName)
Definition sch_sheet.h:149
SCH_SCREEN * GetScreen() const
Definition sch_sheet.h:151
void SetScreen(SCH_SCREEN *aScreen)
Set the SCH_SCREEN associated with this sheet to aScreen.
Store an SCH_SHEET_PATH of each sheet in hierarchy.
SCH_SHEET_PATH m_SheetPath
TREE_ITEM_DATA(SCH_SHEET_PATH &sheet)
WX_PANEL(wxWindow *parent, wxWindowID id=wxID_ANY, const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxSize(-1,-1), long style=wxTAB_TRAVERSAL, const wxString &name=wxEmptyString)
Definition wx_panel.cpp:24
@ FOR_GUI
Definition common.h:93
static void recursiveDescent(wxSizer *aSizer, std::map< int, wxString > &aLabels)
#define _(s)
wxIMPLEMENT_ABSTRACT_CLASS(HIERARCHY_TREE, wxTreeCtrl)
@ LAYER_BRIGHTENED
Definition layer_ids.h:519
@ NO_CLEANUP
Definition schematic.h:93
std::vector< FAB_LAYER_COLOR > dummy
std::string path
@ MD_META
Definition tool_event.h:143
@ MD_ALT
Definition tool_event.h:141
@ MD_CTRL
Definition tool_event.h:140
@ MD_SUPER
Definition tool_event.h:142
@ MD_ALTGR
Definition tool_event.h:144
@ MD_SHIFT
Definition tool_event.h:139