KiCad PCB EDA Suite
Loading...
Searching...
No Matches
dialog_shim.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) 2012 SoftPLC Corporation, Dick Hollenbeck <[email protected]>
5 * Copyright (C) 2023 CERN
6 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <https://www.gnu.org/licenses/>.
20 */
21
22#include <app_monitor.h>
23#include <dialog_shim.h>
26#include <core/ignore.h>
27#include <kiway_player.h>
28#include <kiway.h>
29#include <pgm_base.h>
31#include <property_holder.h>
33#include <tool/tool_manager.h>
34#include <kiplatform/ui.h>
35#include <widgets/unit_binder.h>
36
37#include <wx/display.h>
38#include <wx/evtloop.h>
39#include <wx/app.h>
40#include <wx/event.h>
41#include <wx/grid.h>
42#include <widgets/wx_grid.h>
43#include <wx/propgrid/propgrid.h>
44#include <wx/checklst.h>
45#include <wx/dataview.h>
46#include <wx/bmpbuttn.h>
47#include <wx/textctrl.h>
48#include <wx/stc/stc.h>
49#include <wx/combobox.h>
50#include <wx/odcombo.h>
51#include <wx/choice.h>
52#include <wx/checkbox.h>
53#include <wx/spinctrl.h>
54#include <wx/splitter.h>
55#include <wx/radiobox.h>
56#include <wx/radiobut.h>
57#include <wx/variant.h>
58
59#include <algorithm>
60#include <functional>
61#include <nlohmann/json.hpp>
62#include <typeinfo>
63
64BEGIN_EVENT_TABLE( DIALOG_SHIM, wxDialog )
65 EVT_CHAR_HOOK( DIALOG_SHIM::OnCharHook )
66END_EVENT_TABLE()
67
68
69
75static std::string getDialogKeyFromTitle( const wxString& aTitle )
76{
77 std::string title = aTitle.ToStdString();
78 size_t parenPos = title.rfind( '(' );
79
80 if( parenPos != std::string::npos && parenPos > 0 )
81 {
82 size_t end = parenPos;
83
84 while( end > 0 && title[end - 1] == ' ' )
85 end--;
86
87 return title.substr( 0, end );
88 }
89
90 return title;
91}
92
93
94DIALOG_SHIM::DIALOG_SHIM( wxWindow* aParent, wxWindowID id, const wxString& title, const wxPoint& pos,
95 const wxSize& size, long style, const wxString& name ) :
96 wxDialog( aParent, id, title, pos, size, style, name ),
97 KIWAY_HOLDER( nullptr, KIWAY_HOLDER::DIALOG ),
99 m_useCalculatedSize( false ),
100 m_firstPaintEvent( true ),
101 m_initialFocusTarget( nullptr ),
102 m_isClosing( false ),
103 m_qmodal_loop( nullptr ),
104 m_qmodal_showing( false ),
105 m_qmodal_parent_disabler( nullptr ),
106 m_parentFrame( nullptr ),
107 m_userPositioned( false ),
108 m_userResized( false ),
109 m_handlingUndoRedo( false ),
110 m_childReleased( false )
111{
112 KIWAY_HOLDER* kiwayHolder = nullptr;
113 m_initialSize = size;
114
115 if( aParent )
116 {
117 kiwayHolder = dynamic_cast<KIWAY_HOLDER*>( aParent );
118
119 while( !kiwayHolder && aParent->GetParent() )
120 {
121 aParent = aParent->GetParent();
122 kiwayHolder = dynamic_cast<KIWAY_HOLDER*>( aParent );
123 }
124 }
125
126 // Inherit units from parent
127 if( kiwayHolder && kiwayHolder->GetType() == KIWAY_HOLDER::FRAME )
128 m_units = static_cast<EDA_BASE_FRAME*>( kiwayHolder )->GetUserUnits();
129 else if( kiwayHolder && kiwayHolder->GetType() == KIWAY_HOLDER::DIALOG )
130 m_units = static_cast<DIALOG_SHIM*>( kiwayHolder )->GetUserUnits();
131
132 // Don't mouse-warp after a dialog run from the context menu
133 if( kiwayHolder && kiwayHolder->GetType() == KIWAY_HOLDER::FRAME )
134 {
135 m_parentFrame = static_cast<EDA_BASE_FRAME*>( kiwayHolder );
136 TOOL_MANAGER* toolMgr = m_parentFrame->GetToolManager();
137
138 if( toolMgr && toolMgr->IsContextMenuActive() )
139 toolMgr->VetoContextMenuMouseWarp();
140 }
141
142 // Set up the message bus
143 if( kiwayHolder )
144 SetKiway( this, &kiwayHolder->Kiway() );
145
146 if( HasKiway() )
147 Kiway().SetBlockingDialog( this );
148
149 Bind( wxEVT_CLOSE_WINDOW, &DIALOG_SHIM::OnCloseWindow, this );
150 Bind( wxEVT_BUTTON, &DIALOG_SHIM::OnButton, this );
151 Bind( wxEVT_SIZE, &DIALOG_SHIM::OnSize, this );
152 Bind( wxEVT_MOVE, &DIALOG_SHIM::OnMove, this );
153 Bind( wxEVT_INIT_DIALOG, &DIALOG_SHIM::onInitDialog, this );
154
155#ifdef __WINDOWS__
156 // On Windows, the app top windows can be brought to the foreground (at least temporarily)
157 // in certain circumstances such as when calling an external tool in Eeschema BOM generation.
158 // So set the parent frame (if exists) to top window to avoid this annoying behavior.
159 if( kiwayHolder && kiwayHolder->GetType() == KIWAY_HOLDER::FRAME )
160 Pgm().App().SetTopWindow( (EDA_BASE_FRAME*) kiwayHolder );
161#endif
162
163 Bind( wxEVT_PAINT, &DIALOG_SHIM::OnPaint, this );
164
165 wxString msg = wxString::Format( "Opening dialog %s", GetTitle() );
166 APP_MONITOR::AddNavigationBreadcrumb( msg, "dialog.open" );
167}
168
169
171{
172 m_isClosing = true;
173
174 Unbind( wxEVT_CLOSE_WINDOW, &DIALOG_SHIM::OnCloseWindow, this );
175 Unbind( wxEVT_BUTTON, &DIALOG_SHIM::OnButton, this );
176 Unbind( wxEVT_PAINT, &DIALOG_SHIM::OnPaint, this );
177 Unbind( wxEVT_SIZE, &DIALOG_SHIM::OnSize, this );
178 Unbind( wxEVT_MOVE, &DIALOG_SHIM::OnMove, this );
179 Unbind( wxEVT_INIT_DIALOG, &DIALOG_SHIM::onInitDialog, this );
180
181 std::function<void( wxWindowList& )> disconnectFocusHandlers =
182 [&]( wxWindowList& children )
183 {
184 for( wxWindow* child : children )
185 {
186 if( wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( child ) )
187 {
188 textCtrl->Disconnect( wxEVT_SET_FOCUS, wxFocusEventHandler( DIALOG_SHIM::onChildSetFocus ),
189 nullptr, this );
190 }
191 else if( wxStyledTextCtrl* scintilla = dynamic_cast<wxStyledTextCtrl*>( child ) )
192 {
193 scintilla->Disconnect( wxEVT_SET_FOCUS, wxFocusEventHandler( DIALOG_SHIM::onChildSetFocus ),
194 nullptr, this );
195 }
196 else
197 {
198 disconnectFocusHandlers( child->GetChildren() );
199 }
200 }
201 };
202
203 disconnectFocusHandlers( GetChildren() );
204
205 std::function<void( wxWindowList& )> disconnectUndoRedoHandlers =
206 [&]( wxWindowList& children )
207 {
208 for( wxWindow* child : children )
209 {
210 if( wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( child ) )
211 {
212 textCtrl->Unbind( wxEVT_TEXT, &DIALOG_SHIM::onCommandEvent, this );
213 }
214 else if( wxStyledTextCtrl* scintilla = dynamic_cast<wxStyledTextCtrl*>( child ) )
215 {
216 scintilla->Unbind( wxEVT_STC_CHANGE, &DIALOG_SHIM::onStyledTextChanged, this );
217 }
218 else if( wxComboBox* combo = dynamic_cast<wxComboBox*>( child ) )
219 {
220 combo->Unbind( wxEVT_TEXT, &DIALOG_SHIM::onCommandEvent, this );
221 combo->Unbind( wxEVT_COMBOBOX, &DIALOG_SHIM::onCommandEvent, this );
222 }
223 else if( wxChoice* choice = dynamic_cast<wxChoice*>( child ) )
224 {
225 choice->Unbind( wxEVT_CHOICE, &DIALOG_SHIM::onCommandEvent, this );
226 }
227 else if( wxCheckBox* check = dynamic_cast<wxCheckBox*>( child ) )
228 {
229 check->Unbind( wxEVT_CHECKBOX, &DIALOG_SHIM::onCommandEvent, this );
230 }
231 else if( wxSpinCtrl* spin = dynamic_cast<wxSpinCtrl*>( child ) )
232 {
233 spin->Unbind( wxEVT_SPINCTRL, &DIALOG_SHIM::onSpinEvent, this );
234 spin->Unbind( wxEVT_TEXT, &DIALOG_SHIM::onCommandEvent, this );
235 }
236 else if( wxSpinCtrlDouble* spinD = dynamic_cast<wxSpinCtrlDouble*>( child ) )
237 {
238 spinD->Unbind( wxEVT_SPINCTRLDOUBLE, &DIALOG_SHIM::onSpinDoubleEvent, this );
239 spinD->Unbind( wxEVT_TEXT, &DIALOG_SHIM::onCommandEvent, this );
240 }
241 else if( wxRadioButton* radio = dynamic_cast<wxRadioButton*>( child ) )
242 {
243 radio->Unbind( wxEVT_RADIOBUTTON, &DIALOG_SHIM::onCommandEvent, this );
244 }
245 else if( wxRadioBox* radioBox = dynamic_cast<wxRadioBox*>( child ) )
246 {
247 radioBox->Unbind( wxEVT_RADIOBOX, &DIALOG_SHIM::onCommandEvent, this );
248 }
249 else if( wxGrid* grid = dynamic_cast<wxGrid*>( child ) )
250 {
251 grid->Unbind( wxEVT_GRID_CELL_CHANGED, &DIALOG_SHIM::onGridCellChanged, this );
252 }
253 else if( wxPropertyGrid* propGrid = dynamic_cast<wxPropertyGrid*>( child ) )
254 {
255 propGrid->Unbind( wxEVT_PG_CHANGED, &DIALOG_SHIM::onPropertyGridChanged, this );
256 }
257 else if( wxCheckListBox* checkList = dynamic_cast<wxCheckListBox*>( child ) )
258 {
259 checkList->Unbind( wxEVT_CHECKLISTBOX, &DIALOG_SHIM::onCommandEvent, this );
260 }
261 else if( wxDataViewListCtrl* dataList = dynamic_cast<wxDataViewListCtrl*>( child ) )
262 {
263 dataList->Unbind( wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, &DIALOG_SHIM::onDataViewListChanged, this );
264 }
265 else
266 {
267 disconnectUndoRedoHandlers( child->GetChildren() );
268 }
269 }
270 };
271
272 disconnectUndoRedoHandlers( GetChildren() );
273
274 // The child controls (and the UNIT_BINDERs that live alongside them) outlive this dialog's
275 // member teardown, so sever their back-references now while m_unitBinders is still valid.
276 for( const auto& [window, binder] : m_unitBinders )
277 binder->DetachFromDialogShim();
278
279 // if the dialog is quasi-modal, this will end its event loop
280 if( IsQuasiModal() )
281 EndQuasiModal( wxID_CANCEL );
282
283 if( HasKiway() )
284 Kiway().SetBlockingDialog( nullptr );
285
287}
288
289
290void DIALOG_SHIM::onInitDialog( wxInitDialogEvent& aEvent )
291{
292#ifdef __WXMAC__
293 CallAfter(
294 [this]
295 {
296 if( wxSizer* sz = GetSizer() )
297 sz->Layout();
298 } );
299#endif
300
302 aEvent.Skip();
303}
304
305
307{
308 // must be called from the constructor of derived classes,
309 // when all widgets are initialized, and therefore their size fixed
310
311 // SetSizeHints fixes the minimal size of sizers in the dialog
312 // (SetSizeHints calls Fit(), so no need to call it)
313 GetSizer()->SetSizeHints( this );
314}
315
316
317void DIALOG_SHIM::setSizeInDU( int x, int y )
318{
319 wxSize sz( x, y );
320 SetSize( ConvertDialogToPixels( sz ) );
321}
322
323
325{
326 wxSize sz( x, 0 );
327 return ConvertDialogToPixels( sz ).x;
328}
329
330
332{
333 wxSize sz( 0, y );
334 return ConvertDialogToPixels( sz ).y;
335}
336
337
338// our hashtable is an implementation secret, don't need or want it in a header file
339#include <hashtables.h>
340#include <typeinfo>
341#include <grid_tricks.h>
342
343
344void DIALOG_SHIM::SetPosition( const wxPoint& aNewPosition )
345{
346 wxDialog::SetPosition( aNewPosition );
347}
348
349
350void DIALOG_SHIM::focusParentCanvas( bool aDeferUntilFrameActive )
351{
352 wxWindow* toolCanvas = m_parentFrame ? m_parentFrame->GetToolCanvas() : nullptr;
353 wxWindow* target = toolCanvas ? toolCanvas : m_parent;
354
355 if( !target )
356 return;
357
358 target->SetFocus();
359
360 if( !aDeferUntilFrameActive || !toolCanvas )
361 return;
362
363#ifdef __WXGTK__
364 // A quasi-modal dialog is still the active top-level window when its nested event loop exits,
365 // so the SetFocus() above is undone when the dialog is destroyed and GTK restores the frame's
366 // previously-focused widget. Re-assert focus once the event loop has settled, otherwise
367 // keyboard events keep routing to the stale owner until the mouse re-enters the canvas.
369
370 frame->CallAfter(
371 [frame]()
372 {
373 // Skip if another dialog grabbed the activation in the meantime, otherwise we
374 // would raise the frame from behind a chained modal dialog.
375 if( !KIPLATFORM::UI::IsWindowActive( frame ) )
376 return;
377
378 if( wxWindow* canvas = frame->GetToolCanvas() )
379 canvas->SetFocus();
380 } );
381#endif
382}
383
384
385bool DIALOG_SHIM::Show( bool show )
386{
387 bool ret;
388
389 if( show )
390 {
392
393#ifndef __WINDOWS__
394 wxDialog::Raise(); // Needed on OS X and some other window managers (i.e. Unity)
395#endif
396 ret = wxDialog::Show( show );
397
398 wxRect savedDialogRect;
399 std::string key = m_hash_key.empty() ? getDialogKeyFromTitle( GetTitle() ) : m_hash_key;
400
401 if( COMMON_SETTINGS* settings = Pgm().GetCommonSettings() )
402 {
403 auto dlgIt = settings->CsInternals().m_dialogControlValues.find( key );
404
405 if( dlgIt != settings->CsInternals().m_dialogControlValues.end() )
406 {
407 auto geoIt = dlgIt->second.find( "__geometry" );
408
409 if( geoIt != dlgIt->second.end() && geoIt->second.is_object() )
410 {
411 const nlohmann::json& g = geoIt->second;
412 savedDialogRect.SetPosition( wxPoint( g.value( "x", 0 ), g.value( "y", 0 ) ) );
413 savedDialogRect.SetSize( wxSize( g.value( "w", 500 ), g.value( "h", 300 ) ) );
414 }
415 }
416 }
417
418 if( savedDialogRect.GetSize().x != 0 && savedDialogRect.GetSize().y != 0 )
419 {
420 // Convert saved DIP size to logical pixels for the current monitor
421 wxSize restoredSize = FromDIP( savedDialogRect.GetSize() );
422
424 {
425 SetSize( savedDialogRect.GetPosition().x, savedDialogRect.GetPosition().y,
426 wxDialog::GetSize().x, wxDialog::GetSize().y, 0 );
427 }
428 else
429 {
430 SetSize( savedDialogRect.GetPosition().x, savedDialogRect.GetPosition().y,
431 std::max( wxDialog::GetSize().x, restoredSize.x ),
432 std::max( wxDialog::GetSize().y, restoredSize.y ), 0 );
433
434 // Reset minimum size so the user can resize the dialog smaller than
435 // the saved size. We must clear the current minimum and invalidate
436 // the cached best size so GetBestSize() returns the true sizer
437 // minimum rather than being constrained by the restored size.
438 SetMinSize( wxDefaultSize );
439 InvalidateBestSize();
440 SetMinSize( GetBestSize() );
441 }
442
443#ifdef __WXMAC__
444 if( m_parent != nullptr )
445 {
446 if( wxDisplay::GetFromPoint( m_parent->GetPosition() )
447 != wxDisplay::GetFromPoint( savedDialogRect.GetPosition() ) )
448 {
449 Centre();
450 }
451 }
452#endif
453
454 }
455 else if( m_initialSize != wxDefaultSize )
456 {
457 SetSize( m_initialSize );
458 Centre();
459 }
460
461 if( wxDisplay::GetFromWindow( this ) == wxNOT_FOUND )
462 Centre();
463
464 m_userPositioned = false;
465 m_userResized = false;
466
468 }
469 else
470 {
471
472#ifdef __WXMAC__
473 if ( m_eventLoop )
474 m_eventLoop->Exit( GetReturnCode() ); // Needed for APP-MODAL dlgs on OSX
475#endif
476
477 ret = wxDialog::Show( show );
478
481 }
482
483 return ret;
484}
485
486
488{
489 if( COMMON_SETTINGS* settings = Pgm().GetCommonSettings() )
490 {
491 std::string key = m_hash_key.empty() ? getDialogKeyFromTitle( GetTitle() ) : m_hash_key;
492
493 auto dlgIt = settings->CsInternals().m_dialogControlValues.find( key );
494
495 if( dlgIt == settings->CsInternals().m_dialogControlValues.end() )
496 return;
497
498 dlgIt->second.erase( "__geometry" );
499 }
500}
501
502
503void DIALOG_SHIM::OnSize( wxSizeEvent& aEvent )
504{
505 m_userResized = true;
506 aEvent.Skip();
507}
508
509
510void DIALOG_SHIM::OnMove( wxMoveEvent& aEvent )
511{
512 m_userPositioned = true;
513
514#ifdef __WXMAC__
515 if( m_parent )
516 {
517 int parentDisplay = wxDisplay::GetFromWindow( m_parent );
518 int myDisplay = wxDisplay::GetFromWindow( this );
519
520 if( parentDisplay != wxNOT_FOUND && myDisplay != wxNOT_FOUND )
521 {
522 if( myDisplay != parentDisplay && !m_childReleased )
523 {
524 // Moving to different monitor - release child relationship
526 m_childReleased = true;
527 }
528 else if( myDisplay == parentDisplay && m_childReleased )
529 {
530 // Back on same monitor - restore child relationship
532 m_childReleased = false;
533 }
534 }
535 }
536#endif
537
538 aEvent.Skip();
539}
540
541
542bool DIALOG_SHIM::Enable( bool enable )
543{
544 // so we can do logging of this state change:
545 return wxDialog::Enable( enable );
546}
547
548
549std::string DIALOG_SHIM::generateKey( const wxWindow* aWin ) const
550{
551 auto getSiblingIndex =
552 []( const wxWindow* parent, const wxWindow* child )
553 {
554 wxString childClass = child->GetClassInfo()->GetClassName();
555 int index = 0;
556
557 for( const wxWindow* sibling : parent->GetChildren() )
558 {
559 if( sibling->GetClassInfo()->GetClassName() != childClass )
560 continue;
561
562 if( sibling == child )
563 break;
564
565 index++;
566 }
567
568 return index;
569 };
570
571 auto makeKey =
572 [&]( const wxWindow* window )
573 {
574 std::string key = wxString( window->GetClassInfo()->GetClassName() ).ToStdString();
575
576 if( window->GetParent() )
577 key += "_" + std::to_string( getSiblingIndex( window->GetParent(), window ) );
578
579 return key;
580 };
581
582 std::string key = makeKey( aWin );
583
584 for( const wxWindow* parent = aWin->GetParent(); parent && parent != this; parent = parent->GetParent() )
585 key = makeKey( parent ) + key;
586
587 return key;
588}
589
590
592{
593 COMMON_SETTINGS* settings = Pgm().GetCommonSettings();
594
595 if( !settings )
596 return;
597
598 std::string dialogKey = m_hash_key.empty() ? getDialogKeyFromTitle( GetTitle() ) : m_hash_key;
599 std::map<std::string, nlohmann::json>& dlgMap = settings->CsInternals().m_dialogControlValues[ dialogKey ];
600
601 wxPoint pos = GetPosition();
602 wxSize dipSize = ToDIP( GetSize() );
603 nlohmann::json geom;
604 geom[ "x" ] = pos.x;
605 geom[ "y" ] = pos.y;
606 geom[ "w" ] = dipSize.x;
607 geom[ "h" ] = dipSize.y;
608 dlgMap[ "__geometry" ] = geom;
609
610 std::function<void( wxWindow* )> saveFn =
611 [&]( wxWindow* win )
612 {
613 if( PROPERTY_HOLDER* props = PROPERTY_HOLDER::SafeCast( win->GetClientData() ) )
614 {
615 if( !props->GetPropertyOr( "persist", false ) )
616 return;
617 }
618
619 std::string key = generateKey( win );
620
621 if( !key.empty() )
622 {
623 if( m_unitBinders.contains( win ) && !m_unitBinders[ win ]->UnitsInvariant() )
624 {
625 dlgMap[ key ] = m_unitBinders[ win ]->GetValue();
626 }
627 else if( wxComboBox* combo = dynamic_cast<wxComboBox*>( win ) )
628 {
629 dlgMap[ key ] = combo->GetValue();
630 }
631 else if( wxOwnerDrawnComboBox* od_combo = dynamic_cast<wxOwnerDrawnComboBox*>( win ) )
632 {
633 dlgMap[ key ] = od_combo->GetSelection();
634 }
635 else if( wxTextEntry* textEntry = dynamic_cast<wxTextEntry*>( win ) )
636 {
637 dlgMap[ key ] = textEntry->GetValue();
638 }
639 else if( wxChoice* choice = dynamic_cast<wxChoice*>( win ) )
640 {
641 dlgMap[ key ] = choice->GetSelection();
642 }
643 else if( wxCheckBox* check = dynamic_cast<wxCheckBox*>( win ) )
644 {
645 dlgMap[ key ] = check->GetValue();
646 }
647 else if( wxSpinCtrl* spin = dynamic_cast<wxSpinCtrl*>( win ) )
648 {
649 dlgMap[ key ] = spin->GetValue();
650 }
651 else if( wxRadioButton* radio = dynamic_cast<wxRadioButton*>( win ) )
652 {
653 dlgMap[ key ] = radio->GetValue();
654 }
655 else if( wxRadioBox* radioBox = dynamic_cast<wxRadioBox*>( win ) )
656 {
657 dlgMap[ key ] = radioBox->GetSelection();
658 }
659 else if( wxSplitterWindow* splitter = dynamic_cast<wxSplitterWindow*>( win ) )
660 {
661 dlgMap[ key ] = splitter->GetSashPosition();
662 }
663 else if( wxScrolledWindow* scrolled = dynamic_cast<wxScrolledWindow*>( win ) )
664 {
665 dlgMap[ key ] = scrolled->GetScrollPos( wxVERTICAL );
666 }
667 else if( wxNotebook* notebook = dynamic_cast<wxNotebook*>( win ) )
668 {
669 int index = notebook->GetSelection();
670
671 if( index >= 0 && index < (int) notebook->GetPageCount() )
672 dlgMap[ key ] = notebook->GetPageText( notebook->GetSelection() );
673 }
674 else if( wxAuiNotebook* auiNotebook = dynamic_cast<wxAuiNotebook*>( win ) )
675 {
676 int index = auiNotebook->GetSelection();
677
678 if( index >= 0 && index < (int) auiNotebook->GetPageCount() )
679 dlgMap[ key ] = auiNotebook->GetPageText( auiNotebook->GetSelection() );
680 }
681 else if( WX_GRID* grid = dynamic_cast<WX_GRID*>( win ) )
682 {
683 dlgMap[ key ] = grid->GetShownColumnsAsString();
684 }
685 }
686
687 for( wxWindow* child : win->GetChildren() )
688 saveFn( child );
689 };
690
691 if( PROPERTY_HOLDER* props = PROPERTY_HOLDER::SafeCast( GetClientData() ) )
692 {
693 if( !props->GetPropertyOr( "persist", false ) )
694 return;
695 }
696
697 for( wxWindow* child : GetChildren() )
698 saveFn( child );
699}
700
701
703{
704 COMMON_SETTINGS* settings = Pgm().GetCommonSettings();
705
706 if( !settings )
707 return;
708
709 std::string dialogKey = m_hash_key.empty() ? getDialogKeyFromTitle( GetTitle() ) : m_hash_key;
710 auto dlgIt = settings->CsInternals().m_dialogControlValues.find( dialogKey );
711
712 if( dlgIt == settings->CsInternals().m_dialogControlValues.end() )
713 return;
714
715 const std::map<std::string, nlohmann::json>& dlgMap = dlgIt->second;
716
717 std::function<void( wxWindow* )> loadFn =
718 [&]( wxWindow* win )
719 {
720 if( PROPERTY_HOLDER* props = PROPERTY_HOLDER::SafeCast( win->GetClientData() ) )
721 {
722 if( !props->GetPropertyOr( "persist", false ) )
723 return;
724 }
725
726 std::string key = generateKey( win );
727
728 if( !key.empty() )
729 {
730 auto it = dlgMap.find( key );
731
732 if( it != dlgMap.end() )
733 {
734 const nlohmann::json& j = it->second;
735
736 if( m_unitBinders.contains( win ) )
737 {
738 if( j.is_number_integer() )
739 {
740 m_unitBinders[ win ]->ChangeValue( j.get<int>() );
741 }
742 else if( j.is_string() )
743 {
744 if( wxTextEntry* textEntry = dynamic_cast<wxTextEntry*>( win ) )
745 textEntry->ChangeValue( wxString::FromUTF8( j.get<std::string>().c_str() ) );
746 }
747 }
748 else if( wxComboBox* combo = dynamic_cast<wxComboBox*>( win ) )
749 {
750 if( j.is_string() )
751 combo->SetValue( wxString::FromUTF8( j.get<std::string>().c_str() ) );
752 }
753 else if( wxOwnerDrawnComboBox* od_combo = dynamic_cast<wxOwnerDrawnComboBox*>( win ) )
754 {
755 if( j.is_number_integer() )
756 {
757 int index = j.get<int>();
758
759 if( index >= 0 && index < (int) od_combo->GetCount() )
760 od_combo->SetSelection( index );
761 }
762 }
763 else if( wxTextEntry* textEntry = dynamic_cast<wxTextEntry*>( win ) )
764 {
765 if( j.is_string() )
766 textEntry->ChangeValue( wxString::FromUTF8( j.get<std::string>().c_str() ) );
767 }
768 else if( wxChoice* choice = dynamic_cast<wxChoice*>( win ) )
769 {
770 if( j.is_number_integer() )
771 {
772 int index = j.get<int>();
773
774 if( index >= 0 && index < (int) choice->GetCount() )
775 choice->SetSelection( index );
776 }
777 }
778 else if( wxCheckBox* check = dynamic_cast<wxCheckBox*>( win ) )
779 {
780 if( j.is_boolean() )
781 check->SetValue( j.get<bool>() );
782 }
783 else if( wxSpinCtrl* spin = dynamic_cast<wxSpinCtrl*>( win ) )
784 {
785 if( j.is_number_integer() )
786 spin->SetValue( j.get<int>() );
787 }
788 else if( wxRadioButton* radio = dynamic_cast<wxRadioButton*>( win ) )
789 {
790 if( j.is_boolean() )
791 {
792 // Only set active radio buttons. Let wxWidgets handle unsetting the inactive
793 // ones. This prevents all from being unset, which trips up wxWidgets in some
794 // cases.
795 if( j.get<bool>() )
796 radio->SetValue( true );
797 }
798 }
799 else if( wxRadioBox* radioBox = dynamic_cast<wxRadioBox*>( win ) )
800 {
801 if( j.is_number_integer() )
802 {
803 int index = j.get<int>();
804
805 if( index >= 0 && index < (int) radioBox->GetCount() )
806 radioBox->SetSelection( index );
807 }
808 }
809 else if( wxSplitterWindow* splitter = dynamic_cast<wxSplitterWindow*>( win ) )
810 {
811 if( j.is_number_integer() )
812 splitter->SetSashPosition( j.get<int>() );
813 }
814 else if( wxScrolledWindow* scrolled = dynamic_cast<wxScrolledWindow*>( win ) )
815 {
816 if( j.is_number_integer() )
817 scrolled->SetScrollPos( wxVERTICAL, j.get<int>() );
818 }
819 else if( wxNotebook* notebook = dynamic_cast<wxNotebook*>( win ) )
820 {
821 if( j.is_string() )
822 {
823 wxString pageTitle = wxString::FromUTF8( j.get<std::string>().c_str() );
824
825 for( int page = 0; page < (int) notebook->GetPageCount(); ++page )
826 {
827 if( notebook->GetPageText( page ) == pageTitle )
828 notebook->ChangeSelection( page );
829 }
830 }
831 }
832 else if( wxAuiNotebook* auiNotebook = dynamic_cast<wxAuiNotebook*>( win ) )
833 {
834 if( j.is_string() )
835 {
836 wxString pageTitle = wxString::FromUTF8( j.get<std::string>().c_str() );
837
838 for( int page = 0; page < (int) auiNotebook->GetPageCount(); ++page )
839 {
840 if( auiNotebook->GetPageText( page ) == pageTitle )
841 auiNotebook->ChangeSelection( page );
842 }
843 }
844 }
845 else if( WX_GRID* grid = dynamic_cast<WX_GRID*>( win ) )
846 {
847 if( j.is_string() )
848 grid->ShowHideColumns( wxString::FromUTF8( j.get<std::string>().c_str() ) );
849 }
850 }
851 }
852
853 for( wxWindow* child : win->GetChildren() )
854 loadFn( child );
855 };
856
857 if( PROPERTY_HOLDER* props = PROPERTY_HOLDER::SafeCast( GetClientData() ) )
858 {
859 if( !props->GetPropertyOr( "persist", false ) )
860 return;
861 }
862
863 for( wxWindow* child : GetChildren() )
864 loadFn( child );
865}
866
867
868void DIALOG_SHIM::OptOut( wxWindow* aWindow )
869{
870 PROPERTY_HOLDER* props = new PROPERTY_HOLDER();
871 props->SetProperty( "persist", false );
872 aWindow->SetClientData( props );
873}
874
875
877{
878 m_noControlUndoRedo.insert( aWindow );
879}
880
881
882void DIALOG_SHIM::RegisterUnitBinder( UNIT_BINDER* aUnitBinder, wxWindow* aWindow )
883{
884 m_unitBinders[ aWindow ] = aUnitBinder;
885}
886
887
889{
890 // Erase by binder identity rather than window key, so that a stale entry whose window has
891 // already been reused by a newer binder is left untouched.
892 std::erase_if( m_unitBinders,
893 [aUnitBinder]( const auto& aEntry )
894 {
895 return aEntry.second == aUnitBinder;
896 } );
897}
898
899
900// Recursive descent doing a SelectAll() in wxTextCtrls.
901// MacOS User Interface Guidelines state that when tabbing to a text control all its
902// text should be selected. Since wxWidgets fails to implement this, we do it here.
903void DIALOG_SHIM::SelectAllInTextCtrls( wxWindowList& children )
904{
905 for( wxWindow* child : children )
906 {
907 if( wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( child ) )
908 {
909 m_beforeEditValues[ textCtrl ] = textCtrl->GetValue();
910 textCtrl->Connect( wxEVT_SET_FOCUS, wxFocusEventHandler( DIALOG_SHIM::onChildSetFocus ),
911 nullptr, this );
912
913 // We don't currently run this on GTK because some window managers don't hide the
914 // selection in non-active controls, and other window managers do the selection
915 // automatically anyway.
916#if defined( __WXMAC__ ) || defined( __WXMSW__ )
917 if( !textCtrl->GetStringSelection().IsEmpty() )
918 {
919 // Respect an existing selection
920 }
921 else if( textCtrl->IsEditable() )
922 {
923 textCtrl->SelectAll();
924 }
925#else
926 ignore_unused( textCtrl );
927#endif
928 }
929 else if( wxStyledTextCtrl* scintilla = dynamic_cast<wxStyledTextCtrl*>( child ) )
930 {
931 m_beforeEditValues[ scintilla ] = scintilla->GetText();
932 scintilla->Connect( wxEVT_SET_FOCUS,
933 wxFocusEventHandler( DIALOG_SHIM::onChildSetFocus ),
934 nullptr, this );
935
936 if( !scintilla->GetSelectedText().IsEmpty() )
937 {
938 // Respect an existing selection
939 }
940 else if( scintilla->GetMarginWidth( 0 ) > 0 )
941 {
942 // Don't select-all in Custom Rules, etc.
943 }
944 else if( scintilla->IsEditable() )
945 {
946 scintilla->SelectAll();
947 }
948 }
949#ifdef __WXMAC__
950 // Temp hack for square (looking) buttons on OSX. Will likely be made redundant
951 // by the image store....
952 else if( dynamic_cast<wxBitmapButton*>( child ) != nullptr )
953 {
954 wxSize minSize( 29, 27 );
955 wxRect rect = child->GetRect();
956
957 child->ConvertDialogToPixels( minSize );
958
959 rect.Inflate( std::max( 0, minSize.x - rect.GetWidth() ),
960 std::max( 0, minSize.y - rect.GetHeight() ) );
961
962 child->SetMinSize( rect.GetSize() );
963 child->SetSize( rect );
964 }
965#endif
966 else
967 {
968 SelectAllInTextCtrls( child->GetChildren() );
969 }
970 }
971}
972
973
974void DIALOG_SHIM::registerUndoRedoHandlers( wxWindowList& children )
975{
976 for( wxWindow* child : children )
977 {
978 if( m_noControlUndoRedo.count( child ) )
979 continue;
980
981 if( wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( child ) )
982 {
983 textCtrl->Bind( wxEVT_TEXT, &DIALOG_SHIM::onCommandEvent, this );
984 m_currentValues[ textCtrl ] = textCtrl->GetValue();
985 }
986 else if( wxStyledTextCtrl* scintilla = dynamic_cast<wxStyledTextCtrl*>( child ) )
987 {
988 scintilla->Bind( wxEVT_STC_CHANGE, &DIALOG_SHIM::onStyledTextChanged, this );
989 m_currentValues[ scintilla ] = scintilla->GetText();
990 }
991 else if( wxComboBox* combo = dynamic_cast<wxComboBox*>( child ) )
992 {
993 combo->Bind( wxEVT_TEXT, &DIALOG_SHIM::onCommandEvent, this );
994 combo->Bind( wxEVT_COMBOBOX, &DIALOG_SHIM::onCommandEvent, this );
995 m_currentValues[ combo ] = combo->GetValue();
996 }
997 else if( wxChoice* choice = dynamic_cast<wxChoice*>( child ) )
998 {
999 choice->Bind( wxEVT_CHOICE, &DIALOG_SHIM::onCommandEvent, this );
1000 m_currentValues[ choice ] = static_cast<long>( choice->GetSelection() );
1001 }
1002 else if( wxCheckBox* check = dynamic_cast<wxCheckBox*>( child ) )
1003 {
1004 check->Bind( wxEVT_CHECKBOX, &DIALOG_SHIM::onCommandEvent, this );
1005 m_currentValues[ check ] = check->GetValue();
1006 }
1007 else if( wxSpinCtrl* spin = dynamic_cast<wxSpinCtrl*>( child ) )
1008 {
1009 spin->Bind( wxEVT_SPINCTRL, &DIALOG_SHIM::onSpinEvent, this );
1010 spin->Bind( wxEVT_TEXT, &DIALOG_SHIM::onCommandEvent, this );
1011 m_currentValues[ spin ] = static_cast<long>( spin->GetValue() );
1012 }
1013 else if( wxSpinCtrlDouble* spinD = dynamic_cast<wxSpinCtrlDouble*>( child ) )
1014 {
1015 spinD->Bind( wxEVT_SPINCTRLDOUBLE, &DIALOG_SHIM::onSpinDoubleEvent, this );
1016 spinD->Bind( wxEVT_TEXT, &DIALOG_SHIM::onCommandEvent, this );
1017 m_currentValues[ spinD ] = spinD->GetValue();
1018 }
1019 else if( wxRadioButton* radio = dynamic_cast<wxRadioButton*>( child ) )
1020 {
1021 radio->Bind( wxEVT_RADIOBUTTON, &DIALOG_SHIM::onCommandEvent, this );
1022 m_currentValues[ radio ] = radio->GetValue();
1023 }
1024 else if( wxRadioBox* radioBox = dynamic_cast<wxRadioBox*>( child ) )
1025 {
1026 radioBox->Bind( wxEVT_RADIOBOX, &DIALOG_SHIM::onCommandEvent, this );
1027 m_currentValues[ radioBox ] = static_cast<long>( radioBox->GetSelection() );
1028 }
1029 else if( wxGrid* grid = dynamic_cast<wxGrid*>( child ) )
1030 {
1031 grid->Bind( wxEVT_GRID_CELL_CHANGED, &DIALOG_SHIM::onGridCellChanged, this );
1033 }
1034 else if( wxPropertyGrid* propGrid = dynamic_cast<wxPropertyGrid*>( child ) )
1035 {
1036 propGrid->Bind( wxEVT_PG_CHANGED, &DIALOG_SHIM::onPropertyGridChanged, this );
1037 m_currentValues[ propGrid ] = getControlValue( propGrid );
1038 }
1039 else if( wxCheckListBox* checkList = dynamic_cast<wxCheckListBox*>( child ) )
1040 {
1041 checkList->Bind( wxEVT_CHECKLISTBOX, &DIALOG_SHIM::onCommandEvent, this );
1042 m_currentValues[ checkList ] = getControlValue( checkList );
1043 }
1044 else if( wxDataViewListCtrl* dataList = dynamic_cast<wxDataViewListCtrl*>( child ) )
1045 {
1046 dataList->Bind( wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, &DIALOG_SHIM::onDataViewListChanged, this );
1047 m_currentValues[ dataList ] = getControlValue( dataList );
1048 }
1049 else
1050 {
1051 registerUndoRedoHandlers( child->GetChildren() );
1052 }
1053 }
1054}
1055
1056
1058{
1059 wxVariant before = m_currentValues[ aCtrl ];
1060 wxVariant after = getControlValue( aCtrl );
1061
1062 if( before != after )
1063 {
1064 m_undoStack.push_back( { aCtrl, before, after } );
1065 m_redoStack.clear();
1066 m_currentValues[ aCtrl ] = after;
1067 }
1068}
1069
1070
1071void DIALOG_SHIM::onCommandEvent( wxCommandEvent& aEvent )
1072{
1073 if( !m_handlingUndoRedo )
1074 recordControlChange( static_cast<wxWindow*>( aEvent.GetEventObject() ) );
1075
1076 aEvent.Skip();
1077}
1078
1079
1080void DIALOG_SHIM::onSpinEvent( wxSpinEvent& aEvent )
1081{
1082 if( !m_handlingUndoRedo )
1083 recordControlChange( static_cast<wxWindow*>( aEvent.GetEventObject() ) );
1084
1085 aEvent.Skip();
1086}
1087
1088
1089void DIALOG_SHIM::onSpinDoubleEvent( wxSpinDoubleEvent& aEvent )
1090{
1091 if( !m_handlingUndoRedo )
1092 recordControlChange( static_cast<wxWindow*>( aEvent.GetEventObject() ) );
1093
1094 aEvent.Skip();
1095}
1096
1097
1098void DIALOG_SHIM::onStyledTextChanged( wxStyledTextEvent& aEvent )
1099{
1100 if( !m_handlingUndoRedo )
1101 recordControlChange( static_cast<wxWindow*>( aEvent.GetEventObject() ) );
1102
1103 aEvent.Skip();
1104}
1105
1106
1107void DIALOG_SHIM::onGridCellChanged( wxGridEvent& aEvent )
1108{
1109 if( !m_handlingUndoRedo )
1110 recordControlChange( static_cast<wxWindow*>( aEvent.GetEventObject() ) );
1111
1112 aEvent.Skip();
1113}
1114
1115void DIALOG_SHIM::onPropertyGridChanged( wxPropertyGridEvent& aEvent )
1116{
1117 if( !m_handlingUndoRedo )
1118 recordControlChange( static_cast<wxWindow*>( aEvent.GetEventObject() ) );
1119
1120 aEvent.Skip();
1121}
1122
1123void DIALOG_SHIM::onDataViewListChanged( wxDataViewEvent& aEvent )
1124{
1125 if( !m_handlingUndoRedo )
1126 recordControlChange( static_cast<wxWindow*>( aEvent.GetEventObject() ) );
1127
1128 aEvent.Skip();
1129}
1130
1131wxVariant DIALOG_SHIM::getControlValue( wxWindow* aCtrl )
1132{
1133 if( wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( aCtrl ) )
1134 return wxVariant( textCtrl->GetValue() );
1135 else if( wxStyledTextCtrl* scintilla = dynamic_cast<wxStyledTextCtrl*>( aCtrl ) )
1136 return wxVariant( scintilla->GetText() );
1137 else if( wxComboBox* combo = dynamic_cast<wxComboBox*>( aCtrl ) )
1138 return wxVariant( combo->GetValue() );
1139 else if( wxChoice* choice = dynamic_cast<wxChoice*>( aCtrl ) )
1140 return wxVariant( (long) choice->GetSelection() );
1141 else if( wxCheckBox* check = dynamic_cast<wxCheckBox*>( aCtrl ) )
1142 return wxVariant( check->GetValue() );
1143 else if( wxSpinCtrl* spin = dynamic_cast<wxSpinCtrl*>( aCtrl ) )
1144 return wxVariant( (long) spin->GetValue() );
1145 else if( wxSpinCtrlDouble* spinD = dynamic_cast<wxSpinCtrlDouble*>( aCtrl ) )
1146 return wxVariant( spinD->GetValue() );
1147 else if( wxRadioButton* radio = dynamic_cast<wxRadioButton*>( aCtrl ) )
1148 return wxVariant( radio->GetValue() );
1149 else if( wxRadioBox* radioBox = dynamic_cast<wxRadioBox*>( aCtrl ) )
1150 return wxVariant( (long) radioBox->GetSelection() );
1151 else if( wxGrid* grid = dynamic_cast<wxGrid*>( aCtrl ) )
1152 {
1153 // Tables with regroupable/sortable rows serialize by identity instead of row position.
1154 if( auto* table = dynamic_cast<WX_GRID_TABLE_BASE*>( grid->GetTable() );
1155 table && table->HasUndoStateSerialization() )
1156 {
1157 return wxVariant( table->SerializeUndoState() );
1158 }
1159
1160 nlohmann::json j = nlohmann::json::array();
1161 int rows = grid->GetNumberRows();
1162 int cols = grid->GetNumberCols();
1163
1164 for( int r = 0; r < rows; ++r )
1165 {
1166 nlohmann::json row = nlohmann::json::array();
1167
1168 for( int c = 0; c < cols; ++c )
1169 row.push_back( std::string( grid->GetCellValue( r, c ).ToUTF8() ) );
1170
1171 j.push_back( row );
1172 }
1173
1174 return wxVariant( wxString( j.dump() ) );
1175 }
1176 else if( wxPropertyGrid* propGrid = dynamic_cast<wxPropertyGrid*>( aCtrl ) )
1177 {
1178 nlohmann::json j;
1179
1180 for( wxPropertyGridIterator it = propGrid->GetIterator(); !it.AtEnd(); ++it )
1181 {
1182 wxPGProperty* prop = *it;
1183 j[ prop->GetName().ToStdString() ] = prop->GetValueAsString().ToStdString();
1184 }
1185
1186 return wxVariant( wxString( j.dump() ) );
1187 }
1188 else if( wxCheckListBox* checkList = dynamic_cast<wxCheckListBox*>( aCtrl ) )
1189 {
1190 nlohmann::json j = nlohmann::json::array();
1191 unsigned int count = checkList->GetCount();
1192
1193 for( unsigned int i = 0; i < count; ++i )
1194 {
1195 if( checkList->IsChecked( i ) )
1196 j.push_back( i );
1197 }
1198
1199 return wxVariant( wxString( j.dump() ) );
1200 }
1201 else if( wxDataViewListCtrl* dataList = dynamic_cast<wxDataViewListCtrl*>( aCtrl ) )
1202 {
1203 nlohmann::json j = nlohmann::json::array();
1204 unsigned int rows = dataList->GetItemCount();
1205 unsigned int cols = dataList->GetColumnCount();
1206
1207 for( unsigned int r = 0; r < rows; ++r )
1208 {
1209 nlohmann::json row = nlohmann::json::array();
1210
1211 for( unsigned int c = 0; c < cols; ++c )
1212 {
1213 wxVariant val;
1214 dataList->GetValue( val, r, c );
1215 row.push_back( std::string( val.GetString().ToUTF8() ) );
1216 }
1217
1218 j.push_back( row );
1219 }
1220
1221 return wxVariant( wxString( j.dump() ) );
1222 }
1223 else
1224 return wxVariant();
1225}
1226
1227
1228void DIALOG_SHIM::setControlValue( wxWindow* aCtrl, const wxVariant& aValue )
1229{
1230 if( wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( aCtrl ) )
1231 textCtrl->SetValue( aValue.GetString() );
1232 else if( wxStyledTextCtrl* scintilla = dynamic_cast<wxStyledTextCtrl*>( aCtrl ) )
1233 scintilla->SetText( aValue.GetString() );
1234 else if( wxComboBox* combo = dynamic_cast<wxComboBox*>( aCtrl ) )
1235 combo->SetValue( aValue.GetString() );
1236 else if( wxChoice* choice = dynamic_cast<wxChoice*>( aCtrl ) )
1237 choice->SetSelection( (int) aValue.GetLong() );
1238 else if( wxCheckBox* check = dynamic_cast<wxCheckBox*>( aCtrl ) )
1239 check->SetValue( aValue.GetBool() );
1240 else if( wxSpinCtrl* spin = dynamic_cast<wxSpinCtrl*>( aCtrl ) )
1241 spin->SetValue( (int) aValue.GetLong() );
1242 else if( wxSpinCtrlDouble* spinD = dynamic_cast<wxSpinCtrlDouble*>( aCtrl ) )
1243 spinD->SetValue( aValue.GetDouble() );
1244 else if( wxRadioButton* radio = dynamic_cast<wxRadioButton*>( aCtrl ) )
1245 radio->SetValue( aValue.GetBool() );
1246 else if( wxRadioBox* radioBox = dynamic_cast<wxRadioBox*>( aCtrl ) )
1247 radioBox->SetSelection( (int) aValue.GetLong() );
1248 else if( wxGrid* grid = dynamic_cast<wxGrid*>( aCtrl ) )
1249 {
1250 if( auto* table = dynamic_cast<WX_GRID_TABLE_BASE*>( grid->GetTable() );
1251 table && table->HasUndoStateSerialization() )
1252 {
1253 table->RestoreUndoState( aValue.GetString() );
1254 return;
1255 }
1256
1257 nlohmann::json j = nlohmann::json::parse( aValue.GetString().ToStdString(), nullptr, false );
1258
1259 if( j.is_array() )
1260 {
1261 int rows = std::min( (int) j.size(), grid->GetNumberRows() );
1262
1263 for( int r = 0; r < rows; ++r )
1264 {
1265 nlohmann::json row = j[r];
1266 int cols = std::min( (int) row.size(), grid->GetNumberCols() );
1267
1268 for( int c = 0; c < cols; ++c )
1269 grid->SetCellValue( r, c, wxString( row[c].get<std::string>() ) );
1270 }
1271 }
1272 }
1273 else if( wxPropertyGrid* propGrid = dynamic_cast<wxPropertyGrid*>( aCtrl ) )
1274 {
1275 nlohmann::json j = nlohmann::json::parse( aValue.GetString().ToStdString(), nullptr, false );
1276
1277 if( j.is_object() )
1278 {
1279 for( auto it = j.begin(); it != j.end(); ++it )
1280 propGrid->SetPropertyValue( wxString( it.key() ), wxString( it.value().get<std::string>() ) );
1281 }
1282 }
1283 else if( wxCheckListBox* checkList = dynamic_cast<wxCheckListBox*>( aCtrl ) )
1284 {
1285 nlohmann::json j = nlohmann::json::parse( aValue.GetString().ToStdString(), nullptr, false );
1286
1287 if( j.is_array() )
1288 {
1289 unsigned int count = checkList->GetCount();
1290
1291 for( unsigned int i = 0; i < count; ++i )
1292 checkList->Check( i, false );
1293
1294 for( auto& idx : j )
1295 {
1296 unsigned int i = idx.get<unsigned int>();
1297
1298 if( i < count )
1299 checkList->Check( i, true );
1300 }
1301 }
1302 }
1303 else if( wxDataViewListCtrl* dataList = dynamic_cast<wxDataViewListCtrl*>( aCtrl ) )
1304 {
1305 nlohmann::json j = nlohmann::json::parse( aValue.GetString().ToStdString(), nullptr, false );
1306
1307 if( j.is_array() )
1308 {
1309 unsigned int rows = std::min( static_cast<unsigned int>( j.size() ),
1310 static_cast<unsigned int>( dataList->GetItemCount() ) );
1311
1312 for( unsigned int r = 0; r < rows; ++r )
1313 {
1314 nlohmann::json row = j[r];
1315 unsigned int cols = std::min( (unsigned int) row.size(), dataList->GetColumnCount() );
1316
1317 for( unsigned int c = 0; c < cols; ++c )
1318 {
1319 wxVariant val( wxString( row[c].get<std::string>() ) );
1320 dataList->SetValue( val, r, c );
1321 }
1322 }
1323 }
1324 }
1325}
1326
1327
1329{
1330 if( m_undoStack.empty() )
1331 return;
1332
1333 m_handlingUndoRedo = true;
1334 UNDO_STEP step = m_undoStack.back();
1335 m_undoStack.pop_back();
1336 setControlValue( step.ctrl, step.before );
1337 m_currentValues[ step.ctrl ] = step.before;
1338 m_redoStack.push_back( step );
1339 m_handlingUndoRedo = false;
1340}
1341
1342
1344{
1345 if( m_redoStack.empty() )
1346 return;
1347
1348 m_handlingUndoRedo = true;
1349 UNDO_STEP step = m_redoStack.back();
1350 m_redoStack.pop_back();
1351 setControlValue( step.ctrl, step.after );
1352 m_currentValues[ step.ctrl ] = step.after;
1353 m_undoStack.push_back( step );
1354 m_handlingUndoRedo = false;
1355}
1356
1357
1358void DIALOG_SHIM::OnPaint( wxPaintEvent &event )
1359{
1360 if( m_firstPaintEvent )
1361 {
1363
1364 SelectAllInTextCtrls( GetChildren() );
1365 registerUndoRedoHandlers( GetChildren() );
1366
1369 else
1370 KIPLATFORM::UI::ForceFocus( this ); // Focus the dialog itself
1371
1372 m_firstPaintEvent = false;
1373 }
1374
1375 event.Skip();
1376}
1377
1378
1380{
1381 if( !GetTitle().StartsWith( wxS( "*" ) ) )
1382 SetTitle( wxS( "*" ) + GetTitle() );
1383}
1384
1385
1387{
1388 if( GetTitle().StartsWith( wxS( "*" ) ) )
1389 SetTitle( GetTitle().AfterFirst( '*' ) );
1390}
1391
1393{
1395
1396 // Apple in its infinite wisdom will raise a disabled window before even passing
1397 // us the event, so we have no way to stop it. Instead, we must set an order on
1398 // the windows so that the modal will be pushed in front of the disabled
1399 // window when it is raised.
1401
1402 // Call the base class ShowModal() method
1403 return wxDialog::ShowModal();
1404}
1405
1406/*
1407 QuasiModal Mode Explained:
1408
1409 The gtk calls in wxDialog::ShowModal() cause event routing problems if that
1410 modal dialog then tries to use KIWAY_PLAYER::ShowModal(). The latter shows up
1411 and mostly works but does not respond to the window decoration close button.
1412 There is no way to get around this without reversing the gtk calls temporarily.
1413
1414 There are also issues with the Scintilla text editor putting up autocomplete
1415 popups, which appear behind the dialog window if QuasiModal is not used.
1416
1417 QuasiModal mode is our own almost modal mode which disables only the parent
1418 of the DIALOG_SHIM, leaving other frames operable and while staying captured in the
1419 nested event loop. This avoids the gtk calls and leaves event routing pure
1420 and sufficient to operate the KIWAY_PLAYER::ShowModal() properly. When using
1421 ShowQuasiModal() you have to use EndQuasiModal() in your dialogs and not
1422 EndModal(). There is also IsQuasiModal() but its value can only be true
1423 when the nested event loop is active. Do not mix the modal and quasi-modal
1424 functions. Use one set or the other.
1425
1426 You might find this behavior preferable over a pure modal mode, and it was said
1427 that only the Mac has this natively, but now other platforms have something
1428 similar. You CAN use it anywhere for any dialog. But you MUST use it when
1429 you want to use KIWAY_PLAYER::ShowModal() from a dialog event.
1430*/
1431
1433{
1434 NULLER raii_nuller( (void*&) m_qmodal_loop );
1435
1436 // release the mouse if it's currently captured as the window having it
1437 // will be disabled when this dialog is shown -- but will still keep the
1438 // capture making it impossible to do anything in the modal dialog itself
1439 if( wxWindow* win = wxWindow::GetCapture() )
1440 win->ReleaseMouse();
1441
1442 // Get the optimal parent
1443 wxWindow* parent = GetParentForModalDialog( GetParent(), GetWindowStyle() );
1444
1445 wxASSERT_MSG( !m_qmodal_parent_disabler, wxT( "Caller using ShowQuasiModal() twice on same window?" ) );
1446
1447 // quasi-modal: disable only my "optimal" parent
1449
1450 // Apple in its infinite wisdom will raise a disabled window before even passing
1451 // us the event, so we have no way to stop it. Instead, we must set an order on
1452 // the windows so that the quasi-modal will be pushed in front of the disabled
1453 // window when it is raised.
1455
1456 Show( true );
1457
1458 m_qmodal_showing = true;
1459
1460 wxGUIEventLoop event_loop;
1461
1462 m_qmodal_loop = &event_loop;
1463
1464 event_loop.Run();
1465
1466 m_qmodal_showing = false;
1467 focusParentCanvas( true );
1468
1469 return GetReturnCode();
1470}
1471
1472
1474{
1476 m_qmodal_parent_disabler->SuspendForTrueModal();
1477}
1478
1479
1481{
1483 m_qmodal_parent_disabler->ResumeAfterTrueModal();
1484}
1485
1486
1488{
1489 // Hook up validator and transfer data from controls handling so quasi-modal dialogs
1490 // handle validation in the same way as other dialogs.
1491 if( ( retCode == wxID_OK ) && ( !Validate() || !TransferDataFromWindow() ) )
1492 return;
1493
1494 SetReturnCode( retCode );
1495
1496 if( !IsQuasiModal() )
1497 {
1498 wxFAIL_MSG( wxT( "Either DIALOG_SHIM::EndQuasiModal was called twice, or ShowQuasiModal wasn't called" ) );
1499 return;
1500 }
1501
1503
1504 if( m_qmodal_loop )
1505 {
1506 if( m_qmodal_loop->IsRunning() )
1507 m_qmodal_loop->Exit( 0 );
1508 else
1509 m_qmodal_loop->ScheduleExit( 0 );
1510 }
1511
1513 m_qmodal_parent_disabler = nullptr;
1514
1515 Show( false );
1516}
1517
1518
1519void DIALOG_SHIM::resetUndoRedoForNewContent( wxWindowList& aChildren )
1520{
1521 m_undoStack.clear();
1522 m_redoStack.clear();
1523 m_currentValues.clear();
1524 registerUndoRedoHandlers( aChildren );
1525}
1526
1527
1528void DIALOG_SHIM::unregisterUnitBinders( wxWindow* aWindow )
1529{
1530 m_unitBinders.erase( aWindow );
1531
1532 for( wxWindow* child : aWindow->GetChildren() )
1533 unregisterUnitBinders( child );
1534}
1535
1536
1537void DIALOG_SHIM::OnCloseWindow( wxCloseEvent& aEvent )
1538{
1539 wxString msg = wxString::Format( "Closing dialog %s", GetTitle() );
1540 APP_MONITOR::AddNavigationBreadcrumb( msg, "dialog.close" );
1541
1543
1544 if( IsQuasiModal() )
1545 {
1546 EndQuasiModal( wxID_CANCEL );
1547 return;
1548 }
1549
1550 // This is mandatory to allow wxDialogBase::OnCloseWindow() to be called.
1551 aEvent.Skip();
1552}
1553
1554
1555void DIALOG_SHIM::OnButton( wxCommandEvent& aEvent )
1556{
1557 const int id = aEvent.GetId();
1558
1559 if( IsQuasiModal() )
1560 {
1561 if( id == GetAffirmativeId() )
1562 {
1563 EndQuasiModal( id );
1564 }
1565 else if( id == wxID_APPLY )
1566 {
1567 // Dialogs that provide Apply buttons should make sure data is valid before
1568 // allowing a transfer, as there is no other way to indicate failure
1569 // (i.e. the dialog can't refuse to close as it might with OK, because it
1570 // isn't closing anyway)
1571 if( Validate() )
1572 ignore_unused( TransferDataFromWindow() );
1573 }
1574 else if( id == wxID_CANCEL )
1575 {
1576 EndQuasiModal( wxID_CANCEL );
1577 }
1578 else // not a standard button
1579 {
1580 aEvent.Skip();
1581 }
1582
1583 return;
1584 }
1585
1586 // This is mandatory to allow wxDialogBase::OnButton() to be called.
1587 aEvent.Skip();
1588}
1589
1590
1591void DIALOG_SHIM::onChildSetFocus( wxFocusEvent& aEvent )
1592{
1593 // When setting focus to a text control reset the before-edit value.
1594
1595 if( !m_isClosing )
1596 {
1597 if( wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( aEvent.GetEventObject() ) )
1598 m_beforeEditValues[ textCtrl ] = textCtrl->GetValue();
1599 else if( wxStyledTextCtrl* scintilla = dynamic_cast<wxStyledTextCtrl*>( aEvent.GetEventObject() ) )
1600 m_beforeEditValues[ scintilla ] = scintilla->GetText();
1601 }
1602
1603 aEvent.Skip();
1604}
1605
1606
1607void DIALOG_SHIM::OnCharHook( wxKeyEvent& aEvt )
1608{
1609 int key = aEvt.GetKeyCode();
1610 int mods = 0;
1611
1612 if( aEvt.ControlDown() )
1613 mods |= MD_CTRL;
1614 if( aEvt.ShiftDown() )
1615 mods |= MD_SHIFT;
1616 if( aEvt.AltDown() )
1617 mods |= MD_ALT;
1618
1619 int hotkey = key | mods;
1620
1621 // Check for standard undo/redo hotkeys
1622 if( hotkey == (MD_CTRL + 'Z') )
1623 {
1624 doUndo();
1625 return;
1626 }
1627 else if( hotkey == (MD_CTRL + MD_SHIFT + 'Z') || hotkey == (MD_CTRL + 'Y') )
1628 {
1629 doRedo();
1630 return;
1631 }
1632
1633 if( aEvt.GetKeyCode() == 'U' && aEvt.GetModifiers() == wxMOD_CONTROL )
1634 {
1635 if( m_parentFrame )
1636 {
1637 m_parentFrame->ToggleUserUnits();
1638 return;
1639 }
1640 }
1641 // shift-return (Mac default) or Ctrl-Return (GTK) for new line input
1642 else if( ( aEvt.GetKeyCode() == WXK_RETURN || aEvt.GetKeyCode() == WXK_NUMPAD_ENTER ) && aEvt.ShiftDown() )
1643 {
1644 wxObject* eventSource = aEvt.GetEventObject();
1645
1646 if( wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( eventSource ) )
1647 {
1648 // If the text control is not multi-line, we want to close the dialog
1649 if( !textCtrl->IsMultiLine() )
1650 {
1651 wxPostEvent( this, wxCommandEvent( wxEVT_COMMAND_BUTTON_CLICKED, wxID_OK ) );
1652 return;
1653 }
1654
1655#if defined( __WXMAC__ ) || defined( __WXMSW__ )
1656 wxString eol = "\r\n";
1657#else
1658 wxString eol = "\n";
1659#endif
1660
1661 long pos = textCtrl->GetInsertionPoint();
1662 textCtrl->WriteText( eol );
1663 textCtrl->SetInsertionPoint( pos + eol.length() );
1664 return;
1665 }
1666 else if( wxStyledTextCtrl* scintilla = dynamic_cast<wxStyledTextCtrl*>( eventSource ) )
1667 {
1668 wxString eol = "\n";
1669
1670 switch( scintilla->GetEOLMode() )
1671 {
1672 case wxSTC_EOL_CRLF: eol = "\r\n"; break;
1673 case wxSTC_EOL_CR: eol = "\r"; break;
1674 case wxSTC_EOL_LF: eol = "\n"; break;
1675 }
1676
1677 long pos = scintilla->GetCurrentPos();
1678 scintilla->InsertText( pos, eol );
1679 scintilla->GotoPos( pos + eol.length() );
1680 return;
1681 }
1682 return;
1683 }
1684 // command-return (Mac default) or Ctrl-Return (GTK) for OK
1685 else if( ( aEvt.GetKeyCode() == WXK_RETURN || aEvt.GetKeyCode() == WXK_NUMPAD_ENTER ) && aEvt.ControlDown() )
1686 {
1687 wxPostEvent( this, wxCommandEvent( wxEVT_COMMAND_BUTTON_CLICKED, wxID_OK ) );
1688 return;
1689 }
1690 else if( aEvt.GetKeyCode() == WXK_TAB && !aEvt.ControlDown() )
1691 {
1692 wxWindow* currentWindow = wxWindow::FindFocus();
1693 int currentIdx = -1;
1694 int delta = aEvt.ShiftDown() ? -1 : 1;
1695
1696 auto advance =
1697 [&]( int& idx )
1698 {
1699 // Wrap-around modulus
1700 int size = (int) m_tabOrder.size();
1701 idx = ( ( idx + delta ) % size + size ) % size;
1702 };
1703
1704 for( size_t i = 0; i < m_tabOrder.size(); ++i )
1705 {
1706 // Check for exact match or if currentWindow is a child of the control
1707 // (e.g., the text entry inside a wxComboBox)
1708 if( m_tabOrder[i] == currentWindow
1709 || ( currentWindow && m_tabOrder[i]->IsDescendant( currentWindow ) ) )
1710 {
1711 currentIdx = (int) i;
1712 break;
1713 }
1714 }
1715
1716 if( currentIdx >= 0 )
1717 {
1718 advance( currentIdx );
1719
1720 // Skip hidden or disabled controls
1721 int startIdx = currentIdx;
1722
1723 while( !m_tabOrder[currentIdx]->IsShown() || !m_tabOrder[currentIdx]->IsEnabled() )
1724 {
1725 advance( currentIdx );
1726
1727 if( currentIdx == startIdx )
1728 break; // Avoid infinite loop if all controls are hidden
1729 }
1730
1731 //todo: We don't currently have non-textentry dialog boxes but this will break if
1732 // we add them.
1733#ifdef __APPLE__
1734 while( dynamic_cast<wxTextEntry*>( m_tabOrder[ currentIdx ] ) == nullptr )
1735 advance( currentIdx );
1736#endif
1737
1738 m_tabOrder[ currentIdx ]->SetFocus();
1739 return;
1740 }
1741 }
1742 else if( aEvt.GetKeyCode() == WXK_ESCAPE )
1743 {
1744 wxObject* eventSource = aEvt.GetEventObject();
1745
1746 if( wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( eventSource ) )
1747 {
1748 // First escape after an edit cancels edit
1749 if( textCtrl->GetValue() != m_beforeEditValues[ textCtrl ] )
1750 {
1751 textCtrl->SetValue( m_beforeEditValues[ textCtrl ] );
1752 textCtrl->SelectAll();
1753 return;
1754 }
1755 }
1756 else if( wxStyledTextCtrl* scintilla = dynamic_cast<wxStyledTextCtrl*>( eventSource ) )
1757 {
1758 // First escape after an edit cancels edit
1759 if( scintilla->GetText() != m_beforeEditValues[ scintilla ] )
1760 {
1761 scintilla->SetText( m_beforeEditValues[ scintilla ] );
1762 scintilla->SelectAll();
1763 return;
1764 }
1765 }
1766 }
1767
1768 aEvt.Skip();
1769}
1770
1771
1772static void recursiveDescent( wxSizer* aSizer, std::map<int, wxString>& aLabels )
1773{
1774 wxStdDialogButtonSizer* sdbSizer = dynamic_cast<wxStdDialogButtonSizer*>( aSizer );
1775
1776 auto setupButton =
1777 [&]( wxButton* aButton )
1778 {
1779 if( aLabels.count( aButton->GetId() ) > 0 )
1780 {
1781 aButton->SetLabel( aLabels[ aButton->GetId() ] );
1782 }
1783 else
1784 {
1785 // wxWidgets has an uneven track record when the language is changed on
1786 // the fly so we set them even when they don't appear in the label map
1787 switch( aButton->GetId() )
1788 {
1789 case wxID_OK: aButton->SetLabel( _( "&OK" ) ); break;
1790 case wxID_CANCEL: aButton->SetLabel( _( "&Cancel" ) ); break;
1791 case wxID_YES: aButton->SetLabel( _( "&Yes" ) ); break;
1792 case wxID_NO: aButton->SetLabel( _( "&No" ) ); break;
1793 case wxID_APPLY: aButton->SetLabel( _( "&Apply" ) ); break;
1794 case wxID_SAVE: aButton->SetLabel( _( "&Save" ) ); break;
1795 case wxID_HELP: aButton->SetLabel( _( "&Help" ) ); break;
1796 case wxID_CONTEXT_HELP: aButton->SetLabel( _( "&Help" ) ); break;
1797 }
1798 }
1799 };
1800
1801 if( sdbSizer )
1802 {
1803 if( sdbSizer->GetAffirmativeButton() )
1804 setupButton( sdbSizer->GetAffirmativeButton() );
1805
1806 if( sdbSizer->GetApplyButton() )
1807 setupButton( sdbSizer->GetApplyButton() );
1808
1809 if( sdbSizer->GetNegativeButton() )
1810 setupButton( sdbSizer->GetNegativeButton() );
1811
1812 if( sdbSizer->GetCancelButton() )
1813 setupButton( sdbSizer->GetCancelButton() );
1814
1815 if( sdbSizer->GetHelpButton() )
1816 setupButton( sdbSizer->GetHelpButton() );
1817
1818 sdbSizer->Layout();
1819
1820 if( sdbSizer->GetAffirmativeButton() )
1821 sdbSizer->GetAffirmativeButton()->SetDefault();
1822 }
1823
1824 for( wxSizerItem* item : aSizer->GetChildren() )
1825 {
1826 if( item->GetSizer() )
1827 recursiveDescent( item->GetSizer(), aLabels );
1828 }
1829}
1830
1831
1832void DIALOG_SHIM::SetupStandardButtons( std::map<int, wxString> aLabels )
1833{
1834 recursiveDescent( GetSizer(), aLabels );
1835}
int index
const char * name
COMMON_SETTINGS_INTERNALS & CsInternals()
Dialog helper object to sit in the inheritance tree between wxDialog and any class written by wxFormB...
Definition dialog_shim.h:65
void SelectAllInTextCtrls(wxWindowList &children)
wxVariant getControlValue(wxWindow *aCtrl)
bool m_handlingUndoRedo
void onPropertyGridChanged(wxPropertyGridEvent &aEvent)
std::set< wxWindow * > m_noControlUndoRedo
std::vector< wxWindow * > m_tabOrder
void OnPaint(wxPaintEvent &event)
bool m_qmodal_showing
virtual void TearDownQuasiModal()
Override this method to perform dialog tear down actions not suitable for object dtor.
void recordControlChange(wxWindow *aCtrl)
int vertPixelsFromDU(int y) const
Convert an integer number of dialog units to pixels, vertically.
bool Show(bool show) override
std::vector< UNDO_STEP > m_redoStack
void setControlValue(wxWindow *aCtrl, const wxVariant &aValue)
wxGUIEventLoop * m_qmodal_loop
void onChildSetFocus(wxFocusEvent &aEvent)
EDA_UNITS m_units
void LoadControlState()
Load persisted control values from the current project's local settings.
void ExcludeFromControlUndoRedo(wxWindow *aWindow)
Opt a control out of the dialog's generic Ctrl+Z/Ctrl+Y undo/redo.
void UnregisterUnitBinder(UNIT_BINDER *aUnitBinder)
Remove a UNIT_BINDER from the control-state save/restore map.
void OptOut(wxWindow *aWindow)
Opt out of control state saving.
void SaveControlState()
Save control values and geometry to the current project's local settings.
void SetupStandardButtons(std::map< int, wxString > aLabels={})
WINDOW_DISABLER * m_qmodal_parent_disabler
void onInitDialog(wxInitDialogEvent &aEvent)
std::string m_hash_key
bool m_firstPaintEvent
bool m_userResized
void onSpinDoubleEvent(wxSpinDoubleEvent &aEvent)
void resetUndoRedoForNewContent(wxWindowList &aChildren)
Reset undo/redo tracking after dynamically replacing child panels.
int horizPixelsFromDU(int x) const
Convert an integer number of dialog units to pixels, horizontally.
void resetSize()
Clear the existing dialog size and position.
std::map< wxWindow *, wxString > m_beforeEditValues
void setSizeInDU(int x, int y)
Set the dialog to the given dimensions in "dialog units".
void onDataViewListChanged(wxDataViewEvent &aEvent)
void unregisterUnitBinders(wxWindow *aWindow)
Remove UNIT_BINDER registrations for a window and all its descendants.
bool IsQuasiModal() const
Definition dialog_shim.h:90
bool m_useCalculatedSize
std::map< wxWindow *, UNIT_BINDER * > m_unitBinders
bool m_childReleased
void EndQuasiModal(int retCode)
void RegisterUnitBinder(UNIT_BINDER *aUnitBinder, wxWindow *aWindow)
Register a UNIT_BINDER so that it can handle units in control-state save/restore.
void OnMove(wxMoveEvent &aEvent)
void onCommandEvent(wxCommandEvent &aEvent)
void CleanupAfterModalSubDialog()
std::string generateKey(const wxWindow *aWin) const
void PrepareForModalSubDialog()
void OnButton(wxCommandEvent &aEvent)
Properly handle the default button events when in the quasimodal mode when not calling EndQuasiModal ...
void onGridCellChanged(wxGridEvent &aEvent)
void finishDialogSettings()
In all dialogs, we must call the same functions to fix minimal dlg size, the default position and per...
void registerUndoRedoHandlers(wxWindowList &aChildren)
wxWindow * m_initialFocusTarget
void OnSize(wxSizeEvent &aEvent)
bool Enable(bool enable) override
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)
void focusParentCanvas(bool aDeferUntilFrameActive=false)
Set focus back to the parent frame's tool canvas if available, otherwise to the parent window.
void SetPosition(const wxPoint &aNewPosition)
Force the position of the dialog to a new position.
void onSpinEvent(wxSpinEvent &aEvent)
bool m_userPositioned
void OnCloseWindow(wxCloseEvent &aEvent)
Properly handle the wxCloseEvent when in the quasimodal mode when not calling EndQuasiModal which is ...
std::map< wxWindow *, wxVariant > m_currentValues
wxSize m_initialSize
EDA_BASE_FRAME * m_parentFrame
virtual void OnCharHook(wxKeyEvent &aEvt)
std::vector< UNDO_STEP > m_undoStack
void onStyledTextChanged(wxStyledTextEvent &aEvent)
int ShowModal() override
The base frame for deriving all KiCad main window classes.
KIWAY_HOLDER(KIWAY *aKiway, HOLDER_TYPE aType)
KIWAY & Kiway() const
Return a reference to the KIWAY that this object has an opportunity to participate in.
void SetKiway(wxWindow *aDest, KIWAY *aKiway)
It is only used for debugging, since "this" is not a wxWindow*.
bool HasKiway() const
Safety check before asking for the Kiway reference.
HOLDER_TYPE GetType() const
void SetBlockingDialog(wxWindow *aWin)
Definition kiway.cpp:692
Definition raii.h:34
virtual COMMON_SETTINGS * GetCommonSettings() const
Definition pgm_base.cpp:528
virtual wxApp & App()
Return a bare naked wxApp which may come from wxPython, SINGLE_TOP, or kicad.exe.
Definition pgm_base.cpp:204
bool SetProperty(const std::string &aKey, T &&aValue)
Set a property with the given key and value.
static PROPERTY_HOLDER * SafeCast(void *aPtr) noexcept
Safely cast a void pointer to PROPERTY_HOLDER*.
virtual wxWindow * GetToolCanvas() const =0
Canvas access.
EDA_UNITS GetUserUnits() const
Temporarily disable a window, and then re-enable on destruction.
Definition raii.h:83
static std::string getDialogKeyFromTitle(const wxString &aTitle)
Strip parenthetical suffixes from dialog titles to create stable persistence keys.
static void recursiveDescent(wxSizer *aSizer, std::map< int, wxString > &aLabels)
const int minSize
Push and Shove router track width and via size dialog.
#define _(s)
EDA_UNITS
Definition eda_units.h:44
void ignore_unused(const T &)
Definition ignore.h:20
void AddNavigationBreadcrumb(const wxString &aMsg, const wxString &aCategory)
Add a navigation breadcrumb.
void ReleaseChildWindow(wxNonOwnedWindow *aWindow)
Release a modal window's parent-child relationship with its parent window.
Definition wxgtk/ui.cpp:442
void FixupCancelButtonCmdKeyCollision(wxWindow *aWindow)
Definition wxgtk/ui.cpp:193
void StabilizeWindowPosition(wxWindow *aWindow)
Prepare a top-level window for reliable position round-tripping.
Definition wxgtk/ui.cpp:169
void EnsureVisible(wxWindow *aWindow)
Ensure that a window is visible on the screen.
Definition wxgtk/ui.cpp:163
bool IsWindowActive(wxWindow *aWindow)
Check to see if the given window is the currently active window (e.g.
Definition wxgtk/ui.cpp:148
void ForceFocus(wxWindow *aWindow)
Pass the current focus to the window.
Definition wxgtk/ui.cpp:126
void ReparentModal(wxNonOwnedWindow *aWindow)
Move a window's parent to be the top-level window and force the window to be on top.
Definition wxgtk/ui.cpp:181
STL namespace.
static wxString makeKey(const wxString &aFirst, const wxString &aSecond)
Assemble a two part key as a simple concatenation of aFirst and aSecond parts, using a separator.
PGM_BASE & Pgm()
The global program "get" accessor.
see class PGM_BASE
std::map< std::string, std::map< std::string, nlohmann::json > > m_dialogControlValues
static const long long MM
std::vector< std::vector< std::string > > table
VECTOR2I end
int delta
@ MD_ALT
Definition tool_event.h:141
@ MD_CTRL
Definition tool_event.h:140
@ MD_SHIFT
Definition tool_event.h:139