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
318{
319 // A dialog not yet mapped onto a monitor reports no display, so fall back to the parent's
320 // monitor rather than blindly clamping against display zero on a multi-head setup.
321 int displayIdx = wxDisplay::GetFromWindow( this );
322
323 if( displayIdx == wxNOT_FOUND && m_parent )
324 displayIdx = wxDisplay::GetFromWindow( m_parent );
325
326 if( displayIdx == wxNOT_FOUND )
327 displayIdx = 0;
328
329 wxSize workArea = wxDisplay( (unsigned int) displayIdx ).GetClientArea().GetSize();
330
331 if( workArea.x <= 0 || workArea.y <= 0 )
332 return;
333
334 wxSize minSize = GetMinSize();
335 wxSize clampedMin( std::min( minSize.x, workArea.x ), std::min( minSize.y, workArea.y ) );
336
337 if( clampedMin != minSize )
338 SetMinSize( clampedMin );
339
340 wxSize size = GetSize();
341 wxSize clampedSize( std::min( size.x, workArea.x ), std::min( size.y, workArea.y ) );
342
343 if( clampedSize != size )
344 SetSize( clampedSize );
345}
346
347
348void DIALOG_SHIM::setSizeInDU( int x, int y )
349{
350 wxSize sz( x, y );
351 SetSize( ConvertDialogToPixels( sz ) );
352}
353
354
356{
357 wxSize sz( x, 0 );
358 return ConvertDialogToPixels( sz ).x;
359}
360
361
363{
364 wxSize sz( 0, y );
365 return ConvertDialogToPixels( sz ).y;
366}
367
368
369// our hashtable is an implementation secret, don't need or want it in a header file
370#include <hashtables.h>
371#include <typeinfo>
372#include <grid_tricks.h>
373
374
375void DIALOG_SHIM::SetPosition( const wxPoint& aNewPosition )
376{
377 wxDialog::SetPosition( aNewPosition );
378}
379
380
381void DIALOG_SHIM::focusParentCanvas( bool aDeferUntilFrameActive )
382{
383 wxWindow* toolCanvas = m_parentFrame ? m_parentFrame->GetToolCanvas() : nullptr;
384 wxWindow* target = toolCanvas ? toolCanvas : m_parent;
385
386 if( !target )
387 return;
388
389 target->SetFocus();
390
391 if( !aDeferUntilFrameActive || !toolCanvas )
392 return;
393
394#ifdef __WXGTK__
395 // A quasi-modal dialog is still the active top-level window when its nested event loop exits,
396 // so the SetFocus() above is undone when the dialog is destroyed and GTK restores the frame's
397 // previously-focused widget. Re-assert focus once the event loop has settled, otherwise
398 // keyboard events keep routing to the stale owner until the mouse re-enters the canvas.
400
401 frame->CallAfter(
402 [frame]()
403 {
404 // Skip if another dialog grabbed the activation in the meantime, otherwise we
405 // would raise the frame from behind a chained modal dialog.
406 if( !KIPLATFORM::UI::IsWindowActive( frame ) )
407 return;
408
409 if( wxWindow* canvas = frame->GetToolCanvas() )
410 canvas->SetFocus();
411 } );
412#endif
413}
414
415
416bool DIALOG_SHIM::Show( bool show )
417{
418 bool ret;
419
420 if( show )
421 {
423
424#ifndef __WINDOWS__
425 wxDialog::Raise(); // Needed on OS X and some other window managers (i.e. Unity)
426#endif
427 ret = wxDialog::Show( show );
428
429 wxRect savedDialogRect;
430 std::string key = m_hash_key.empty() ? getDialogKeyFromTitle( GetTitle() ) : m_hash_key;
431
432 if( COMMON_SETTINGS* settings = Pgm().GetCommonSettings() )
433 {
434 auto dlgIt = settings->CsInternals().m_dialogControlValues.find( key );
435
436 if( dlgIt != settings->CsInternals().m_dialogControlValues.end() )
437 {
438 auto geoIt = dlgIt->second.find( "__geometry" );
439
440 if( geoIt != dlgIt->second.end() && geoIt->second.is_object() )
441 {
442 const nlohmann::json& g = geoIt->second;
443 savedDialogRect.SetPosition( wxPoint( g.value( "x", 0 ), g.value( "y", 0 ) ) );
444 savedDialogRect.SetSize( wxSize( g.value( "w", 500 ), g.value( "h", 300 ) ) );
445 }
446 }
447 }
448
449 if( savedDialogRect.GetSize().x != 0 && savedDialogRect.GetSize().y != 0 )
450 {
451 // Convert saved DIP size to logical pixels for the current monitor
452 wxSize restoredSize = FromDIP( savedDialogRect.GetSize() );
453
455 {
456 SetSize( savedDialogRect.GetPosition().x, savedDialogRect.GetPosition().y,
457 wxDialog::GetSize().x, wxDialog::GetSize().y, 0 );
458 }
459 else
460 {
461 SetSize( savedDialogRect.GetPosition().x, savedDialogRect.GetPosition().y,
462 std::max( wxDialog::GetSize().x, restoredSize.x ),
463 std::max( wxDialog::GetSize().y, restoredSize.y ), 0 );
464
465 // Reset minimum size so the user can resize the dialog smaller than
466 // the saved size. We must clear the current minimum and invalidate
467 // the cached best size so GetBestSize() returns the true sizer
468 // minimum rather than being constrained by the restored size.
469 SetMinSize( wxDefaultSize );
470 InvalidateBestSize();
471 SetMinSize( GetBestSize() );
472 }
473
474#ifdef __WXMAC__
475 if( m_parent != nullptr )
476 {
477 if( wxDisplay::GetFromPoint( m_parent->GetPosition() )
478 != wxDisplay::GetFromPoint( savedDialogRect.GetPosition() ) )
479 {
480 Centre();
481 }
482 }
483#endif
484
485 }
486 else if( m_initialSize != wxDefaultSize )
487 {
488 SetSize( m_initialSize );
489 Centre();
490 }
491
492 // Re-center if the title bar would land on no display. Testing a point inside the title
493 // bar (not the window corner) ignores the negative border offset of maximized windows.
494 wxPoint grabPoint = GetPosition();
495 grabPoint.x += GetSize().x / 2;
496 grabPoint.y += FromDIP( 15 );
497
498 if( wxDisplay::GetFromPoint( grabPoint ) == wxNOT_FOUND )
499 Centre();
500
501 m_userPositioned = false;
502 m_userResized = false;
503
504 // A dialog whose content is taller or wider than the monitor must still fit on it; clamp
505 // here, after the minimum has been (re)established above, so the cap is not overwritten.
507
509 }
510 else
511 {
512
513#ifdef __WXMAC__
514 if ( m_eventLoop )
515 m_eventLoop->Exit( GetReturnCode() ); // Needed for APP-MODAL dlgs on OSX
516#endif
517
518 ret = wxDialog::Show( show );
519
522 }
523
524 return ret;
525}
526
527
529{
530 if( COMMON_SETTINGS* settings = Pgm().GetCommonSettings() )
531 {
532 std::string key = m_hash_key.empty() ? getDialogKeyFromTitle( GetTitle() ) : m_hash_key;
533
534 auto dlgIt = settings->CsInternals().m_dialogControlValues.find( key );
535
536 if( dlgIt == settings->CsInternals().m_dialogControlValues.end() )
537 return;
538
539 dlgIt->second.erase( "__geometry" );
540 }
541}
542
543
544void DIALOG_SHIM::OnSize( wxSizeEvent& aEvent )
545{
546 m_userResized = true;
547 aEvent.Skip();
548}
549
550
551void DIALOG_SHIM::OnMove( wxMoveEvent& aEvent )
552{
553 m_userPositioned = true;
554
555#ifdef __WXMAC__
556 if( m_parent )
557 {
558 int parentDisplay = wxDisplay::GetFromWindow( m_parent );
559 int myDisplay = wxDisplay::GetFromWindow( this );
560
561 if( parentDisplay != wxNOT_FOUND && myDisplay != wxNOT_FOUND )
562 {
563 if( myDisplay != parentDisplay && !m_childReleased )
564 {
565 // Moving to different monitor - release child relationship
567 m_childReleased = true;
568 }
569 else if( myDisplay == parentDisplay && m_childReleased )
570 {
571 // Back on same monitor - restore child relationship
573 m_childReleased = false;
574 }
575 }
576 }
577#endif
578
579 aEvent.Skip();
580}
581
582
583bool DIALOG_SHIM::Enable( bool enable )
584{
585 // so we can do logging of this state change:
586 return wxDialog::Enable( enable );
587}
588
589
590std::string DIALOG_SHIM::generateKey( const wxWindow* aWin ) const
591{
592 auto getSiblingIndex =
593 []( const wxWindow* parent, const wxWindow* child )
594 {
595 wxString childClass = child->GetClassInfo()->GetClassName();
596 int index = 0;
597
598 for( const wxWindow* sibling : parent->GetChildren() )
599 {
600 if( sibling->GetClassInfo()->GetClassName() != childClass )
601 continue;
602
603 if( sibling == child )
604 break;
605
606 index++;
607 }
608
609 return index;
610 };
611
612 auto makeKey =
613 [&]( const wxWindow* window )
614 {
615 std::string key = wxString( window->GetClassInfo()->GetClassName() ).ToStdString();
616
617 if( window->GetParent() )
618 key += "_" + std::to_string( getSiblingIndex( window->GetParent(), window ) );
619
620 return key;
621 };
622
623 std::string key = makeKey( aWin );
624
625 for( const wxWindow* parent = aWin->GetParent(); parent && parent != this; parent = parent->GetParent() )
626 key = makeKey( parent ) + key;
627
628 return key;
629}
630
631
633{
634 COMMON_SETTINGS* settings = Pgm().GetCommonSettings();
635
636 if( !settings )
637 return;
638
639 std::string dialogKey = m_hash_key.empty() ? getDialogKeyFromTitle( GetTitle() ) : m_hash_key;
640 std::map<std::string, nlohmann::json>& dlgMap = settings->CsInternals().m_dialogControlValues[ dialogKey ];
641
642 wxPoint pos = GetPosition();
643 wxSize dipSize = ToDIP( GetSize() );
644 nlohmann::json geom;
645 geom[ "x" ] = pos.x;
646 geom[ "y" ] = pos.y;
647 geom[ "w" ] = dipSize.x;
648 geom[ "h" ] = dipSize.y;
649 dlgMap[ "__geometry" ] = geom;
650
651 std::function<void( wxWindow* )> saveFn =
652 [&]( wxWindow* win )
653 {
654 if( PROPERTY_HOLDER* props = PROPERTY_HOLDER::SafeCast( win->GetClientData() ) )
655 {
656 if( !props->GetPropertyOr( "persist", false ) )
657 return;
658 }
659
660 std::string key = generateKey( win );
661
662 if( !key.empty() )
663 {
664 if( m_unitBinders.contains( win ) && !m_unitBinders[ win ]->UnitsInvariant() )
665 {
666 dlgMap[ key ] = m_unitBinders[ win ]->GetValue();
667 }
668 else if( wxComboBox* combo = dynamic_cast<wxComboBox*>( win ) )
669 {
670 dlgMap[ key ] = combo->GetValue();
671 }
672 else if( wxOwnerDrawnComboBox* od_combo = dynamic_cast<wxOwnerDrawnComboBox*>( win ) )
673 {
674 dlgMap[ key ] = od_combo->GetSelection();
675 }
676 else if( wxTextEntry* textEntry = dynamic_cast<wxTextEntry*>( win ) )
677 {
678 dlgMap[ key ] = textEntry->GetValue();
679 }
680 else if( wxChoice* choice = dynamic_cast<wxChoice*>( win ) )
681 {
682 dlgMap[ key ] = choice->GetSelection();
683 }
684 else if( wxCheckBox* check = dynamic_cast<wxCheckBox*>( win ) )
685 {
686 dlgMap[ key ] = check->GetValue();
687 }
688 else if( wxSpinCtrl* spin = dynamic_cast<wxSpinCtrl*>( win ) )
689 {
690 dlgMap[ key ] = spin->GetValue();
691 }
692 else if( wxRadioButton* radio = dynamic_cast<wxRadioButton*>( win ) )
693 {
694 dlgMap[ key ] = radio->GetValue();
695 }
696 else if( wxRadioBox* radioBox = dynamic_cast<wxRadioBox*>( win ) )
697 {
698 dlgMap[ key ] = radioBox->GetSelection();
699 }
700 else if( wxSplitterWindow* splitter = dynamic_cast<wxSplitterWindow*>( win ) )
701 {
702 dlgMap[ key ] = splitter->GetSashPosition();
703 }
704 else if( wxScrolledWindow* scrolled = dynamic_cast<wxScrolledWindow*>( win ) )
705 {
706 dlgMap[ key ] = scrolled->GetScrollPos( wxVERTICAL );
707 }
708 else if( wxNotebook* notebook = dynamic_cast<wxNotebook*>( win ) )
709 {
710 int index = notebook->GetSelection();
711
712 if( index >= 0 && index < (int) notebook->GetPageCount() )
713 dlgMap[ key ] = notebook->GetPageText( notebook->GetSelection() );
714 }
715 else if( wxAuiNotebook* auiNotebook = dynamic_cast<wxAuiNotebook*>( win ) )
716 {
717 int index = auiNotebook->GetSelection();
718
719 if( index >= 0 && index < (int) auiNotebook->GetPageCount() )
720 dlgMap[ key ] = auiNotebook->GetPageText( auiNotebook->GetSelection() );
721 }
722 else if( WX_GRID* grid = dynamic_cast<WX_GRID*>( win ) )
723 {
724 dlgMap[ key ] = grid->GetShownColumnsAsString();
725 }
726 }
727
728 for( wxWindow* child : win->GetChildren() )
729 saveFn( child );
730 };
731
732 if( PROPERTY_HOLDER* props = PROPERTY_HOLDER::SafeCast( GetClientData() ) )
733 {
734 if( !props->GetPropertyOr( "persist", false ) )
735 return;
736 }
737
738 for( wxWindow* child : GetChildren() )
739 saveFn( child );
740}
741
742
744{
745 COMMON_SETTINGS* settings = Pgm().GetCommonSettings();
746
747 if( !settings )
748 return;
749
750 std::string dialogKey = m_hash_key.empty() ? getDialogKeyFromTitle( GetTitle() ) : m_hash_key;
751 auto dlgIt = settings->CsInternals().m_dialogControlValues.find( dialogKey );
752
753 if( dlgIt == settings->CsInternals().m_dialogControlValues.end() )
754 return;
755
756 const std::map<std::string, nlohmann::json>& dlgMap = dlgIt->second;
757
758 std::function<void( wxWindow* )> loadFn =
759 [&]( wxWindow* win )
760 {
761 if( PROPERTY_HOLDER* props = PROPERTY_HOLDER::SafeCast( win->GetClientData() ) )
762 {
763 if( !props->GetPropertyOr( "persist", false ) )
764 return;
765 }
766
767 std::string key = generateKey( win );
768
769 if( !key.empty() )
770 {
771 auto it = dlgMap.find( key );
772
773 if( it != dlgMap.end() )
774 {
775 const nlohmann::json& j = it->second;
776
777 if( m_unitBinders.contains( win ) )
778 {
779 if( j.is_number_integer() )
780 {
781 m_unitBinders[ win ]->ChangeValue( j.get<int>() );
782 }
783 else if( j.is_string() )
784 {
785 if( wxTextEntry* textEntry = dynamic_cast<wxTextEntry*>( win ) )
786 textEntry->ChangeValue( wxString::FromUTF8( j.get<std::string>().c_str() ) );
787 }
788 }
789 else if( wxComboBox* combo = dynamic_cast<wxComboBox*>( win ) )
790 {
791 if( j.is_string() )
792 combo->SetValue( wxString::FromUTF8( j.get<std::string>().c_str() ) );
793 }
794 else if( wxOwnerDrawnComboBox* od_combo = dynamic_cast<wxOwnerDrawnComboBox*>( win ) )
795 {
796 if( j.is_number_integer() )
797 {
798 int index = j.get<int>();
799
800 if( index >= 0 && index < (int) od_combo->GetCount() )
801 od_combo->SetSelection( index );
802 }
803 }
804 else if( wxTextEntry* textEntry = dynamic_cast<wxTextEntry*>( win ) )
805 {
806 if( j.is_string() )
807 textEntry->ChangeValue( wxString::FromUTF8( j.get<std::string>().c_str() ) );
808 }
809 else if( wxChoice* choice = dynamic_cast<wxChoice*>( win ) )
810 {
811 if( j.is_number_integer() )
812 {
813 int index = j.get<int>();
814
815 if( index >= 0 && index < (int) choice->GetCount() )
816 choice->SetSelection( index );
817 }
818 }
819 else if( wxCheckBox* check = dynamic_cast<wxCheckBox*>( win ) )
820 {
821 if( j.is_boolean() )
822 check->SetValue( j.get<bool>() );
823 }
824 else if( wxSpinCtrl* spin = dynamic_cast<wxSpinCtrl*>( win ) )
825 {
826 if( j.is_number_integer() )
827 spin->SetValue( j.get<int>() );
828 }
829 else if( wxRadioButton* radio = dynamic_cast<wxRadioButton*>( win ) )
830 {
831 if( j.is_boolean() )
832 {
833 // Only set active radio buttons. Let wxWidgets handle unsetting the inactive
834 // ones. This prevents all from being unset, which trips up wxWidgets in some
835 // cases.
836 if( j.get<bool>() )
837 radio->SetValue( true );
838 }
839 }
840 else if( wxRadioBox* radioBox = dynamic_cast<wxRadioBox*>( win ) )
841 {
842 if( j.is_number_integer() )
843 {
844 int index = j.get<int>();
845
846 if( index >= 0 && index < (int) radioBox->GetCount() )
847 radioBox->SetSelection( index );
848 }
849 }
850 else if( wxSplitterWindow* splitter = dynamic_cast<wxSplitterWindow*>( win ) )
851 {
852 if( j.is_number_integer() )
853 splitter->SetSashPosition( j.get<int>() );
854 }
855 else if( wxScrolledWindow* scrolled = dynamic_cast<wxScrolledWindow*>( win ) )
856 {
857 if( j.is_number_integer() )
858 scrolled->SetScrollPos( wxVERTICAL, j.get<int>() );
859 }
860 else if( wxNotebook* notebook = dynamic_cast<wxNotebook*>( win ) )
861 {
862 if( j.is_string() )
863 {
864 wxString pageTitle = wxString::FromUTF8( j.get<std::string>().c_str() );
865
866 for( int page = 0; page < (int) notebook->GetPageCount(); ++page )
867 {
868 if( notebook->GetPageText( page ) == pageTitle )
869 notebook->ChangeSelection( page );
870 }
871 }
872 }
873 else if( wxAuiNotebook* auiNotebook = dynamic_cast<wxAuiNotebook*>( win ) )
874 {
875 if( j.is_string() )
876 {
877 wxString pageTitle = wxString::FromUTF8( j.get<std::string>().c_str() );
878
879 for( int page = 0; page < (int) auiNotebook->GetPageCount(); ++page )
880 {
881 if( auiNotebook->GetPageText( page ) == pageTitle )
882 auiNotebook->ChangeSelection( page );
883 }
884 }
885 }
886 else if( WX_GRID* grid = dynamic_cast<WX_GRID*>( win ) )
887 {
888 if( j.is_string() )
889 grid->ShowHideColumns( wxString::FromUTF8( j.get<std::string>().c_str() ) );
890 }
891 }
892 }
893
894 for( wxWindow* child : win->GetChildren() )
895 loadFn( child );
896 };
897
898 if( PROPERTY_HOLDER* props = PROPERTY_HOLDER::SafeCast( GetClientData() ) )
899 {
900 if( !props->GetPropertyOr( "persist", false ) )
901 return;
902 }
903
904 for( wxWindow* child : GetChildren() )
905 loadFn( child );
906}
907
908
909void DIALOG_SHIM::OptOut( wxWindow* aWindow )
910{
911 PROPERTY_HOLDER* props = new PROPERTY_HOLDER();
912 props->SetProperty( "persist", false );
913 aWindow->SetClientData( props );
914}
915
916
918{
919 m_noControlUndoRedo.insert( aWindow );
920}
921
922
923void DIALOG_SHIM::RegisterUnitBinder( UNIT_BINDER* aUnitBinder, wxWindow* aWindow )
924{
925 m_unitBinders[ aWindow ] = aUnitBinder;
926}
927
928
930{
931 // Erase by binder identity rather than window key, so that a stale entry whose window has
932 // already been reused by a newer binder is left untouched.
933 std::erase_if( m_unitBinders,
934 [aUnitBinder]( const auto& aEntry )
935 {
936 return aEntry.second == aUnitBinder;
937 } );
938}
939
940
941// Recursive descent doing a SelectAll() in wxTextCtrls.
942// MacOS User Interface Guidelines state that when tabbing to a text control all its
943// text should be selected. Since wxWidgets fails to implement this, we do it here.
944void DIALOG_SHIM::SelectAllInTextCtrls( wxWindowList& children )
945{
946 for( wxWindow* child : children )
947 {
948 if( wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( child ) )
949 {
950 m_beforeEditValues[ textCtrl ] = textCtrl->GetValue();
951 textCtrl->Connect( wxEVT_SET_FOCUS, wxFocusEventHandler( DIALOG_SHIM::onChildSetFocus ),
952 nullptr, this );
953
954 // We don't currently run this on GTK because some window managers don't hide the
955 // selection in non-active controls, and other window managers do the selection
956 // automatically anyway.
957#if defined( __WXMAC__ ) || defined( __WXMSW__ )
958 if( !textCtrl->GetStringSelection().IsEmpty() )
959 {
960 // Respect an existing selection
961 }
962 else if( textCtrl->IsEditable() )
963 {
964 textCtrl->SelectAll();
965 }
966#else
967 ignore_unused( textCtrl );
968#endif
969 }
970 else if( wxStyledTextCtrl* scintilla = dynamic_cast<wxStyledTextCtrl*>( child ) )
971 {
972 m_beforeEditValues[ scintilla ] = scintilla->GetText();
973 scintilla->Connect( wxEVT_SET_FOCUS,
974 wxFocusEventHandler( DIALOG_SHIM::onChildSetFocus ),
975 nullptr, this );
976
977 if( !scintilla->GetSelectedText().IsEmpty() )
978 {
979 // Respect an existing selection
980 }
981 else if( scintilla->GetMarginWidth( 0 ) > 0 )
982 {
983 // Don't select-all in Custom Rules, etc.
984 }
985 else if( scintilla->IsEditable() )
986 {
987 scintilla->SelectAll();
988 }
989 }
990#ifdef __WXMAC__
991 // Temp hack for square (looking) buttons on OSX. Will likely be made redundant
992 // by the image store....
993 else if( dynamic_cast<wxBitmapButton*>( child ) != nullptr )
994 {
995 wxSize minSize( 29, 27 );
996 wxRect rect = child->GetRect();
997
998 child->ConvertDialogToPixels( minSize );
999
1000 rect.Inflate( std::max( 0, minSize.x - rect.GetWidth() ),
1001 std::max( 0, minSize.y - rect.GetHeight() ) );
1002
1003 child->SetMinSize( rect.GetSize() );
1004 child->SetSize( rect );
1005 }
1006#endif
1007 else
1008 {
1009 SelectAllInTextCtrls( child->GetChildren() );
1010 }
1011 }
1012}
1013
1014
1015void DIALOG_SHIM::registerUndoRedoHandlers( wxWindowList& children )
1016{
1017 for( wxWindow* child : children )
1018 {
1019 if( m_noControlUndoRedo.count( child ) )
1020 continue;
1021
1022 if( wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( child ) )
1023 {
1024 textCtrl->Bind( wxEVT_TEXT, &DIALOG_SHIM::onCommandEvent, this );
1025 m_currentValues[ textCtrl ] = textCtrl->GetValue();
1026 }
1027 else if( wxStyledTextCtrl* scintilla = dynamic_cast<wxStyledTextCtrl*>( child ) )
1028 {
1029 scintilla->Bind( wxEVT_STC_CHANGE, &DIALOG_SHIM::onStyledTextChanged, this );
1030 m_currentValues[ scintilla ] = scintilla->GetText();
1031 }
1032 else if( wxComboBox* combo = dynamic_cast<wxComboBox*>( child ) )
1033 {
1034 combo->Bind( wxEVT_TEXT, &DIALOG_SHIM::onCommandEvent, this );
1035 combo->Bind( wxEVT_COMBOBOX, &DIALOG_SHIM::onCommandEvent, this );
1036 m_currentValues[ combo ] = combo->GetValue();
1037 }
1038 else if( wxChoice* choice = dynamic_cast<wxChoice*>( child ) )
1039 {
1040 choice->Bind( wxEVT_CHOICE, &DIALOG_SHIM::onCommandEvent, this );
1041 m_currentValues[ choice ] = static_cast<long>( choice->GetSelection() );
1042 }
1043 else if( wxCheckBox* check = dynamic_cast<wxCheckBox*>( child ) )
1044 {
1045 check->Bind( wxEVT_CHECKBOX, &DIALOG_SHIM::onCommandEvent, this );
1046 m_currentValues[ check ] = check->GetValue();
1047 }
1048 else if( wxSpinCtrl* spin = dynamic_cast<wxSpinCtrl*>( child ) )
1049 {
1050 spin->Bind( wxEVT_SPINCTRL, &DIALOG_SHIM::onSpinEvent, this );
1051 spin->Bind( wxEVT_TEXT, &DIALOG_SHIM::onCommandEvent, this );
1052 m_currentValues[ spin ] = static_cast<long>( spin->GetValue() );
1053 }
1054 else if( wxSpinCtrlDouble* spinD = dynamic_cast<wxSpinCtrlDouble*>( child ) )
1055 {
1056 spinD->Bind( wxEVT_SPINCTRLDOUBLE, &DIALOG_SHIM::onSpinDoubleEvent, this );
1057 spinD->Bind( wxEVT_TEXT, &DIALOG_SHIM::onCommandEvent, this );
1058 m_currentValues[ spinD ] = spinD->GetValue();
1059 }
1060 else if( wxRadioButton* radio = dynamic_cast<wxRadioButton*>( child ) )
1061 {
1062 radio->Bind( wxEVT_RADIOBUTTON, &DIALOG_SHIM::onCommandEvent, this );
1063 m_currentValues[ radio ] = radio->GetValue();
1064 }
1065 else if( wxRadioBox* radioBox = dynamic_cast<wxRadioBox*>( child ) )
1066 {
1067 radioBox->Bind( wxEVT_RADIOBOX, &DIALOG_SHIM::onCommandEvent, this );
1068 m_currentValues[ radioBox ] = static_cast<long>( radioBox->GetSelection() );
1069 }
1070 else if( wxGrid* grid = dynamic_cast<wxGrid*>( child ) )
1071 {
1072 grid->Bind( wxEVT_GRID_CELL_CHANGED, &DIALOG_SHIM::onGridCellChanged, this );
1074 }
1075 else if( wxPropertyGrid* propGrid = dynamic_cast<wxPropertyGrid*>( child ) )
1076 {
1077 propGrid->Bind( wxEVT_PG_CHANGED, &DIALOG_SHIM::onPropertyGridChanged, this );
1078 m_currentValues[ propGrid ] = getControlValue( propGrid );
1079 }
1080 else if( wxCheckListBox* checkList = dynamic_cast<wxCheckListBox*>( child ) )
1081 {
1082 checkList->Bind( wxEVT_CHECKLISTBOX, &DIALOG_SHIM::onCommandEvent, this );
1083 m_currentValues[ checkList ] = getControlValue( checkList );
1084 }
1085 else if( wxDataViewListCtrl* dataList = dynamic_cast<wxDataViewListCtrl*>( child ) )
1086 {
1087 dataList->Bind( wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, &DIALOG_SHIM::onDataViewListChanged, this );
1088 m_currentValues[ dataList ] = getControlValue( dataList );
1089 }
1090 else
1091 {
1092 registerUndoRedoHandlers( child->GetChildren() );
1093 }
1094 }
1095}
1096
1097
1099{
1100 wxVariant before = m_currentValues[ aCtrl ];
1101 wxVariant after = getControlValue( aCtrl );
1102
1103 if( before != after )
1104 {
1105 m_undoStack.push_back( { aCtrl, before, after } );
1106 m_redoStack.clear();
1107 m_currentValues[ aCtrl ] = after;
1108 }
1109}
1110
1111
1112void DIALOG_SHIM::onCommandEvent( wxCommandEvent& aEvent )
1113{
1114 if( !m_handlingUndoRedo )
1115 recordControlChange( static_cast<wxWindow*>( aEvent.GetEventObject() ) );
1116
1117 aEvent.Skip();
1118}
1119
1120
1121void DIALOG_SHIM::onSpinEvent( wxSpinEvent& aEvent )
1122{
1123 if( !m_handlingUndoRedo )
1124 recordControlChange( static_cast<wxWindow*>( aEvent.GetEventObject() ) );
1125
1126 aEvent.Skip();
1127}
1128
1129
1130void DIALOG_SHIM::onSpinDoubleEvent( wxSpinDoubleEvent& aEvent )
1131{
1132 if( !m_handlingUndoRedo )
1133 recordControlChange( static_cast<wxWindow*>( aEvent.GetEventObject() ) );
1134
1135 aEvent.Skip();
1136}
1137
1138
1139void DIALOG_SHIM::onStyledTextChanged( wxStyledTextEvent& aEvent )
1140{
1141 if( !m_handlingUndoRedo )
1142 recordControlChange( static_cast<wxWindow*>( aEvent.GetEventObject() ) );
1143
1144 aEvent.Skip();
1145}
1146
1147
1148void DIALOG_SHIM::onGridCellChanged( wxGridEvent& aEvent )
1149{
1150 if( !m_handlingUndoRedo )
1151 recordControlChange( static_cast<wxWindow*>( aEvent.GetEventObject() ) );
1152
1153 aEvent.Skip();
1154}
1155
1156void DIALOG_SHIM::onPropertyGridChanged( wxPropertyGridEvent& aEvent )
1157{
1158 if( !m_handlingUndoRedo )
1159 recordControlChange( static_cast<wxWindow*>( aEvent.GetEventObject() ) );
1160
1161 aEvent.Skip();
1162}
1163
1164void DIALOG_SHIM::onDataViewListChanged( wxDataViewEvent& aEvent )
1165{
1166 if( !m_handlingUndoRedo )
1167 recordControlChange( static_cast<wxWindow*>( aEvent.GetEventObject() ) );
1168
1169 aEvent.Skip();
1170}
1171
1172wxVariant DIALOG_SHIM::getControlValue( wxWindow* aCtrl )
1173{
1174 if( wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( aCtrl ) )
1175 return wxVariant( textCtrl->GetValue() );
1176 else if( wxStyledTextCtrl* scintilla = dynamic_cast<wxStyledTextCtrl*>( aCtrl ) )
1177 return wxVariant( scintilla->GetText() );
1178 else if( wxComboBox* combo = dynamic_cast<wxComboBox*>( aCtrl ) )
1179 return wxVariant( combo->GetValue() );
1180 else if( wxChoice* choice = dynamic_cast<wxChoice*>( aCtrl ) )
1181 return wxVariant( (long) choice->GetSelection() );
1182 else if( wxCheckBox* check = dynamic_cast<wxCheckBox*>( aCtrl ) )
1183 return wxVariant( check->GetValue() );
1184 else if( wxSpinCtrl* spin = dynamic_cast<wxSpinCtrl*>( aCtrl ) )
1185 return wxVariant( (long) spin->GetValue() );
1186 else if( wxSpinCtrlDouble* spinD = dynamic_cast<wxSpinCtrlDouble*>( aCtrl ) )
1187 return wxVariant( spinD->GetValue() );
1188 else if( wxRadioButton* radio = dynamic_cast<wxRadioButton*>( aCtrl ) )
1189 return wxVariant( radio->GetValue() );
1190 else if( wxRadioBox* radioBox = dynamic_cast<wxRadioBox*>( aCtrl ) )
1191 return wxVariant( (long) radioBox->GetSelection() );
1192 else if( wxGrid* grid = dynamic_cast<wxGrid*>( aCtrl ) )
1193 {
1194 // Tables with regroupable/sortable rows serialize by identity instead of row position.
1195 if( auto* table = dynamic_cast<WX_GRID_TABLE_BASE*>( grid->GetTable() );
1196 table && table->HasUndoStateSerialization() )
1197 {
1198 return wxVariant( table->SerializeUndoState() );
1199 }
1200
1201 nlohmann::json j = nlohmann::json::array();
1202 int rows = grid->GetNumberRows();
1203 int cols = grid->GetNumberCols();
1204
1205 for( int r = 0; r < rows; ++r )
1206 {
1207 nlohmann::json row = nlohmann::json::array();
1208
1209 for( int c = 0; c < cols; ++c )
1210 row.push_back( std::string( grid->GetCellValue( r, c ).ToUTF8() ) );
1211
1212 j.push_back( row );
1213 }
1214
1215 return wxVariant( wxString( j.dump() ) );
1216 }
1217 else if( wxPropertyGrid* propGrid = dynamic_cast<wxPropertyGrid*>( aCtrl ) )
1218 {
1219 nlohmann::json j;
1220
1221 for( wxPropertyGridIterator it = propGrid->GetIterator(); !it.AtEnd(); ++it )
1222 {
1223 wxPGProperty* prop = *it;
1224 j[ prop->GetName().ToStdString() ] = prop->GetValueAsString().ToStdString();
1225 }
1226
1227 return wxVariant( wxString( j.dump() ) );
1228 }
1229 else if( wxCheckListBox* checkList = dynamic_cast<wxCheckListBox*>( aCtrl ) )
1230 {
1231 nlohmann::json j = nlohmann::json::array();
1232 unsigned int count = checkList->GetCount();
1233
1234 for( unsigned int i = 0; i < count; ++i )
1235 {
1236 if( checkList->IsChecked( i ) )
1237 j.push_back( i );
1238 }
1239
1240 return wxVariant( wxString( j.dump() ) );
1241 }
1242 else if( wxDataViewListCtrl* dataList = dynamic_cast<wxDataViewListCtrl*>( aCtrl ) )
1243 {
1244 nlohmann::json j = nlohmann::json::array();
1245 unsigned int rows = dataList->GetItemCount();
1246 unsigned int cols = dataList->GetColumnCount();
1247
1248 for( unsigned int r = 0; r < rows; ++r )
1249 {
1250 nlohmann::json row = nlohmann::json::array();
1251
1252 for( unsigned int c = 0; c < cols; ++c )
1253 {
1254 wxVariant val;
1255 dataList->GetValue( val, r, c );
1256 row.push_back( std::string( val.GetString().ToUTF8() ) );
1257 }
1258
1259 j.push_back( row );
1260 }
1261
1262 return wxVariant( wxString( j.dump() ) );
1263 }
1264 else
1265 return wxVariant();
1266}
1267
1268
1269void DIALOG_SHIM::setControlValue( wxWindow* aCtrl, const wxVariant& aValue )
1270{
1271 if( wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( aCtrl ) )
1272 textCtrl->SetValue( aValue.GetString() );
1273 else if( wxStyledTextCtrl* scintilla = dynamic_cast<wxStyledTextCtrl*>( aCtrl ) )
1274 scintilla->SetText( aValue.GetString() );
1275 else if( wxComboBox* combo = dynamic_cast<wxComboBox*>( aCtrl ) )
1276 combo->SetValue( aValue.GetString() );
1277 else if( wxChoice* choice = dynamic_cast<wxChoice*>( aCtrl ) )
1278 choice->SetSelection( (int) aValue.GetLong() );
1279 else if( wxCheckBox* check = dynamic_cast<wxCheckBox*>( aCtrl ) )
1280 check->SetValue( aValue.GetBool() );
1281 else if( wxSpinCtrl* spin = dynamic_cast<wxSpinCtrl*>( aCtrl ) )
1282 spin->SetValue( (int) aValue.GetLong() );
1283 else if( wxSpinCtrlDouble* spinD = dynamic_cast<wxSpinCtrlDouble*>( aCtrl ) )
1284 spinD->SetValue( aValue.GetDouble() );
1285 else if( wxRadioButton* radio = dynamic_cast<wxRadioButton*>( aCtrl ) )
1286 radio->SetValue( aValue.GetBool() );
1287 else if( wxRadioBox* radioBox = dynamic_cast<wxRadioBox*>( aCtrl ) )
1288 radioBox->SetSelection( (int) aValue.GetLong() );
1289 else if( wxGrid* grid = dynamic_cast<wxGrid*>( aCtrl ) )
1290 {
1291 if( auto* table = dynamic_cast<WX_GRID_TABLE_BASE*>( grid->GetTable() );
1292 table && table->HasUndoStateSerialization() )
1293 {
1294 table->RestoreUndoState( aValue.GetString() );
1295 return;
1296 }
1297
1298 nlohmann::json j = nlohmann::json::parse( aValue.GetString().ToStdString(), nullptr, false );
1299
1300 if( j.is_array() )
1301 {
1302 int rows = std::min( (int) j.size(), grid->GetNumberRows() );
1303
1304 for( int r = 0; r < rows; ++r )
1305 {
1306 nlohmann::json row = j[r];
1307 int cols = std::min( (int) row.size(), grid->GetNumberCols() );
1308
1309 for( int c = 0; c < cols; ++c )
1310 grid->SetCellValue( r, c, wxString( row[c].get<std::string>() ) );
1311 }
1312 }
1313 }
1314 else if( wxPropertyGrid* propGrid = dynamic_cast<wxPropertyGrid*>( aCtrl ) )
1315 {
1316 nlohmann::json j = nlohmann::json::parse( aValue.GetString().ToStdString(), nullptr, false );
1317
1318 if( j.is_object() )
1319 {
1320 for( auto it = j.begin(); it != j.end(); ++it )
1321 propGrid->SetPropertyValue( wxString( it.key() ), wxString( it.value().get<std::string>() ) );
1322 }
1323 }
1324 else if( wxCheckListBox* checkList = dynamic_cast<wxCheckListBox*>( aCtrl ) )
1325 {
1326 nlohmann::json j = nlohmann::json::parse( aValue.GetString().ToStdString(), nullptr, false );
1327
1328 if( j.is_array() )
1329 {
1330 unsigned int count = checkList->GetCount();
1331
1332 for( unsigned int i = 0; i < count; ++i )
1333 checkList->Check( i, false );
1334
1335 for( auto& idx : j )
1336 {
1337 unsigned int i = idx.get<unsigned int>();
1338
1339 if( i < count )
1340 checkList->Check( i, true );
1341 }
1342 }
1343 }
1344 else if( wxDataViewListCtrl* dataList = dynamic_cast<wxDataViewListCtrl*>( aCtrl ) )
1345 {
1346 nlohmann::json j = nlohmann::json::parse( aValue.GetString().ToStdString(), nullptr, false );
1347
1348 if( j.is_array() )
1349 {
1350 unsigned int rows = std::min( static_cast<unsigned int>( j.size() ),
1351 static_cast<unsigned int>( dataList->GetItemCount() ) );
1352
1353 for( unsigned int r = 0; r < rows; ++r )
1354 {
1355 nlohmann::json row = j[r];
1356 unsigned int cols = std::min( (unsigned int) row.size(), dataList->GetColumnCount() );
1357
1358 for( unsigned int c = 0; c < cols; ++c )
1359 {
1360 wxVariant val( wxString( row[c].get<std::string>() ) );
1361 dataList->SetValue( val, r, c );
1362 }
1363 }
1364 }
1365 }
1366}
1367
1368
1370{
1371 if( m_undoStack.empty() )
1372 return;
1373
1374 m_handlingUndoRedo = true;
1375 UNDO_STEP step = m_undoStack.back();
1376 m_undoStack.pop_back();
1377 setControlValue( step.ctrl, step.before );
1378 m_currentValues[ step.ctrl ] = step.before;
1379 m_redoStack.push_back( step );
1380 m_handlingUndoRedo = false;
1381}
1382
1383
1385{
1386 if( m_redoStack.empty() )
1387 return;
1388
1389 m_handlingUndoRedo = true;
1390 UNDO_STEP step = m_redoStack.back();
1391 m_redoStack.pop_back();
1392 setControlValue( step.ctrl, step.after );
1393 m_currentValues[ step.ctrl ] = step.after;
1394 m_undoStack.push_back( step );
1395 m_handlingUndoRedo = false;
1396}
1397
1398
1399void DIALOG_SHIM::OnPaint( wxPaintEvent &event )
1400{
1401 if( m_firstPaintEvent )
1402 {
1404
1405 SelectAllInTextCtrls( GetChildren() );
1406 registerUndoRedoHandlers( GetChildren() );
1407
1410 else
1411 KIPLATFORM::UI::ForceFocus( this ); // Focus the dialog itself
1412
1413 m_firstPaintEvent = false;
1414 }
1415
1416 event.Skip();
1417}
1418
1419
1421{
1422 if( !GetTitle().StartsWith( wxS( "*" ) ) )
1423 SetTitle( wxS( "*" ) + GetTitle() );
1424}
1425
1426
1428{
1429 if( GetTitle().StartsWith( wxS( "*" ) ) )
1430 SetTitle( GetTitle().AfterFirst( '*' ) );
1431}
1432
1434{
1436
1437 // Apple in its infinite wisdom will raise a disabled window before even passing
1438 // us the event, so we have no way to stop it. Instead, we must set an order on
1439 // the windows so that the modal will be pushed in front of the disabled
1440 // window when it is raised.
1442
1443 // Call the base class ShowModal() method
1444 return wxDialog::ShowModal();
1445}
1446
1447/*
1448 QuasiModal Mode Explained:
1449
1450 The gtk calls in wxDialog::ShowModal() cause event routing problems if that
1451 modal dialog then tries to use KIWAY_PLAYER::ShowModal(). The latter shows up
1452 and mostly works but does not respond to the window decoration close button.
1453 There is no way to get around this without reversing the gtk calls temporarily.
1454
1455 There are also issues with the Scintilla text editor putting up autocomplete
1456 popups, which appear behind the dialog window if QuasiModal is not used.
1457
1458 QuasiModal mode is our own almost modal mode which disables only the parent
1459 of the DIALOG_SHIM, leaving other frames operable and while staying captured in the
1460 nested event loop. This avoids the gtk calls and leaves event routing pure
1461 and sufficient to operate the KIWAY_PLAYER::ShowModal() properly. When using
1462 ShowQuasiModal() you have to use EndQuasiModal() in your dialogs and not
1463 EndModal(). There is also IsQuasiModal() but its value can only be true
1464 when the nested event loop is active. Do not mix the modal and quasi-modal
1465 functions. Use one set or the other.
1466
1467 You might find this behavior preferable over a pure modal mode, and it was said
1468 that only the Mac has this natively, but now other platforms have something
1469 similar. You CAN use it anywhere for any dialog. But you MUST use it when
1470 you want to use KIWAY_PLAYER::ShowModal() from a dialog event.
1471*/
1472
1474{
1475 NULLER raii_nuller( (void*&) m_qmodal_loop );
1476
1477 // release the mouse if it's currently captured as the window having it
1478 // will be disabled when this dialog is shown -- but will still keep the
1479 // capture making it impossible to do anything in the modal dialog itself
1480 if( wxWindow* win = wxWindow::GetCapture() )
1481 win->ReleaseMouse();
1482
1483 // Get the optimal parent
1484 wxWindow* parent = GetParentForModalDialog( GetParent(), GetWindowStyle() );
1485
1486 wxASSERT_MSG( !m_qmodal_parent_disabler, wxT( "Caller using ShowQuasiModal() twice on same window?" ) );
1487
1488 // quasi-modal: disable only my "optimal" parent
1490
1491 // Apple in its infinite wisdom will raise a disabled window before even passing
1492 // us the event, so we have no way to stop it. Instead, we must set an order on
1493 // the windows so that the quasi-modal will be pushed in front of the disabled
1494 // window when it is raised.
1496
1497 Show( true );
1498
1499 m_qmodal_showing = true;
1500
1501 wxGUIEventLoop event_loop;
1502
1503 m_qmodal_loop = &event_loop;
1504
1505 event_loop.Run();
1506
1507 m_qmodal_showing = false;
1508 focusParentCanvas( true );
1509
1510 return GetReturnCode();
1511}
1512
1513
1515{
1517 m_qmodal_parent_disabler->SuspendForTrueModal();
1518}
1519
1520
1522{
1524 m_qmodal_parent_disabler->ResumeAfterTrueModal();
1525}
1526
1527
1529{
1530 // Hook up validator and transfer data from controls handling so quasi-modal dialogs
1531 // handle validation in the same way as other dialogs.
1532 if( ( retCode == wxID_OK ) && ( !Validate() || !TransferDataFromWindow() ) )
1533 return;
1534
1535 SetReturnCode( retCode );
1536
1537 if( !IsQuasiModal() )
1538 {
1539 wxFAIL_MSG( wxT( "Either DIALOG_SHIM::EndQuasiModal was called twice, or ShowQuasiModal wasn't called" ) );
1540 return;
1541 }
1542
1544
1545 if( m_qmodal_loop )
1546 {
1547 if( m_qmodal_loop->IsRunning() )
1548 m_qmodal_loop->Exit( 0 );
1549 else
1550 m_qmodal_loop->ScheduleExit( 0 );
1551 }
1552
1554 m_qmodal_parent_disabler = nullptr;
1555
1556 Show( false );
1557}
1558
1559
1560void DIALOG_SHIM::resetUndoRedoForNewContent( wxWindowList& aChildren )
1561{
1562 m_undoStack.clear();
1563 m_redoStack.clear();
1564 m_currentValues.clear();
1565 registerUndoRedoHandlers( aChildren );
1566}
1567
1568
1569void DIALOG_SHIM::unregisterUnitBinders( wxWindow* aWindow )
1570{
1571 m_unitBinders.erase( aWindow );
1572
1573 for( wxWindow* child : aWindow->GetChildren() )
1574 unregisterUnitBinders( child );
1575}
1576
1577
1578void DIALOG_SHIM::OnCloseWindow( wxCloseEvent& aEvent )
1579{
1580 wxString msg = wxString::Format( "Closing dialog %s", GetTitle() );
1581 APP_MONITOR::AddNavigationBreadcrumb( msg, "dialog.close" );
1582
1584
1585 if( IsQuasiModal() )
1586 {
1587 EndQuasiModal( wxID_CANCEL );
1588 return;
1589 }
1590
1591 // This is mandatory to allow wxDialogBase::OnCloseWindow() to be called.
1592 aEvent.Skip();
1593}
1594
1595
1596void DIALOG_SHIM::OnButton( wxCommandEvent& aEvent )
1597{
1598 const int id = aEvent.GetId();
1599
1600 if( IsQuasiModal() )
1601 {
1602 if( id == GetAffirmativeId() )
1603 {
1604 EndQuasiModal( id );
1605 }
1606 else if( id == wxID_APPLY )
1607 {
1608 // Dialogs that provide Apply buttons should make sure data is valid before
1609 // allowing a transfer, as there is no other way to indicate failure
1610 // (i.e. the dialog can't refuse to close as it might with OK, because it
1611 // isn't closing anyway)
1612 if( Validate() )
1613 ignore_unused( TransferDataFromWindow() );
1614 }
1615 else if( id == wxID_CANCEL )
1616 {
1617 EndQuasiModal( wxID_CANCEL );
1618 }
1619 else // not a standard button
1620 {
1621 aEvent.Skip();
1622 }
1623
1624 return;
1625 }
1626
1627 // This is mandatory to allow wxDialogBase::OnButton() to be called.
1628 aEvent.Skip();
1629}
1630
1631
1632void DIALOG_SHIM::onChildSetFocus( wxFocusEvent& aEvent )
1633{
1634 // When setting focus to a text control reset the before-edit value.
1635
1636 if( !m_isClosing )
1637 {
1638 if( wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( aEvent.GetEventObject() ) )
1639 m_beforeEditValues[ textCtrl ] = textCtrl->GetValue();
1640 else if( wxStyledTextCtrl* scintilla = dynamic_cast<wxStyledTextCtrl*>( aEvent.GetEventObject() ) )
1641 m_beforeEditValues[ scintilla ] = scintilla->GetText();
1642 }
1643
1644 aEvent.Skip();
1645}
1646
1647
1648void DIALOG_SHIM::OnCharHook( wxKeyEvent& aEvt )
1649{
1650 int key = aEvt.GetKeyCode();
1651 int mods = 0;
1652
1653 if( aEvt.ControlDown() )
1654 mods |= MD_CTRL;
1655 if( aEvt.ShiftDown() )
1656 mods |= MD_SHIFT;
1657 if( aEvt.AltDown() )
1658 mods |= MD_ALT;
1659
1660 int hotkey = key | mods;
1661
1662 // Check for standard undo/redo hotkeys
1663 if( hotkey == (MD_CTRL + 'Z') )
1664 {
1665 doUndo();
1666 return;
1667 }
1668 else if( hotkey == (MD_CTRL + MD_SHIFT + 'Z') || hotkey == (MD_CTRL + 'Y') )
1669 {
1670 doRedo();
1671 return;
1672 }
1673
1674 if( aEvt.GetKeyCode() == 'U' && aEvt.GetModifiers() == wxMOD_CONTROL )
1675 {
1676 if( m_parentFrame )
1677 {
1678 m_parentFrame->ToggleUserUnits();
1679 return;
1680 }
1681 }
1682 // shift-return (Mac default) or Ctrl-Return (GTK) for new line input
1683 else if( ( aEvt.GetKeyCode() == WXK_RETURN || aEvt.GetKeyCode() == WXK_NUMPAD_ENTER ) && aEvt.ShiftDown() )
1684 {
1685 wxObject* eventSource = aEvt.GetEventObject();
1686
1687 if( wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( eventSource ) )
1688 {
1689 // If the text control is not multi-line, we want to close the dialog
1690 if( !textCtrl->IsMultiLine() )
1691 {
1692 wxPostEvent( this, wxCommandEvent( wxEVT_COMMAND_BUTTON_CLICKED, wxID_OK ) );
1693 return;
1694 }
1695
1696#if defined( __WXMAC__ ) || defined( __WXMSW__ )
1697 wxString eol = "\r\n";
1698#else
1699 wxString eol = "\n";
1700#endif
1701
1702 long pos = textCtrl->GetInsertionPoint();
1703 textCtrl->WriteText( eol );
1704 textCtrl->SetInsertionPoint( pos + eol.length() );
1705 return;
1706 }
1707 else if( wxStyledTextCtrl* scintilla = dynamic_cast<wxStyledTextCtrl*>( eventSource ) )
1708 {
1709 wxString eol = "\n";
1710
1711 switch( scintilla->GetEOLMode() )
1712 {
1713 case wxSTC_EOL_CRLF: eol = "\r\n"; break;
1714 case wxSTC_EOL_CR: eol = "\r"; break;
1715 case wxSTC_EOL_LF: eol = "\n"; break;
1716 }
1717
1718 long pos = scintilla->GetCurrentPos();
1719 scintilla->InsertText( pos, eol );
1720 scintilla->GotoPos( pos + eol.length() );
1721 return;
1722 }
1723 return;
1724 }
1725 // command-return (Mac default) or Ctrl-Return (GTK) for OK
1726 else if( ( aEvt.GetKeyCode() == WXK_RETURN || aEvt.GetKeyCode() == WXK_NUMPAD_ENTER ) && aEvt.ControlDown() )
1727 {
1728 wxPostEvent( this, wxCommandEvent( wxEVT_COMMAND_BUTTON_CLICKED, wxID_OK ) );
1729 return;
1730 }
1731 else if( aEvt.GetKeyCode() == WXK_TAB && !aEvt.ControlDown() )
1732 {
1733 wxWindow* currentWindow = wxWindow::FindFocus();
1734 int currentIdx = -1;
1735 int delta = aEvt.ShiftDown() ? -1 : 1;
1736
1737 auto advance =
1738 [&]( int& idx )
1739 {
1740 // Wrap-around modulus
1741 int size = (int) m_tabOrder.size();
1742 idx = ( ( idx + delta ) % size + size ) % size;
1743 };
1744
1745 for( size_t i = 0; i < m_tabOrder.size(); ++i )
1746 {
1747 // Check for exact match or if currentWindow is a child of the control
1748 // (e.g., the text entry inside a wxComboBox)
1749 if( m_tabOrder[i] == currentWindow
1750 || ( currentWindow && m_tabOrder[i]->IsDescendant( currentWindow ) ) )
1751 {
1752 currentIdx = (int) i;
1753 break;
1754 }
1755 }
1756
1757 if( currentIdx >= 0 )
1758 {
1759 advance( currentIdx );
1760
1761 // Skip hidden or disabled controls
1762 int startIdx = currentIdx;
1763
1764 while( !m_tabOrder[currentIdx]->IsShown() || !m_tabOrder[currentIdx]->IsEnabled() )
1765 {
1766 advance( currentIdx );
1767
1768 if( currentIdx == startIdx )
1769 break; // Avoid infinite loop if all controls are hidden
1770 }
1771
1772 //todo: We don't currently have non-textentry dialog boxes but this will break if
1773 // we add them.
1774#ifdef __APPLE__
1775 while( dynamic_cast<wxTextEntry*>( m_tabOrder[ currentIdx ] ) == nullptr )
1776 advance( currentIdx );
1777#endif
1778
1779 m_tabOrder[ currentIdx ]->SetFocus();
1780 return;
1781 }
1782 }
1783 else if( aEvt.GetKeyCode() == WXK_ESCAPE )
1784 {
1785 wxObject* eventSource = aEvt.GetEventObject();
1786
1787 if( wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( eventSource ) )
1788 {
1789 // First escape after an edit cancels edit
1790 if( textCtrl->GetValue() != m_beforeEditValues[ textCtrl ] )
1791 {
1792 textCtrl->SetValue( m_beforeEditValues[ textCtrl ] );
1793 textCtrl->SelectAll();
1794 return;
1795 }
1796 }
1797 else if( wxStyledTextCtrl* scintilla = dynamic_cast<wxStyledTextCtrl*>( eventSource ) )
1798 {
1799 // First escape after an edit cancels edit
1800 if( scintilla->GetText() != m_beforeEditValues[ scintilla ] )
1801 {
1802 scintilla->SetText( m_beforeEditValues[ scintilla ] );
1803 scintilla->SelectAll();
1804 return;
1805 }
1806 }
1807 }
1808
1809 aEvt.Skip();
1810}
1811
1812
1813static void recursiveDescent( wxSizer* aSizer, std::map<int, wxString>& aLabels )
1814{
1815 wxStdDialogButtonSizer* sdbSizer = dynamic_cast<wxStdDialogButtonSizer*>( aSizer );
1816
1817 auto setupButton =
1818 [&]( wxButton* aButton )
1819 {
1820 if( aLabels.count( aButton->GetId() ) > 0 )
1821 {
1822 aButton->SetLabel( aLabels[ aButton->GetId() ] );
1823 }
1824 else
1825 {
1826 // wxWidgets has an uneven track record when the language is changed on
1827 // the fly so we set them even when they don't appear in the label map
1828 switch( aButton->GetId() )
1829 {
1830 case wxID_OK: aButton->SetLabel( _( "&OK" ) ); break;
1831 case wxID_CANCEL: aButton->SetLabel( _( "&Cancel" ) ); break;
1832 case wxID_YES: aButton->SetLabel( _( "&Yes" ) ); break;
1833 case wxID_NO: aButton->SetLabel( _( "&No" ) ); break;
1834 case wxID_APPLY: aButton->SetLabel( _( "&Apply" ) ); break;
1835 case wxID_SAVE: aButton->SetLabel( _( "&Save" ) ); break;
1836 case wxID_HELP: aButton->SetLabel( _( "&Help" ) ); break;
1837 case wxID_CONTEXT_HELP: aButton->SetLabel( _( "&Help" ) ); break;
1838 }
1839 }
1840 };
1841
1842 if( sdbSizer )
1843 {
1844 if( sdbSizer->GetAffirmativeButton() )
1845 setupButton( sdbSizer->GetAffirmativeButton() );
1846
1847 if( sdbSizer->GetApplyButton() )
1848 setupButton( sdbSizer->GetApplyButton() );
1849
1850 if( sdbSizer->GetNegativeButton() )
1851 setupButton( sdbSizer->GetNegativeButton() );
1852
1853 if( sdbSizer->GetCancelButton() )
1854 setupButton( sdbSizer->GetCancelButton() );
1855
1856 if( sdbSizer->GetHelpButton() )
1857 setupButton( sdbSizer->GetHelpButton() );
1858
1859 sdbSizer->Layout();
1860
1861 if( sdbSizer->GetAffirmativeButton() )
1862 sdbSizer->GetAffirmativeButton()->SetDefault();
1863 }
1864
1865 for( wxSizerItem* item : aSizer->GetChildren() )
1866 {
1867 if( item->GetSizer() )
1868 recursiveDescent( item->GetSizer(), aLabels );
1869 }
1870}
1871
1872
1873void DIALOG_SHIM::SetupStandardButtons( std::map<int, wxString> aLabels )
1874{
1875 recursiveDescent( GetSizer(), aLabels );
1876}
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 clampToWorkArea()
Shrink the dialog's minimum and current size down to the work area of the display it occupies,...
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:553
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