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