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