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