KiCad PCB EDA Suite
Loading...
Searching...
No Matches
rule_editor_dialog_base.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) 2024 KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20
22#include <widgets/wx_infobar.h>
23#include <widgets/wx_panel.h>
24#include <widgets/ui_common.h>
25
26#include <wx/button.h>
27#include <wx/grid.h>
28#include <wx/sizer.h>
29#include <wx/treectrl.h>
30#include <wx/listctrl.h>
31#include <wx/stc/stc.h>
32#include <wx/menu.h>
33#include <wx/log.h>
34#include <wx/dragimag.h> // For wxDragImage
35#include <wx/wx.h>
36#include <wx/dnd.h>
37#include <wx/dcmemory.h> // Include for wxMemoryDC
38#include <wx/dcclient.h> // Include for wxClientDC if needed
39#include <wx/dcbuffer.h> // Include for double-buffered drawing
40#include <wx/bitmap.h>
41
42#include <confirm.h>
43#include <paths.h>
44#include <bitmaps.h>
45#include <launch_ext.h>
46#include <algorithm>
48
49
50static wxSearchCtrl* CreateTextFilterBox( wxWindow* aParent, const wxString& aDescriptiveText )
51{
52 wxSearchCtrl* search_widget = new wxSearchCtrl( aParent, wxID_ANY );
53
54 search_widget->ShowSearchButton( false );
55 search_widget->ShowCancelButton( true );
56
57 search_widget->SetDescriptiveText( aDescriptiveText );
58
59#ifdef __WXGTK__
60 // wxSearchCtrl vertical height is not calculated correctly on some GTK setups
61 // See https://gitlab.com/kicad/code/kicad/-/issues/9019
62 search_widget->SetMinSize( wxSize( -1, aParent->GetTextExtent( wxT( "qb" ) ).y + 10 ) );
63#endif
64
65 return search_widget;
66}
67
68
69RULE_EDITOR_DIALOG_BASE::RULE_EDITOR_DIALOG_BASE( wxWindow* aParent, const wxString& aTitle,
70 const wxSize& aInitialSize ) :
71 DIALOG_SHIM( aParent, wxID_ANY, aTitle, wxDefaultPosition, aInitialSize,
72 wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER ),
73 m_isDragging( false ),
74 m_enableMoveUp( false ),
75 m_enableMoveDown( false ),
76 m_enableAddRule( false ),
77 m_enableDuplicateRule( false ),
78 m_enableDeleteRule( false ),
80 m_title( aTitle ),
81 m_selectedData( nullptr ),
82 m_previousId( nullptr ),
83 m_draggedItem( nullptr ),
84 m_dragImage( nullptr )
85{
86 wxBoxSizer* mainSizer = new wxBoxSizer( wxVERTICAL );
87 SetSizer( mainSizer );
88
89 wxBoxSizer* infoBarSizer = new wxBoxSizer( wxHORIZONTAL );
90
91 m_infoBar = new WX_INFOBAR( this );
92 infoBarSizer->Add( m_infoBar, 0, wxEXPAND, 0 );
93
94 // Add the tree panel to the content sizer
95 m_splitter = new wxSplitterWindow( this, wxID_ANY, wxDefaultPosition, wxDefaultSize,
96 wxSP_3D | wxSP_LIVE_UPDATE | wxSP_3DSASH );
97 m_splitter->SetMinimumPaneSize( 50 ); // Set a minimum pane size
98
99 // Create the tree control panel
100 WX_PANEL* treeCtrlPanel =
101 new WX_PANEL( m_splitter, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE | wxTAB_TRAVERSAL );
102 treeCtrlPanel->SetBorders( true, true, true, true );
103 wxBoxSizer* treeCtrlSizer = new wxBoxSizer( wxVERTICAL );
104 treeCtrlPanel->SetSizer( treeCtrlSizer );
105
106 // Add a search text box above the tree control
107 m_filterSearch = CreateTextFilterBox( treeCtrlPanel, _( "Type filter text" ) );
108 treeCtrlSizer->Add( m_filterSearch, 0, wxEXPAND | wxBOTTOM, 5 );
109
110 // Create the tree control and set its font
111 m_ruleTreeCtrl = new wxTreeCtrl( treeCtrlPanel, wxID_ANY, wxDefaultPosition, wxDefaultSize,
112 wxTR_DEFAULT_STYLE | wxTR_HIDE_ROOT | wxTR_HAS_BUTTONS );
113 m_ruleTreeCtrl->SetFont( KIUI::GetControlFont( this ) );
114
115 // Adjust the tree control's window style to remove the border
116 long treeCtrlFlags = m_ruleTreeCtrl->GetWindowStyleFlag();
117 treeCtrlFlags = ( treeCtrlFlags & ~wxBORDER_MASK ) | wxBORDER_NONE;
118 m_ruleTreeCtrl->SetWindowStyleFlag( treeCtrlFlags );
119
120 // Add the tree control to its sizer
121 treeCtrlSizer->Add( m_ruleTreeCtrl, 1, wxEXPAND | wxBOTTOM, 5 );
122
123 // Create a sizer for the action buttons
124 wxBoxSizer* actionButtonsSizer = new wxBoxSizer( wxHORIZONTAL );
125
126 // Create the action buttons
128 new wxBitmapButton( treeCtrlPanel, wxID_ANY, KiBitmapBundle( BITMAPS::small_plus ),
129 wxDefaultPosition, wxDefaultSize, 0 );
130 m_copyRuleButton = new wxBitmapButton( treeCtrlPanel, wxID_ANY, KiBitmapBundle( BITMAPS::copy ),
131 wxDefaultPosition, wxDefaultSize, 0 );
133 new wxBitmapButton( treeCtrlPanel, wxID_ANY, KiBitmapBundle( BITMAPS::small_up ),
134 wxDefaultPosition, wxDefaultSize, 0 );
136 new wxBitmapButton( treeCtrlPanel, wxID_ANY, KiBitmapBundle( BITMAPS::small_down ),
137 wxDefaultPosition, wxDefaultSize, 0 );
139 new wxBitmapButton( treeCtrlPanel, wxID_ANY, KiBitmapBundle( BITMAPS::small_trash ),
140 wxDefaultPosition, wxDefaultSize, 0 );
141
142 actionButtonsSizer->Add( m_addRuleButton, 0, wxLEFT | wxRIGHT, 5 );
143 actionButtonsSizer->Add( m_copyRuleButton, 0, wxLEFT | wxRIGHT, 5 );
144 actionButtonsSizer->Add( m_moveTreeItemUpButton, 0, wxLEFT | wxRIGHT, 5 );
145 actionButtonsSizer->Add( m_moveTreeItemDownButton, 0, wxLEFT | wxRIGHT, 5 );
146
147 // Add a spacer between the "Move" buttons and the "Delete" button
148 actionButtonsSizer->AddStretchSpacer( 1 ); // Spacer between the Move and Delete buttons
149
150 // Add the "Delete" button at the end
151 actionButtonsSizer->Add( m_deleteRuleButton, 0, wxRIGHT, 5 );
152
153 // Add the action buttons sizer to the tree control sizer
154 treeCtrlSizer->Add( actionButtonsSizer, 0, wxBOTTOM | wxEXPAND, 5 );
155
156 treeCtrlPanel->Layout();
157
158 // Create the dynamic content panel
159 m_contentPanel = new wxPanel( m_splitter, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE );
160 m_contentPanel->SetBackgroundColour( *wxLIGHT_GREY );
161
162 // Add the tree control panel to the splitter.
163 // Calculate the minimum tree panel width based on the tree control's best size.
164 treeCtrlPanel->Layout();
165 int minTreeWidth = treeCtrlPanel->GetBestSize().GetWidth();
166
167 // Ensure a reasonable minimum width for the tree panel
168 if( minTreeWidth < 200 )
169 minTreeWidth = 200;
170
171 m_defaultSashPosition = minTreeWidth;
172
173 m_splitter->SplitVertically( treeCtrlPanel, m_contentPanel, m_defaultSashPosition );
174
175 // Set gravity to 0 so the tree panel maintains its size and the content panel resizes
176 m_splitter->SetSashGravity( 0.0 );
177
178 wxBoxSizer* m_sizerButtons = new wxBoxSizer( wxHORIZONTAL );
179
180 wxStdDialogButtonSizer* m_sdbSizer = new wxStdDialogButtonSizer();
181 m_cancelRuleButton = new wxButton( this, wxID_CANCEL );
182 m_cancelRuleButton->SetLabelText( _( "Close" ) );
183 m_sdbSizer->AddButton( m_cancelRuleButton );
184 m_saveRuleButton = new wxButton( this, wxID_OK );
185 m_saveRuleButton->SetLabelText( _( "Save" ) );
186 m_sdbSizer->AddButton( m_saveRuleButton );
187 m_sdbSizer->Realize();
188
189 m_sizerButtons->Add( m_sdbSizer, 1, wxEXPAND, 5 );
190
191
192 // Add the info bar sizer to the main sizer
193 mainSizer->Add( infoBarSizer, 0, wxEXPAND, 0 );
194 mainSizer->Add( m_splitter, 1, wxEXPAND | wxBOTTOM, 5 );
195 mainSizer->Add( m_sizerButtons, 0, wxALL | wxEXPAND, 5 );
196 SetSize( wxSize( 980, 680 ) );
197 Layout();
198
199 // Bind the context menu event
200 m_filterSearch->Bind( wxEVT_COMMAND_TEXT_UPDATED, &RULE_EDITOR_DIALOG_BASE::onFilterSearch,
201 this );
202 m_ruleTreeCtrl->Bind( wxEVT_TREE_ITEM_RIGHT_CLICK,
204 m_ruleTreeCtrl->Bind( wxEVT_TREE_SEL_CHANGED,
206 m_ruleTreeCtrl->Bind( wxEVT_TREE_ITEM_ACTIVATED,
211
214 this );
216 this );
217 m_moveTreeItemDownButton->Bind( wxEVT_BUTTON,
220 this );
221 m_saveRuleButton->Bind( wxEVT_BUTTON, &RULE_EDITOR_DIALOG_BASE::OnSave, this );
222 m_cancelRuleButton->Bind( wxEVT_BUTTON, &RULE_EDITOR_DIALOG_BASE::OnCancel, this );
223
224 this->Bind( wxEVT_SIZE, &RULE_EDITOR_DIALOG_BASE::onResize, this );
225
226 this->Bind( wxEVT_CLOSE_WINDOW, &RULE_EDITOR_DIALOG_BASE::onClose, this );
227}
228
229
231{
232 wxSize curSz = GetSize();
233 wxSize minSz = GetMinSize();
234
236
237 curSz = GetSize();
238 minSz = GetMinSize();
239}
240
241
243{
244 m_filterSearch->Unbind( wxEVT_COMMAND_TEXT_UPDATED, &RULE_EDITOR_DIALOG_BASE::onFilterSearch, this );
245 m_ruleTreeCtrl->Unbind( wxEVT_TREE_ITEM_RIGHT_CLICK, &RULE_EDITOR_DIALOG_BASE::onRuleTreeItemRightClick, this );
246 m_ruleTreeCtrl->Unbind( wxEVT_TREE_SEL_CHANGED, &RULE_EDITOR_DIALOG_BASE::onRuleTreeItemSelectionChanged, this );
247 m_ruleTreeCtrl->Unbind( wxEVT_TREE_ITEM_ACTIVATED, &RULE_EDITOR_DIALOG_BASE::onRuleTreeItemActivated, this );
248 m_ruleTreeCtrl->Unbind( wxEVT_LEFT_DOWN, &RULE_EDITOR_DIALOG_BASE::onRuleTreeItemLeftDown, this );
250 m_ruleTreeCtrl->Unbind( wxEVT_LEFT_UP, &RULE_EDITOR_DIALOG_BASE::onRuleTreeItemLeftUp, this );
251
257 m_saveRuleButton->Unbind( wxEVT_BUTTON, &RULE_EDITOR_DIALOG_BASE::OnSave, this );
258 m_cancelRuleButton->Unbind( wxEVT_BUTTON, &RULE_EDITOR_DIALOG_BASE::OnCancel, this );
259
260 this->Unbind( wxEVT_SIZE, &RULE_EDITOR_DIALOG_BASE::onResize, this );
261 this->Unbind( wxEVT_CLOSE_WINDOW, &RULE_EDITOR_DIALOG_BASE::onClose, this );
262
263 m_selectedData = nullptr;
264 m_previousId = nullptr;
265
266 if( m_dragImage )
267 {
268 delete m_dragImage;
269 m_dragImage = nullptr;
270 }
271}
272
273
275{
277
278 // Call TransferDataToWindow() only once:
279 // this is enough on wxWidgets 3.1
280 if( !DIALOG_SHIM::TransferDataToWindow() )
281 return false;
282
283 return true;
284}
285
286
288{
289 bool ret = true;
290
291 // Call TransferDataFromWindow() only once:
292 // this is enough on wxWidgets 3.1
293 if( !DIALOG_SHIM::TransferDataFromWindow() )
294 ret = false;
295
296 return ret;
297}
298
299
301{
302 while( aParent )
303 {
304 if( RULE_EDITOR_DIALOG_BASE* currentDialog =
305 dynamic_cast<RULE_EDITOR_DIALOG_BASE*>( aParent ) )
306 return currentDialog;
307
308 aParent = aParent->GetParent();
309 }
310
311 return nullptr;
312}
313
314
315void RULE_EDITOR_DIALOG_BASE::InitRuleTreeItems( const std::vector<RULE_TREE_NODE>& aNodes )
316{
317 if( aNodes.empty() )
318 return;
319
320 m_defaultTreeItems = aNodes;
321
322 wxTreeItemId rootId = m_ruleTreeCtrl->AddRoot( aNodes[0].m_nodeName );
323
324 RULE_TREE_ITEM_DATA* itemData =
325 new RULE_TREE_ITEM_DATA( aNodes[0].m_nodeId, nullptr, rootId );
326 m_ruleTreeCtrl->SetItemData( rootId, itemData );
327
328 std::vector<RULE_TREE_NODE> childNodes;
329 getRuleTreeChildNodes( aNodes, aNodes[0].m_nodeId, childNodes );
330
331 for( const auto& child : childNodes )
332 {
333 populateRuleTreeCtrl( aNodes, child, rootId );
334 }
335
336 m_treeHistoryData.clear();
337 saveRuleTreeState( m_ruleTreeCtrl->GetRootItem(), m_defaultTreeItems[0].m_nodeId );
338
339 m_ruleTreeCtrl->SelectItem( m_ruleTreeCtrl->GetFirstVisibleItem() );
340 m_ruleTreeCtrl->CollapseAll();
341}
342
343
344void RULE_EDITOR_DIALOG_BASE::SetContentPanel( wxPanel* aContentPanel )
345{
346 int sash_position = m_defaultSashPosition;
347
348 if( m_contentPanel )
349 {
350 sash_position = m_splitter->GetSashPosition();
351 m_splitter->Unsplit( m_contentPanel );
352 m_contentPanel->Destroy();
353 }
354
355 m_contentPanel = aContentPanel;
356 auto treeCtrlPanel = m_splitter->GetWindow1();
357 m_splitter->SplitVertically( treeCtrlPanel, m_contentPanel, sash_position );
358
359 Layout();
360 Refresh();
361}
362
363
365 wxTreeItemId aParent )
366{
367 wxTreeItemId currentTreeItemId = appendRuleTreeItem( aNode, aParent );
368
369 auto it = m_treeHistoryData.find( aNode.m_nodeData->GetParentId() );
370
371 if( it != m_treeHistoryData.end() )
372 {
373 std::vector<int>& existingChildren = std::get<1>( it->second );
374 existingChildren.push_back( aNode.m_nodeId );
375
376 m_treeHistoryData[aNode.m_nodeId] = { aNode.m_nodeName,
377 {},
378 currentTreeItemId };
379 }
380
381 m_ruleTreeCtrl->SelectItem( currentTreeItemId );
382}
383
384
385void RULE_EDITOR_DIALOG_BASE::UpdateRuleTreeItemText( wxTreeItemId aItemId, wxString aItemText )
386{
387 m_ruleTreeCtrl->SetItemText( aItemId, aItemText );
388}
389
390
392{
393 m_ruleTreeCtrl->Enable( aEnable );
394 m_filterSearch->Enable( aEnable );
395
397
399 m_cancelRuleButton->SetLabelText( aEnable ? _( "Close" ) : _( "Cancel" ) );
400}
401
402
403void RULE_EDITOR_DIALOG_BASE::DeleteRuleTreeItem( wxTreeItemId aItemId, const int& aNodeId )
404{
405 m_ruleTreeCtrl->Delete( aItemId );
406 m_treeHistoryData.erase( aNodeId );
407}
408
409
411 const std::vector<RULE_TREE_NODE>& aNodes, const RULE_TREE_NODE& aNode,
412 wxTreeItemId aParent )
413{
414 wxTreeItemId currentId = appendRuleTreeItem( aNode, aParent );
415
416 std::vector<RULE_TREE_NODE> childNodes;
417 getRuleTreeChildNodes( aNodes, aNode.m_nodeId, childNodes );
418
419 for( const auto& child : childNodes )
420 {
421 populateRuleTreeCtrl( aNodes, child, currentId );
422 }
423
424 m_ruleTreeCtrl->Expand( currentId );
425}
426
427
429{
430 wxTreeItemId item = aEvent.GetItem();
431
432 if( !item.IsOk() )
433 return;
434
435 m_ruleTreeCtrl->SelectItem( item );
436
437 RULE_TREE_ITEM_DATA* ruleTreeItemData =
438 dynamic_cast<RULE_TREE_ITEM_DATA*>( m_ruleTreeCtrl->GetItemData( item ) );
439
440 if( !ruleTreeItemData )
441 return;
442
443 updateRuleTreeActionButtonsState( ruleTreeItemData );
444
445 wxMenu menu;
446
447 if( m_enableAddRule )
448 KIUI::AddMenuItem( &menu, ID_NEWRULE, _( "New Rule" ), KiBitmap( BITMAPS::small_plus ) );
449
450 if( m_enableAddRule )
451 KIUI::AddMenuItem( &menu, ID_COPYRULE, _( "Copy Rule" ), KiBitmap( BITMAPS::copy ) );
452
454 KIUI::AddMenuItem( &menu, ID_DELETERULE, _( "Delete Rule" ),
456
457 if( isEnabled( ruleTreeItemData,
459 && m_enableMoveUp )
460 KIUI::AddMenuItem( &menu, ID_MOVEUP, _( "Move Up" ), KiBitmap( BITMAPS::small_up ) );
461
462 if( isEnabled( ruleTreeItemData,
465 KIUI::AddMenuItem( &menu, ID_MOVEDOWN, _( "Move Down" ), KiBitmap( BITMAPS::small_down ) );
466
467 menu.Bind( wxEVT_COMMAND_MENU_SELECTED,
468 [&]( wxCommandEvent& aCmd )
469 {
470 switch( aCmd.GetId() )
471 {
472 case ID_NEWRULE: onNewRuleOptionClick( aCmd ); break;
473
474 case ID_COPYRULE: onDuplicateRuleOptionClick( aCmd ); break;
475
476 case ID_DELETERULE: onDeleteRuleOptionClick( aCmd ); break;
477
478 case ID_MOVEUP: onMoveUpRuleOptionClick( aCmd ); break;
479
480 case ID_MOVEDOWN: onMoveDownRuleOptionClick( aCmd ); break;
481 default: aCmd.Skip();
482 }
483 } );
484
485 PopupMenu( &menu );
486}
487
488
490{
491 wxTreeItemId selectedItem = aEvent.GetItem();
492
493 RULE_TREE_ITEM_DATA* selectedItemData =
494 dynamic_cast<RULE_TREE_ITEM_DATA*>( m_ruleTreeCtrl->GetItemData( selectedItem ) );
495
496 if( !selectedItemData )
497 return;
498
499 if( m_selectedData )
500 {
501 if( m_selectedData->GetNodeId() == selectedItemData->GetNodeId() )
502 {
503 m_selectedData = selectedItemData;
504 updateRuleTreeActionButtonsState( selectedItemData );
505 return;
506 }
507
508 m_previousId = m_selectedData->GetTreeItemId();
509 }
510
511 m_selectedData = selectedItemData;
512
514
515 updateRuleTreeActionButtonsState( selectedItemData );
516}
517
519{
520 RULE_TREE_ITEM_DATA* itemData =
521 dynamic_cast<RULE_TREE_ITEM_DATA*>( m_ruleTreeCtrl->GetItemData( aEvent.GetItem() ) );
522
523 if( itemData &&
525 {
526 AddNewRule( itemData );
527 }
528}
529
530
532{
534}
535
536
541
542
544{
545 RemoveRule( m_selectedData->GetNodeId() );
546}
547
548
550{
551 if( !m_enableMoveUp )
552 return;
553
554 wxTreeItemId selectedItem = m_ruleTreeCtrl->GetSelection();
555 if( !selectedItem.IsOk() )
556 return;
557
558 wxTreeItemId parent = m_ruleTreeCtrl->GetItemParent( selectedItem );
559
560 if( !parent.IsOk() )
561 return;
562
563 wxTreeItemIdValue cookie;
564 wxTreeItemId current = m_ruleTreeCtrl->GetFirstChild( parent, cookie );
565 wxTreeItemId previous;
566 wxTreeItemId beforePrevious;
567
568 while( current.IsOk() && current != selectedItem )
569 {
570 beforePrevious = previous;
571 previous = current;
572 current = m_ruleTreeCtrl->GetNextChild( parent, cookie );
573 }
574
575 if( !previous.IsOk() )
576 {
577 return;
578 }
579
580 wxTreeItemId insertPosition = beforePrevious.IsOk() ? beforePrevious : wxTreeItemId();
581
582 wxTreeItemId newItem = m_ruleTreeCtrl->InsertItem(
583 parent, insertPosition, m_ruleTreeCtrl->GetItemText( selectedItem ) );
584
585 moveRuleTreeItemChildrensTooOnDrag( selectedItem, newItem );
586
587 wxTreeItemData* itemData = m_ruleTreeCtrl->GetItemData( selectedItem );
588
589 if( itemData )
590 {
591 RULE_TREE_ITEM_DATA* ruleTreeItemData = dynamic_cast<RULE_TREE_ITEM_DATA*>( itemData );
592 ruleTreeItemData->SetTreeItemId( newItem );
593
594 m_ruleTreeCtrl->SetItemData( newItem, itemData );
595 m_ruleTreeCtrl->SetItemData( selectedItem, nullptr ); // Detach data from the old item
596
597 saveRuleTreeState( newItem, ruleTreeItemData->GetNodeId() );
598
599 m_ruleTreeCtrl->SelectItem( newItem );
600
601 m_ruleTreeCtrl->Expand( newItem );
602 m_ruleTreeCtrl->ExpandAllChildren( newItem );
603 }
604
605 m_ruleTreeCtrl->DeleteChildren( selectedItem );
606 m_ruleTreeCtrl->Delete( selectedItem );
607}
608
609
611{
612 if( !m_enableMoveDown )
613 return;
614
615 wxTreeItemId selectedItem = m_ruleTreeCtrl->GetSelection();
616
617 if( !selectedItem.IsOk() )
618 return;
619
620 wxTreeItemId parent = m_ruleTreeCtrl->GetItemParent( selectedItem );
621 wxTreeItemIdValue cookie;
622 wxTreeItemId current = m_ruleTreeCtrl->GetFirstChild( parent, cookie );
623
624 while( current.IsOk() && current != selectedItem )
625 {
626 current = m_ruleTreeCtrl->GetNextChild( parent, cookie );
627 }
628
629 wxTreeItemId next = m_ruleTreeCtrl->GetNextChild( parent, cookie );
630
631 if( !next.IsOk() )
632 {
633 return;
634 }
635
636 wxString itemText = m_ruleTreeCtrl->GetItemText( selectedItem );
637 wxTreeItemData* itemData = m_ruleTreeCtrl->GetItemData( selectedItem );
638
639 wxTreeItemId newItem = m_ruleTreeCtrl->InsertItem( parent, next, itemText );
640
641 moveRuleTreeItemChildrensTooOnDrag( selectedItem, newItem );
642
643 if( itemData )
644 {
645 RULE_TREE_ITEM_DATA* ruleTreeItemData = dynamic_cast<RULE_TREE_ITEM_DATA*>( itemData );
646 ruleTreeItemData->SetTreeItemId( newItem );
647
648 m_ruleTreeCtrl->SetItemData( newItem, itemData );
649 m_ruleTreeCtrl->SetItemData( selectedItem, nullptr ); // Detach data
650
651 saveRuleTreeState( newItem, ruleTreeItemData->GetNodeId() );
652
653 m_ruleTreeCtrl->SelectItem( newItem );
654
655 m_ruleTreeCtrl->Expand( newItem );
656 }
657
658 m_ruleTreeCtrl->DeleteChildren( selectedItem );
659 m_ruleTreeCtrl->Delete( selectedItem );
660}
661
662
664{
665 wxPoint pt = aEvent.GetPosition();
666 int flags;
667 wxTreeItemId item = m_ruleTreeCtrl->HitTest( pt, flags );
668
669 // Check if click is on the expand/collapse button
670 if( flags & wxTREE_HITTEST_ONITEMBUTTON )
671 {
672 // Prevent wxEVT_LEFT_DOWN from propagating further
673 aEvent.Skip();
674 return;
675 }
676
677 if( item.IsOk() )
678 {
679 m_draggedItem = item;
680
681 wxString text = m_ruleTreeCtrl->GetItemText( item );
682 wxMemoryDC dc;
683 wxBitmap bitmap( 200, 30 );
684 dc.SelectObject( bitmap );
685 dc.SetBackground( *wxWHITE_BRUSH );
686 dc.Clear();
687 dc.SetFont( *wxNORMAL_FONT );
688 dc.DrawText( text, 5, 5 );
689 dc.SelectObject( wxNullBitmap );
690
691 m_dragImage = new wxDragImage( bitmap );
692 m_isDragging = false;
693 }
694
695 aEvent.Skip();
696}
697
698
700{
701 if( aEvent.Dragging() && m_draggedItem.IsOk() && m_dragImage )
702 {
703 wxPoint currentPos = aEvent.GetPosition();
704
705 if( !m_isDragging )
706 {
707 if( !m_dragImage->BeginDrag( wxPoint( 0, 0 ), m_ruleTreeCtrl, true ) )
708 {
709 delete m_dragImage;
710 m_dragImage = nullptr;
711 return;
712 }
713
714 m_dragImage->Show();
715 m_isDragging = true;
716 }
717 else
718 {
719 m_dragImage->Move( currentPos );
720 }
721 }
722
723 aEvent.Skip();
724}
725
726
728{
729 if( m_draggedItem.IsOk() && m_isDragging )
730 {
731 if( m_dragImage )
732 {
733 m_dragImage->Hide();
734 m_dragImage->EndDrag();
735 delete m_dragImage;
736 m_dragImage = nullptr;
737 m_isDragging = false;
738 }
739
740 wxPoint pos = aEvent.GetPosition();
741 int flags;
742 wxTreeItemId targetItem = m_ruleTreeCtrl->HitTest( pos, flags );
743
744 if( !targetItem.IsOk() )
745 return;
746
747 wxTreeItemId draggedParent = m_ruleTreeCtrl->GetItemParent( m_draggedItem );
748 wxTreeItemId targetParent = m_ruleTreeCtrl->GetItemParent( targetItem );
749
750 if( draggedParent != targetParent )
751 return;
752
753 if( draggedParent == targetParent )
754 {
755 wxTreeItemId newItem;
756 wxTreeItemIdValue cookie;
757
758 if( flags & wxTREE_HITTEST_ONITEMLABEL
759 && targetItem == m_ruleTreeCtrl->GetFirstChild( targetParent, cookie ) )
760 {
761 // Prepend item as the first child of target parent
762 newItem = m_ruleTreeCtrl->PrependItem(
763 targetParent, m_ruleTreeCtrl->GetItemText( m_draggedItem ) );
764 }
765 else
766 {
767 wxTreeItemId current = m_ruleTreeCtrl->GetFirstChild( targetParent, cookie );
768 wxTreeItemId previous;
769
770 // Traverse through siblings to find the position of the target item
771 while( current.IsOk() && current != targetItem )
772 {
773 previous = current;
774 current = m_ruleTreeCtrl->GetNextChild( targetParent, cookie );
775 }
776
777 if( !previous.IsOk() )
778 {
779 // If no previous item, insert as the first child
780 newItem = m_ruleTreeCtrl->PrependItem(
781 targetParent, m_ruleTreeCtrl->GetItemText( m_draggedItem ) );
782 }
783 else
784 {
785 wxTreeItemId nextSibling = m_ruleTreeCtrl->GetNextChild( targetParent, cookie );
786
787 if( nextSibling.IsOk() )
788 {
789 // If there is a next sibling, insert after the previous item
790 newItem = m_ruleTreeCtrl->InsertItem(
791 targetParent, previous,
792 m_ruleTreeCtrl->GetItemText( m_draggedItem ) );
793 }
794 else
795 {
796 // If no next sibling, append the item as the last child
797 newItem = m_ruleTreeCtrl->AppendItem(
798 targetParent, m_ruleTreeCtrl->GetItemText( m_draggedItem ) );
799 }
800 }
801 }
802
804
805 wxTreeItemData* itemData = m_ruleTreeCtrl->GetItemData( m_draggedItem );
806
807 if( itemData )
808 {
809 RULE_TREE_ITEM_DATA* ruleTreeItemData =
810 dynamic_cast<RULE_TREE_ITEM_DATA*>( itemData );
811 ruleTreeItemData->SetTreeItemId( newItem );
812
813 m_ruleTreeCtrl->SetItemData( newItem, ruleTreeItemData );
814 m_ruleTreeCtrl->SetItemData( m_draggedItem, nullptr );
815
816 saveRuleTreeState( newItem, ruleTreeItemData->GetNodeId() );
817
818 m_ruleTreeCtrl->SelectItem( newItem );
819
820 m_ruleTreeCtrl->Expand( newItem );
821 }
822
823 m_ruleTreeCtrl->DeleteChildren( m_draggedItem );
824 m_ruleTreeCtrl->Delete( m_draggedItem );
825 }
826
827 m_draggedItem = nullptr;
828 }
829
830 aEvent.Skip();
831}
832
833
834void RULE_EDITOR_DIALOG_BASE::onFilterSearch( wxCommandEvent& aEvent )
835{
836 const auto searchStr = aEvent.GetString().Lower();
837
838 m_ruleTreeCtrl->DeleteAllItems();
839
840 restoreRuleTree( nullptr, m_defaultTreeItems[0].m_nodeId );
841
842 wxTreeItemId root = m_ruleTreeCtrl->GetRootItem();
843
844 if( root.IsOk() )
845 {
846 filterRuleTree( root, searchStr );
847 }
848}
849
850
851bool RULE_EDITOR_DIALOG_BASE::filterRuleTree( const wxTreeItemId& aItem, const wxString& aFilter )
852{
853 bool matches = m_ruleTreeCtrl->GetItemText( aItem ).Lower().Contains( aFilter );
854
855 wxTreeItemIdValue cookie;
856 wxTreeItemId child = m_ruleTreeCtrl->GetFirstChild( aItem, cookie );
857 bool hasVisibleChildren = false;
858
859 std::vector<wxTreeItemId> itemsToDelete;
860
861 while( child.IsOk() )
862 {
863 if( filterRuleTree( child, aFilter ) )
864 {
865 hasVisibleChildren = true;
866 }
867 else
868 {
869 itemsToDelete.push_back( child );
870 }
871
872 child = m_ruleTreeCtrl->GetNextChild( aItem, cookie );
873 }
874
875 for( const auto& id : itemsToDelete )
876 {
877 m_ruleTreeCtrl->Delete( id );
878 }
879
880 return matches || hasVisibleChildren;
881}
882
883
884void RULE_EDITOR_DIALOG_BASE::saveRuleTreeState( const wxTreeItemId& aItem, const int& aNodeId )
885{
886 wxString itemText = m_ruleTreeCtrl->GetItemText( aItem );
887 std::vector<int> children;
888
889 wxTreeItemIdValue cookie;
890 wxTreeItemId child = m_ruleTreeCtrl->GetFirstChild( aItem, cookie );
891
892 while( child.IsOk() )
893 {
894 RULE_TREE_ITEM_DATA* childItemData =
895 dynamic_cast<RULE_TREE_ITEM_DATA*>( m_ruleTreeCtrl->GetItemData( child ) );
896 int childId = childItemData->GetNodeId();
897
898 children.push_back( childId );
899
900 saveRuleTreeState( child, childId );
901
902 child = m_ruleTreeCtrl->GetNextChild( aItem, cookie );
903 }
904
905 m_treeHistoryData[aNodeId] = { itemText, children, aItem };
906}
907
908
909void RULE_EDITOR_DIALOG_BASE::restoreRuleTree( const wxTreeItemId& aParent, const int& aNodeId )
910{
911 auto it = m_treeHistoryData.find( aNodeId );
912
913 if( it == m_treeHistoryData.end() )
914 return;
915
916 const auto& [itemText, children, itemId] = it->second;
917
918 wxTreeItemId newItem;
919
920 if( aParent )
921 newItem = m_ruleTreeCtrl->AppendItem( aParent, itemText );
922 else
923 newItem = m_ruleTreeCtrl->AddRoot( itemText );
924
925 if( newItem )
926 {
927 wxTreeItemId& treeItemId = std::get<2>( it->second );
928 treeItemId = newItem;
929 }
930
931 RULE_TREE_ITEM_DATA* itemData = new RULE_TREE_ITEM_DATA( aNodeId, aParent, newItem );
932 m_ruleTreeCtrl->SetItemData( newItem, itemData );
933
934 for( const auto& childId : children )
935 {
936 restoreRuleTree( newItem, childId );
937 }
938
939 // Don't expand the root item because it is hidden
940 if( aParent )
941 m_ruleTreeCtrl->Expand( newItem );
942}
943
944
945wxTreeItemId RULE_EDITOR_DIALOG_BASE::appendRuleTreeItem( const RULE_TREE_NODE& aNode, wxTreeItemId aParent )
946{
947 wxTreeItemId currentTreeItemId = m_ruleTreeCtrl->AppendItem( aParent, aNode.m_nodeName );
948
949 RULE_TREE_ITEM_DATA* itemData = new RULE_TREE_ITEM_DATA( aNode.m_nodeId, aParent, currentTreeItemId );
950 m_ruleTreeCtrl->SetItemData( currentTreeItemId, itemData );
951
952 m_ruleTreeCtrl->Expand( currentTreeItemId );
953
954 return currentTreeItemId;
955}
956
957
958void RULE_EDITOR_DIALOG_BASE::getRuleTreeChildNodes( const std::vector<RULE_TREE_NODE>& aNodes,
959 int aParentId,
960 std::vector<RULE_TREE_NODE>& aResult )
961{
962 std::vector<RULE_TREE_NODE> filteredNodes;
963
964 std::copy_if( aNodes.begin(), aNodes.end(), std::back_inserter( filteredNodes ),
965 [&aParentId]( const RULE_TREE_NODE& node )
966 {
967 return node.m_nodeData->GetParentId() == aParentId;
968 } );
969
970 if( filteredNodes.size() > 0 )
971 {
972 aResult.insert( aResult.end(), filteredNodes.begin(), filteredNodes.end() );
973 }
974}
975
977 wxTreeItemId aDest )
978{
979 wxTreeItemIdValue cookie;
980 wxTreeItemId child = m_ruleTreeCtrl->GetFirstChild( aSrc, cookie );
981
982 while( child.IsOk() )
983 {
984 wxTreeItemId newChild =
985 m_ruleTreeCtrl->AppendItem( aDest, m_ruleTreeCtrl->GetItemText( child ) );
986
987 RULE_TREE_ITEM_DATA* ruleTreeItemData =
988 dynamic_cast<RULE_TREE_ITEM_DATA*>( m_ruleTreeCtrl->GetItemData( child ) );
989 ruleTreeItemData->SetParentTreeItemId( aDest );
990 ruleTreeItemData->SetTreeItemId( newChild );
991
992 m_ruleTreeCtrl->SetItemData( newChild, ruleTreeItemData );
993 m_ruleTreeCtrl->SetItemData( child, nullptr );
994
995 moveRuleTreeItemChildrensTooOnDrag( child, newChild );
996
997 child = m_ruleTreeCtrl->GetNextChild( aSrc, cookie );
998 }
999}
1000
1001
1003{
1004 wxTreeItemId selectedItem = m_ruleTreeCtrl->GetSelection();
1005
1006 m_enableMoveUp = false;
1007 m_enableMoveDown = false;
1008
1009 if( !selectedItem.IsOk() )
1010 return;
1011
1012 if( selectedItem == m_ruleTreeCtrl->GetRootItem() )
1013 return;
1014
1015 RULE_TREE_ITEM_DATA* itemData =
1016 dynamic_cast<RULE_TREE_ITEM_DATA*>( m_ruleTreeCtrl->GetItemData( selectedItem ) );
1017
1018 if( !itemData ||
1021 {
1022 return;
1023 }
1024
1025 wxTreeItemId parent = m_ruleTreeCtrl->GetItemParent( selectedItem );
1026 wxTreeItemIdValue cookie;
1027 wxTreeItemId firstChild = m_ruleTreeCtrl->GetFirstChild( parent, cookie );
1028 wxTreeItemId lastChild = firstChild;
1029
1030 while( lastChild.IsOk() )
1031 {
1032 wxTreeItemId next = m_ruleTreeCtrl->GetNextChild( parent, cookie );
1033
1034 if( !next.IsOk() )
1035 break;
1036
1037 lastChild = next;
1038 }
1039
1040 m_enableMoveUp = ( selectedItem != firstChild );
1041 m_enableMoveDown = ( selectedItem != lastChild );
1042}
1043
1044
1046{
1047 wxTreeItemId selectedItem = m_ruleTreeCtrl->GetSelection();
1048
1049 if( !selectedItem.IsOk() || !m_ruleTreeCtrl->IsEnabled() )
1050 {
1051 m_addRuleButton->Enable( false );
1052 m_copyRuleButton->Enable( false );
1053 m_moveTreeItemUpButton->Enable( false );
1054 m_moveTreeItemDownButton->Enable( false );
1055 m_deleteRuleButton->Enable( false );
1056
1057 return;
1058 }
1059
1060 m_enableAddRule = false;
1061 m_enableDuplicateRule = false;
1062 m_enableDeleteRule = false;
1063
1065 m_enableAddRule = true;
1066
1068 m_enableDuplicateRule = true;
1069
1071 m_enableDeleteRule = true;
1072
1074
1080}
1081
1082
1083void RULE_EDITOR_DIALOG_BASE::onResize( wxSizeEvent& event )
1084{
1085 Layout();
1086
1087 event.Skip();
1088}
1089
1090
1091void RULE_EDITOR_DIALOG_BASE::onClose( wxCloseEvent& aEvt )
1092{
1093 wxPostEvent( this, wxCommandEvent( wxEVT_COMMAND_BUTTON_CLICKED, wxID_CANCEL ) );
1094}
wxBitmap KiBitmap(BITMAPS aBitmap, int aHeightTag)
Construct a wxBitmap from an image identifier Returns the image from the active theme if the image ha...
Definition bitmap.cpp:104
wxBitmapBundle KiBitmapBundle(BITMAPS aBitmap, int aMinHeight)
Definition bitmap.cpp:110
void finishDialogSettings()
In all dialogs, we must call the same functions to fix minimal dlg size, the default position and per...
DIALOG_SHIM(wxWindow *aParent, wxWindowID id, const wxString &title, const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize, long style=wxDEFAULT_FRAME_STYLE|wxRESIZE_BORDER, const wxString &name=wxDialogNameStr)
int GetParentId()
Get the parent ID of the rule.
void populateRuleTreeCtrl(const std::vector< RULE_TREE_NODE > &aRuleTreeNodes, const RULE_TREE_NODE &aRuleTreeNode, wxTreeItemId aParentTreeItemId)
Populates the rule tree with nodes and their children.
void onRuleTreeItemSelectionChanged(wxTreeEvent &aEvent)
Updates action buttons based on the selected tree item.
void moveRuleTreeItemChildrensTooOnDrag(wxTreeItemId aSrcTreeItemId, wxTreeItemId aDestTreeItemId)
Recursively moves all child nodes of a source item to a destination during drag.
void DeleteRuleTreeItem(wxTreeItemId aItemId, const int &aNodeId)
Deletes a tree item and removes its corresponding node from history.
void onRuleTreeItemMouseMotion(wxMouseEvent &aEvent)
Handles drag motion to move the item along with the cursor.
void UpdateRuleTreeItemText(wxTreeItemId aItemId, wxString aItemText)
Updates the text of a specified rule tree item.
void onNewRuleOptionClick(wxCommandEvent &aEvent)
Creates a new rule when the "New Rule" option is clicked.
void onRuleTreeItemLeftDown(wxMouseEvent &aEvent)
Initiates drag operation for a tree item on mouse down.
void onMoveUpRuleOptionClick(wxCommandEvent &aEvent)
Moves a rule item up in the tree when "Move Up" is clicked.
virtual void RuleTreeItemSelectionChanged(RULE_TREE_ITEM_DATA *aCurrentRuleTreeItemData)=0
Pure virtual method to handle tree item selection changes.
void onClose(wxCloseEvent &aEvt)
void onMoveDownRuleOptionClick(wxCommandEvent &aEvent)
Moves a rule item down in the tree when "Move Down" is clicked.
void AppendNewRuleTreeItem(const RULE_TREE_NODE &aRuleTreeNode, wxTreeItemId aParentTreeItemId)
Adds a new rule tree item under the specified parent and updates the tree history.
void InitRuleTreeItems(const std::vector< RULE_TREE_NODE > &aRuleTreeNodes)
Initializes the rule tree by adding nodes, setting up the structure, and saving its state.
void saveRuleTreeState(const wxTreeItemId &aItem, const int &aNodeId=0)
Saves the state of a tree item to history.
static RULE_EDITOR_DIALOG_BASE * GetDialog(wxWindow *aWindow)
Static method to retrieve the rule editor dialog instance associated with a given window.
void onRuleTreeItemLeftUp(wxMouseEvent &aEvent)
Completes the drag operation on mouse release.
void onRuleTreeItemActivated(wxTreeEvent &aEvent)
Handles double-click activation of a tree item.
void SetContentPanel(wxPanel *aContentPanel)
Replaces the current content panel with a new one based on the selected constraint type.
virtual void OnSave(wxCommandEvent &aEvent)=0
std::vector< RULE_TREE_NODE > m_defaultTreeItems
virtual void AddNewRule(RULE_TREE_ITEM_DATA *aRuleTreeItemData)=0
Pure virtual method to add a new rule to the tree.
void onDuplicateRuleOptionClick(wxCommandEvent &aEvent)
Duplicates the selected rule when "Duplicate Rule" is clicked.
RULE_TREE_ITEM_DATA * m_selectedData
void restoreRuleTree(const wxTreeItemId &aParent, const int &aNodeId)
Restores a tree item from history and appends it under a parent.
RULE_EDITOR_DIALOG_BASE(wxWindow *aParent, const wxString &aTitle, const wxSize &aInitialSize=wxDefaultSize)
virtual void RemoveRule(int aNodeId)=0
Pure virtual method to remove a rule from the tree.
wxTreeItemId appendRuleTreeItem(const RULE_TREE_NODE &aRuleTreeNode, wxTreeItemId aParentTreeItemId)
Appends a new rule item to the tree.
std::unordered_map< int, std::tuple< wxString, std::vector< int >, wxTreeItemId > > m_treeHistoryData
void onResize(wxSizeEvent &event)
void onDeleteRuleOptionClick(wxCommandEvent &aEvent)
Deletes the selected rule when "Delete Rule" is clicked.
virtual void DuplicateRule(RULE_TREE_ITEM_DATA *aRuleTreeItemData)=0
Pure virtual method to duplicate an existing rule in the tree.
void onRuleTreeItemRightClick(wxTreeEvent &aEvent)
Handles right-click on a rule tree item to create a context menu.
void getRuleTreeChildNodes(const std::vector< RULE_TREE_NODE > &aNodes, int aParentId, std::vector< RULE_TREE_NODE > &aResult)
Retrieves child nodes of a given parent node.
void onFilterSearch(wxCommandEvent &aEvent)
Applies filter to the rule tree based on the search string.
virtual bool isEnabled(RULE_TREE_ITEM_DATA *aRuleTreeItemData, RULE_EDITOR_TREE_CONTEXT_OPT aOption)=0
Pure virtual method to verify if a context menu option for a rule tree item should be enabled.
void updateRuleTreeItemMoveOptionState()
Updates the state of move options (up/down) for the selected item.
virtual void OnCancel(wxCommandEvent &aEvent)=0
void updateRuleTreeActionButtonsState(RULE_TREE_ITEM_DATA *aRuleTreeItemData)
Updates the action buttons based on the current selection.
void SetControlsEnabled(bool aEnable)
Enables or disables controls within the rule editor dialog.
bool filterRuleTree(const wxTreeItemId &aItem, const wxString &aFilter)
Recursively filters tree items to show only those matching the filter.
wxBitmapButton * m_moveTreeItemDownButton
A class representing additional data associated with a wxTree item.
void SetTreeItemId(wxTreeItemId aTreeItemId)
void SetParentTreeItemId(wxTreeItemId aParentTreeItemId)
A modified version of the wxInfoBar class that allows us to:
Definition wx_infobar.h:76
void SetBorders(bool aLeft, bool aRight, bool aTop, bool aBottom)
Definition wx_panel.h:39
This file is part of the common library.
#define _(s)
KICOMMON_API wxFont GetControlFont(wxWindow *aWindow)
KICOMMON_API wxMenuItem * AddMenuItem(wxMenu *aMenu, int aId, const wxString &aText, const wxBitmapBundle &aImage, wxItemKind aType=wxITEM_NORMAL)
Create and insert a menu item with an icon into aMenu.
static wxSearchCtrl * CreateTextFilterBox(wxWindow *aParent, const wxString &aDescriptiveText)
Helper function to add a filter box to a panel, with some sensible defaults for that purpose.
void Refresh()
Update the board display after modifying it by a python script (note: it is automatically called by a...
CITER next(CITER it)
Definition ptree.cpp:124
static wxSearchCtrl * CreateTextFilterBox(wxWindow *aParent, const wxString &aDescriptiveText)
Structure representing a node in a rule tree, collection of this used for building the rule tree.
std::shared_ptr< RULE_EDITOR_DATA_BASE > m_nodeData
Functions to provide common constants and other functions to assist in making a consistent UI.