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