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