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