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/bmpbuttn.h>
45#include <wx/textctrl.h>
46#include <wx/stc/stc.h>
47#include <wx/combobox.h>
48#include <wx/odcombo.h>
49#include <wx/choice.h>
50#include <wx/checkbox.h>
51#include <wx/spinctrl.h>
52#include <wx/splitter.h>
53#include <wx/radiobox.h>
54#include <wx/radiobut.h>
55#include <wx/variant.h>
56
57#include <algorithm>
58#include <functional>
59#include <nlohmann/json.hpp>
60#include <typeinfo>
61
62BEGIN_EVENT_TABLE( DIALOG_SHIM, wxDialog )
63 EVT_CHAR_HOOK( DIALOG_SHIM::OnCharHook )
64END_EVENT_TABLE()
65
66
67DIALOG_SHIM::DIALOG_SHIM( wxWindow* aParent, wxWindowID id, const wxString& title,
68 const wxPoint& pos, const wxSize& size, long style,
69 const wxString& name ) :
70 wxDialog( aParent, id, title, pos, size, style, name ),
71 KIWAY_HOLDER( nullptr, KIWAY_HOLDER::DIALOG ),
72 m_units( EDA_UNITS::MM ),
73 m_useCalculatedSize( false ),
74 m_firstPaintEvent( true ),
75 m_initialFocusTarget( nullptr ),
76 m_isClosing( false ),
77 m_qmodal_loop( nullptr ),
78 m_qmodal_showing( false ),
79 m_qmodal_parent_disabler( nullptr ),
80 m_parentFrame( nullptr ),
81 m_userPositioned( false ),
82 m_userResized( false )
83{
84 KIWAY_HOLDER* kiwayHolder = nullptr;
85 m_initialSize = size;
86
87 if( aParent )
88 {
89 kiwayHolder = dynamic_cast<KIWAY_HOLDER*>( aParent );
90
91 while( !kiwayHolder && aParent->GetParent() )
92 {
93 aParent = aParent->GetParent();
94 kiwayHolder = dynamic_cast<KIWAY_HOLDER*>( aParent );
95 }
96 }
97
98 // Inherit units from parent
99 if( kiwayHolder && kiwayHolder->GetType() == KIWAY_HOLDER::FRAME )
100 m_units = static_cast<EDA_BASE_FRAME*>( kiwayHolder )->GetUserUnits();
101 else if( kiwayHolder && kiwayHolder->GetType() == KIWAY_HOLDER::DIALOG )
102 m_units = static_cast<DIALOG_SHIM*>( kiwayHolder )->GetUserUnits();
103
104 // Don't mouse-warp after a dialog run from the context menu
105 if( kiwayHolder && kiwayHolder->GetType() == KIWAY_HOLDER::FRAME )
106 {
107 m_parentFrame = static_cast<EDA_BASE_FRAME*>( kiwayHolder );
108 TOOL_MANAGER* toolMgr = m_parentFrame->GetToolManager();
109
110 if( toolMgr && toolMgr->IsContextMenuActive() )
111 toolMgr->VetoContextMenuMouseWarp();
112 }
113
114 // Set up the message bus
115 if( kiwayHolder )
116 SetKiway( this, &kiwayHolder->Kiway() );
117
118 if( HasKiway() )
119 Kiway().SetBlockingDialog( this );
120
121 Bind( wxEVT_CLOSE_WINDOW, &DIALOG_SHIM::OnCloseWindow, this );
122 Bind( wxEVT_BUTTON, &DIALOG_SHIM::OnButton, this );
123 Bind( wxEVT_SIZE, &DIALOG_SHIM::OnSize, this );
124 Bind( wxEVT_MOVE, &DIALOG_SHIM::OnMove, this );
125 Bind( wxEVT_INIT_DIALOG, &DIALOG_SHIM::onInitDialog, this );
126
127#ifdef __WINDOWS__
128 // On Windows, the app top windows can be brought to the foreground (at least temporarily)
129 // in certain circumstances such as when calling an external tool in Eeschema BOM generation.
130 // So set the parent frame (if exists) to top window to avoid this annoying behavior.
131 if( kiwayHolder && kiwayHolder->GetType() == KIWAY_HOLDER::FRAME )
132 Pgm().App().SetTopWindow( (EDA_BASE_FRAME*) kiwayHolder );
133#endif
134
135 Bind( wxEVT_PAINT, &DIALOG_SHIM::OnPaint, this );
136
137 wxString msg = wxString::Format( "Opening dialog %s", GetTitle() );
138 APP_MONITOR::AddNavigationBreadcrumb( msg, "dialog.open" );
139}
140
141
143{
144 m_isClosing = true;
145
146 Unbind( wxEVT_CLOSE_WINDOW, &DIALOG_SHIM::OnCloseWindow, this );
147 Unbind( wxEVT_BUTTON, &DIALOG_SHIM::OnButton, this );
148 Unbind( wxEVT_PAINT, &DIALOG_SHIM::OnPaint, this );
149 Unbind( wxEVT_SIZE, &DIALOG_SHIM::OnSize, this );
150 Unbind( wxEVT_MOVE, &DIALOG_SHIM::OnMove, this );
151 Unbind( wxEVT_INIT_DIALOG, &DIALOG_SHIM::onInitDialog, this );
152
153 std::function<void( wxWindowList& )> disconnectFocusHandlers =
154 [&]( wxWindowList& children )
155 {
156 for( wxWindow* child : children )
157 {
158 if( wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( child ) )
159 {
160 textCtrl->Disconnect( wxEVT_SET_FOCUS, wxFocusEventHandler( DIALOG_SHIM::onChildSetFocus ),
161 nullptr, this );
162 }
163 else if( wxStyledTextCtrl* scintilla = dynamic_cast<wxStyledTextCtrl*>( child ) )
164 {
165 scintilla->Disconnect( wxEVT_SET_FOCUS, wxFocusEventHandler( DIALOG_SHIM::onChildSetFocus ),
166 nullptr, this );
167 }
168 else
169 {
170 disconnectFocusHandlers( child->GetChildren() );
171 }
172 }
173 };
174
175 disconnectFocusHandlers( GetChildren() );
176
177 // if the dialog is quasi-modal, this will end its event loop
178 if( IsQuasiModal() )
179 EndQuasiModal( wxID_CANCEL );
180
181 if( HasKiway() )
182 Kiway().SetBlockingDialog( nullptr );
183
185}
186
187
188void DIALOG_SHIM::onInitDialog( wxInitDialogEvent& aEvent )
189{
191 aEvent.Skip();
192}
193
194
196{
197 // must be called from the constructor of derived classes,
198 // when all widgets are initialized, and therefore their size fixed
199
200 // SetSizeHints fixes the minimal size of sizers in the dialog
201 // (SetSizeHints calls Fit(), so no need to call it)
202 GetSizer()->SetSizeHints( this );
203}
204
205
206void DIALOG_SHIM::setSizeInDU( int x, int y )
207{
208 wxSize sz( x, y );
209 SetSize( ConvertDialogToPixels( sz ) );
210}
211
212
214{
215 wxSize sz( x, 0 );
216 return ConvertDialogToPixels( sz ).x;
217}
218
219
221{
222 wxSize sz( 0, y );
223 return ConvertDialogToPixels( sz ).y;
224}
225
226
227// our hashtable is an implementation secret, don't need or want it in a header file
228#include <hashtables.h>
229#include <typeinfo>
230
231
232
233void DIALOG_SHIM::SetPosition( const wxPoint& aNewPosition )
234{
235 wxDialog::SetPosition( aNewPosition );
236}
237
238
239bool DIALOG_SHIM::Show( bool show )
240{
241 bool ret;
242
243 if( show )
244 {
245#ifndef __WINDOWS__
246 wxDialog::Raise(); // Needed on OS X and some other window managers (i.e. Unity)
247#endif
248 ret = wxDialog::Show( show );
249
250 wxRect savedDialogRect;
252 std::string key = m_hash_key.empty() ? GetTitle().ToStdString() : m_hash_key;
253
254 auto dlgIt = settings->m_dialogControlValues.find( key );
255
256 if( dlgIt != settings->m_dialogControlValues.end() )
257 {
258 auto geoIt = dlgIt->second.find( "__geometry" );
259
260 if( geoIt != dlgIt->second.end() && geoIt->second.is_object() )
261 {
262 const nlohmann::json& g = geoIt->second;
263 savedDialogRect.SetPosition( wxPoint( g.value( "x", 0 ), g.value( "y", 0 ) ) );
264 savedDialogRect.SetSize( wxSize( g.value( "w", 500 ), g.value( "h", 300 ) ) );
265 }
266 }
267
268 if( savedDialogRect.GetSize().x != 0 && savedDialogRect.GetSize().y != 0 )
269 {
271 {
272 SetSize( savedDialogRect.GetPosition().x, savedDialogRect.GetPosition().y,
273 wxDialog::GetSize().x, wxDialog::GetSize().y, 0 );
274 }
275 else
276 {
277 SetSize( savedDialogRect.GetPosition().x, savedDialogRect.GetPosition().y,
278 std::max( wxDialog::GetSize().x, savedDialogRect.GetSize().x ),
279 std::max( wxDialog::GetSize().y, savedDialogRect.GetSize().y ), 0 );
280 }
281#ifdef __WXMAC__
282 if( m_parent != nullptr )
283 {
284 if( wxDisplay::GetFromPoint( m_parent->GetPosition() )
285 != wxDisplay::GetFromPoint( savedDialogRect.GetPosition() ) )
286 {
287 Centre();
288 }
289 }
290#endif
291 }
292 else if( m_initialSize != wxDefaultSize )
293 {
294 SetSize( m_initialSize );
295 Centre();
296 }
297
298 if( wxDisplay::GetFromWindow( this ) == wxNOT_FOUND )
299 Centre();
300
301 m_userPositioned = false;
302 m_userResized = false;
303
305 }
306 else
307 {
308
309#ifdef __WXMAC__
310 if ( m_eventLoop )
311 m_eventLoop->Exit( GetReturnCode() ); // Needed for APP-MODAL dlgs on OSX
312#endif
313
314 ret = wxDialog::Show( show );
315
317
318 if( m_parent )
319 m_parent->SetFocus();
320 }
321
322 return ret;
323}
324
325
327{
329 std::string key = m_hash_key.empty() ? GetTitle().ToStdString() : m_hash_key;
330
331 auto dlgIt = settings->m_dialogControlValues.find( key );
332
333 if( dlgIt == settings->m_dialogControlValues.end() )
334 return;
335
336 dlgIt->second.erase( "__geometry" );
337}
338
339
340void DIALOG_SHIM::OnSize( wxSizeEvent& aEvent )
341{
342 m_userResized = true;
343 aEvent.Skip();
344}
345
346
347void DIALOG_SHIM::OnMove( wxMoveEvent& aEvent )
348{
349 m_userPositioned = true;
350 aEvent.Skip();
351}
352
353
354bool DIALOG_SHIM::Enable( bool enable )
355{
356 // so we can do logging of this state change:
357 return wxDialog::Enable( enable );
358}
359
360
361std::string DIALOG_SHIM::generateKey( const wxWindow* aWin ) const
362{
363 auto getSiblingIndex =
364 []( const wxWindow* parent, const wxWindow* child )
365 {
366 wxString childClass = child->GetClassInfo()->GetClassName();
367 int index = 0;
368
369 for( const wxWindow* sibling : parent->GetChildren() )
370 {
371 if( sibling->GetClassInfo()->GetClassName() != childClass )
372 continue;
373
374 if( sibling == child )
375 break;
376
377 index++;
378 }
379
380 return index;
381 };
382
383 auto makeKey =
384 [&]( const wxWindow* window )
385 {
386 std::string key = wxString( window->GetClassInfo()->GetClassName() ).ToStdString();
387
388 if( window->GetParent() )
389 key += "_" + std::to_string( getSiblingIndex( window->GetParent(), window ) );
390
391 return key;
392 };
393
394 std::string key = makeKey( aWin );
395
396 for( const wxWindow* parent = aWin->GetParent(); parent && parent != this; parent = parent->GetParent() )
397 key = makeKey( parent ) + key;
398
399 return key;
400}
401
402
404{
406 std::string dialogKey = m_hash_key.empty() ? GetTitle().ToStdString() : m_hash_key;
407 auto& dlgMap = settings->m_dialogControlValues[ dialogKey ];
408
409 wxRect rect( GetPosition(), GetSize() );
410 nlohmann::json geom;
411 geom[ "x" ] = rect.GetX();
412 geom[ "y" ] = rect.GetY();
413 geom[ "w" ] = rect.GetWidth();
414 geom[ "h" ] = rect.GetHeight();
415 dlgMap[ "__geometry" ] = geom;
416
417 std::function<void( wxWindow* )> saveFn =
418 [&]( wxWindow* win )
419 {
420 if( PROPERTY_HOLDER* props = PROPERTY_HOLDER::SafeCast( win->GetClientData() ) )
421 {
422 if( !props->GetPropertyOr( "persist", false ) )
423 return;
424 }
425
426 std::string key = generateKey( win );
427
428 if( !key.empty() )
429 {
430 if( m_unitBinders.contains( win ) && !m_unitBinders[ win ]->UnitsInvariant() )
431 dlgMap[ key ] = m_unitBinders[ win ]->GetValue();
432 else if( wxComboBox* combo = dynamic_cast<wxComboBox*>( win ) )
433 dlgMap[ key ] = combo->GetValue();
434 else if( wxOwnerDrawnComboBox* od_combo = dynamic_cast<wxOwnerDrawnComboBox*>( win ) )
435 dlgMap[ key ] = od_combo->GetSelection();
436 else if( wxTextEntry* textEntry = dynamic_cast<wxTextEntry*>( win ) )
437 dlgMap[ key ] = textEntry->GetValue();
438 else if( wxChoice* choice = dynamic_cast<wxChoice*>( win ) )
439 dlgMap[ key ] = choice->GetSelection();
440 else if( wxCheckBox* check = dynamic_cast<wxCheckBox*>( win ) )
441 dlgMap[ key ] = check->GetValue();
442 else if( wxSpinCtrl* spin = dynamic_cast<wxSpinCtrl*>( win ) )
443 dlgMap[ key ] = spin->GetValue();
444 else if( wxRadioButton* radio = dynamic_cast<wxRadioButton*>( win ) )
445 dlgMap[ key ] = radio->GetValue();
446 else if( wxRadioBox* radioBox = dynamic_cast<wxRadioBox*>( win ) )
447 dlgMap[ key ] = radioBox->GetSelection();
448 else if( wxSplitterWindow* splitter = dynamic_cast<wxSplitterWindow*>( win ) )
449 dlgMap[ key ] = splitter->GetSashPosition();
450 else if( wxScrolledWindow* scrolled = dynamic_cast<wxScrolledWindow*>( win ) )
451 dlgMap[ key ] = scrolled->GetScrollPos( wxVERTICAL );
452 else if( wxNotebook* notebook = dynamic_cast<wxNotebook*>( win ) )
453 dlgMap[ key ] = notebook->GetPageText( notebook->GetSelection() );
454 }
455
456 for( wxWindow* child : win->GetChildren() )
457 saveFn( child );
458 };
459
460 if( PROPERTY_HOLDER* props = PROPERTY_HOLDER::SafeCast( GetClientData() ) )
461 {
462 if( !props->GetPropertyOr( "persist", false ) )
463 return;
464 }
465
466 for( wxWindow* child : GetChildren() )
467 saveFn( child );
468}
469
470
472{
474 std::string dialogKey = m_hash_key.empty() ? GetTitle().ToStdString() : m_hash_key;
475 auto dlgIt = settings->m_dialogControlValues.find( dialogKey );
476
477 if( dlgIt == settings->m_dialogControlValues.end() )
478 return;
479
480 const auto& dlgMap = dlgIt->second;
481
482 std::function<void( wxWindow* )> loadFn =
483 [&]( wxWindow* win )
484 {
485 if( PROPERTY_HOLDER* props = PROPERTY_HOLDER::SafeCast( win->GetClientData() ) )
486 {
487 if( !props->GetPropertyOr( "persist", false ) )
488 return;
489 }
490
491 std::string key = generateKey( win );
492
493 if( !key.empty() )
494 {
495 auto it = dlgMap.find( key );
496
497 if( it != dlgMap.end() )
498 {
499 const nlohmann::json& j = it->second;
500
501 if( m_unitBinders.contains( win ) && !m_unitBinders[ win ]->UnitsInvariant() )
502 {
503 if( j.is_number_integer() )
504 m_unitBinders[ win ]->SetValue( j.get<int>() );
505 }
506 else if( wxComboBox* combo = dynamic_cast<wxComboBox*>( win ) )
507 {
508 if( j.is_string() )
509 combo->SetValue( wxString::FromUTF8( j.get<std::string>().c_str() ) );
510 }
511 else if( wxOwnerDrawnComboBox* od_combo = dynamic_cast<wxOwnerDrawnComboBox*>( win ) )
512 {
513 if( j.is_number_integer() )
514 {
515 int index = j.get<int>();
516
517 if( index >= 0 && index < (int) od_combo->GetCount() )
518 od_combo->SetSelection( index );
519 }
520 }
521 else if( wxTextEntry* textEntry = dynamic_cast<wxTextEntry*>( win ) )
522 {
523 if( j.is_string() )
524 textEntry->ChangeValue( wxString::FromUTF8( j.get<std::string>().c_str() ) );
525 }
526 else if( wxChoice* choice = dynamic_cast<wxChoice*>( win ) )
527 {
528 if( j.is_number_integer() )
529 {
530 int index = j.get<int>();
531
532 if( index >= 0 && index < (int) choice->GetCount() )
533 choice->SetSelection( index );
534 }
535 }
536 else if( wxCheckBox* check = dynamic_cast<wxCheckBox*>( win ) )
537 {
538 if( j.is_boolean() )
539 check->SetValue( j.get<bool>() );
540 }
541 else if( wxSpinCtrl* spin = dynamic_cast<wxSpinCtrl*>( win ) )
542 {
543 if( j.is_number_integer() )
544 spin->SetValue( j.get<int>() );
545 }
546 else if( wxRadioButton* radio = dynamic_cast<wxRadioButton*>( win ) )
547 {
548 if( j.is_boolean() )
549 radio->SetValue( j.get<bool>() );
550 }
551 else if( wxRadioBox* radioBox = dynamic_cast<wxRadioBox*>( win ) )
552 {
553 if( j.is_number_integer() )
554 {
555 int index = j.get<int>();
556
557 if( index >= 0 && index < (int) radioBox->GetCount() )
558 radioBox->SetSelection( index );
559 }
560 }
561 else if( wxSplitterWindow* splitter = dynamic_cast<wxSplitterWindow*>( win ) )
562 {
563 if( j.is_number_integer() )
564 splitter->SetSashPosition( j.get<int>() );
565 }
566 else if( wxScrolledWindow* scrolled = dynamic_cast<wxScrolledWindow*>( win ) )
567 {
568 if( j.is_number_integer() )
569 scrolled->SetScrollPos( wxVERTICAL, j.get<int>() );
570 }
571 else if( wxNotebook* notebook = dynamic_cast<wxNotebook*>( win ) )
572 {
573 if( j.is_string() )
574 {
575 wxString pageTitle = wxString::FromUTF8( j.get<std::string>().c_str() );
576
577 for( int page = 0; page < (int) notebook->GetPageCount(); ++page )
578 {
579 if( notebook->GetPageText( page ) == pageTitle )
580 notebook->SetSelection( page );
581 }
582 }
583 }
584 }
585 }
586
587 for( wxWindow* child : win->GetChildren() )
588 loadFn( child );
589 };
590
591 if( PROPERTY_HOLDER* props = PROPERTY_HOLDER::SafeCast( GetClientData() ) )
592 {
593 if( !props->GetPropertyOr( "persist", false ) )
594 return;
595 }
596
597 for( wxWindow* child : GetChildren() )
598 loadFn( child );
599}
600
601
602void DIALOG_SHIM::OptOut( wxWindow* aWindow )
603{
604 PROPERTY_HOLDER* props = new PROPERTY_HOLDER();
605 props->SetProperty( "persist", false );
606 aWindow->SetClientData( props );
607}
608
609
610void DIALOG_SHIM::RegisterUnitBinder( UNIT_BINDER* aUnitBinder, wxWindow* aWindow )
611{
612 m_unitBinders[ aWindow ] = aUnitBinder;
613}
614
615
616// Recursive descent doing a SelectAll() in wxTextCtrls.
617// MacOS User Interface Guidelines state that when tabbing to a text control all its
618// text should be selected. Since wxWidgets fails to implement this, we do it here.
619void DIALOG_SHIM::SelectAllInTextCtrls( wxWindowList& children )
620{
621 for( wxWindow* child : children )
622 {
623 if( wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( child ) )
624 {
625 m_beforeEditValues[ textCtrl ] = textCtrl->GetValue();
626 textCtrl->Connect( wxEVT_SET_FOCUS, wxFocusEventHandler( DIALOG_SHIM::onChildSetFocus ),
627 nullptr, this );
628
629 // We don't currently run this on GTK because some window managers don't hide the
630 // selection in non-active controls, and other window managers do the selection
631 // automatically anyway.
632#if defined( __WXMAC__ ) || defined( __WXMSW__ )
633 if( !textCtrl->GetStringSelection().IsEmpty() )
634 {
635 // Respect an existing selection
636 }
637 else if( textCtrl->IsEditable() )
638 {
639 textCtrl->SelectAll();
640 }
641#else
642 ignore_unused( textCtrl );
643#endif
644 }
645 else if( wxStyledTextCtrl* scintilla = dynamic_cast<wxStyledTextCtrl*>( child ) )
646 {
647 m_beforeEditValues[ scintilla ] = scintilla->GetText();
648 scintilla->Connect( wxEVT_SET_FOCUS,
649 wxFocusEventHandler( DIALOG_SHIM::onChildSetFocus ),
650 nullptr, this );
651
652 if( !scintilla->GetSelectedText().IsEmpty() )
653 {
654 // Respect an existing selection
655 }
656 else if( scintilla->GetMarginWidth( 0 ) > 0 )
657 {
658 // Don't select-all in Custom Rules, etc.
659 }
660 else if( scintilla->IsEditable() )
661 {
662 scintilla->SelectAll();
663 }
664 }
665#ifdef __WXMAC__
666 // Temp hack for square (looking) buttons on OSX. Will likely be made redundant
667 // by the image store....
668 else if( dynamic_cast<wxBitmapButton*>( child ) != nullptr )
669 {
670 wxSize minSize( 29, 27 );
671 wxRect rect = child->GetRect();
672
673 child->ConvertDialogToPixels( minSize );
674
675 rect.Inflate( std::max( 0, minSize.x - rect.GetWidth() ),
676 std::max( 0, minSize.y - rect.GetHeight() ) );
677
678 child->SetMinSize( rect.GetSize() );
679 child->SetSize( rect );
680 }
681#endif
682 else
683 {
684 SelectAllInTextCtrls( child->GetChildren() );
685 }
686 }
687}
688
689
690void DIALOG_SHIM::OnPaint( wxPaintEvent &event )
691{
693 {
695
696 SelectAllInTextCtrls( GetChildren() );
697
700 else
701 KIPLATFORM::UI::ForceFocus( this ); // Focus the dialog itself
702
703 m_firstPaintEvent = false;
704 }
705
706 event.Skip();
707}
708
709
711{
712 if( !GetTitle().StartsWith( wxS( "*" ) ) )
713 SetTitle( wxS( "*" ) + GetTitle() );
714}
715
716
718{
719 if( GetTitle().StartsWith( wxS( "*" ) ) )
720 SetTitle( GetTitle().AfterFirst( '*' ) );
721}
722
724{
725 // Apple in its infinite wisdom will raise a disabled window before even passing
726 // us the event, so we have no way to stop it. Instead, we must set an order on
727 // the windows so that the modal will be pushed in front of the disabled
728 // window when it is raised.
730
731 // Call the base class ShowModal() method
732 return wxDialog::ShowModal();
733}
734
735/*
736 Quasi-Modal Mode Explained:
737
738 The gtk calls in wxDialog::ShowModal() cause event routing problems if that
739 modal dialog then tries to use KIWAY_PLAYER::ShowModal(). The latter shows up
740 and mostly works but does not respond to the window decoration close button.
741 There is no way to get around this without reversing the gtk calls temporarily.
742
743 Quasi-Modal mode is our own almost modal mode which disables only the parent
744 of the DIALOG_SHIM, leaving other frames operable and while staying captured in the
745 nested event loop. This avoids the gtk calls and leaves event routing pure
746 and sufficient to operate the KIWAY_PLAYER::ShowModal() properly. When using
747 ShowQuasiModal() you have to use EndQuasiModal() in your dialogs and not
748 EndModal(). There is also IsQuasiModal() but its value can only be true
749 when the nested event loop is active. Do not mix the modal and quasi-modal
750 functions. Use one set or the other.
751
752 You might find this behavior preferable over a pure modal mode, and it was said
753 that only the Mac has this natively, but now other platforms have something
754 similar. You CAN use it anywhere for any dialog. But you MUST use it when
755 you want to use KIWAY_PLAYER::ShowModal() from a dialog event.
756*/
757
759{
760 NULLER raii_nuller( (void*&) m_qmodal_loop );
761
762 // release the mouse if it's currently captured as the window having it
763 // will be disabled when this dialog is shown -- but will still keep the
764 // capture making it impossible to do anything in the modal dialog itself
765 wxWindow* win = wxWindow::GetCapture();
766 if( win )
767 win->ReleaseMouse();
768
769 // Get the optimal parent
770 wxWindow* parent = GetParentForModalDialog( GetParent(), GetWindowStyle() );
771
772 wxASSERT_MSG( !m_qmodal_parent_disabler, wxT( "Caller using ShowQuasiModal() twice on same window?" ) );
773
774 // quasi-modal: disable only my "optimal" parent
776
777 // Apple in its infinite wisdom will raise a disabled window before even passing
778 // us the event, so we have no way to stop it. Instead, we must set an order on
779 // the windows so that the quasi-modal will be pushed in front of the disabled
780 // window when it is raised.
782
783 Show( true );
784
785 m_qmodal_showing = true;
786
787 wxGUIEventLoop event_loop;
788
789 m_qmodal_loop = &event_loop;
790
791 event_loop.Run();
792
793 m_qmodal_showing = false;
794
795 if( parent )
796 parent->SetFocus();
797
798 return GetReturnCode();
799}
800
801
803{
806}
807
808
810{
813}
814
815
816void DIALOG_SHIM::EndQuasiModal( int retCode )
817{
818 // Hook up validator and transfer data from controls handling so quasi-modal dialogs
819 // handle validation in the same way as other dialogs.
820 if( ( retCode == wxID_OK ) && ( !Validate() || !TransferDataFromWindow() ) )
821 return;
822
823 SetReturnCode( retCode );
824
825 if( !IsQuasiModal() )
826 {
827 wxFAIL_MSG( wxT( "Either DIALOG_SHIM::EndQuasiModal was called twice, or ShowQuasiModal"
828 "wasn't called" ) );
829 return;
830 }
831
833
834 if( m_qmodal_loop )
835 {
836 if( m_qmodal_loop->IsRunning() )
837 m_qmodal_loop->Exit( 0 );
838 else
839 m_qmodal_loop->ScheduleExit( 0 );
840 }
841
843 m_qmodal_parent_disabler = nullptr;
844
845 Show( false );
846}
847
848
849void DIALOG_SHIM::OnCloseWindow( wxCloseEvent& aEvent )
850{
851 wxString msg = wxString::Format( "Closing dialog %s", GetTitle() );
852 APP_MONITOR::AddNavigationBreadcrumb( msg, "dialog.close" );
853
854 if( IsQuasiModal() )
855 {
856 EndQuasiModal( wxID_CANCEL );
857 return;
858 }
859
860 // This is mandatory to allow wxDialogBase::OnCloseWindow() to be called.
861 aEvent.Skip();
862}
863
864
865void DIALOG_SHIM::OnButton( wxCommandEvent& aEvent )
866{
867 const int id = aEvent.GetId();
868
869 if( IsQuasiModal() )
870 {
871 if( id == GetAffirmativeId() )
872 {
873 EndQuasiModal( id );
874 }
875 else if( id == wxID_APPLY )
876 {
877 // Dialogs that provide Apply buttons should make sure data is valid before
878 // allowing a transfer, as there is no other way to indicate failure
879 // (i.e. the dialog can't refuse to close as it might with OK, because it
880 // isn't closing anyway)
881 if( Validate() )
882 {
883 ignore_unused( TransferDataFromWindow() );
884 }
885 }
886 else if( id == wxID_CANCEL )
887 {
888 EndQuasiModal( wxID_CANCEL );
889 }
890 else // not a standard button
891 {
892 aEvent.Skip();
893 }
894
895 return;
896 }
897
898 // This is mandatory to allow wxDialogBase::OnButton() to be called.
899 aEvent.Skip();
900}
901
902
903void DIALOG_SHIM::onChildSetFocus( wxFocusEvent& aEvent )
904{
905 // When setting focus to a text control reset the before-edit value.
906
907 if( !m_isClosing )
908 {
909 if( wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( aEvent.GetEventObject() ) )
910 m_beforeEditValues[ textCtrl ] = textCtrl->GetValue();
911 else if( wxStyledTextCtrl* scintilla = dynamic_cast<wxStyledTextCtrl*>( aEvent.GetEventObject() ) )
912 m_beforeEditValues[ scintilla ] = scintilla->GetText();
913 }
914
915 aEvent.Skip();
916}
917
918
919void DIALOG_SHIM::OnCharHook( wxKeyEvent& aEvt )
920{
921 if( aEvt.GetKeyCode() == 'U' && aEvt.GetModifiers() == wxMOD_CONTROL )
922 {
923 if( m_parentFrame )
924 {
926 return;
927 }
928 }
929 // shift-return (Mac default) or Ctrl-Return (GTK) for new line input
930 else if( ( aEvt.GetKeyCode() == WXK_RETURN || aEvt.GetKeyCode() == WXK_NUMPAD_ENTER ) && aEvt.ShiftDown() )
931 {
932 wxObject* eventSource = aEvt.GetEventObject();
933
934 if( wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( eventSource ) )
935 {
936 // If the text control is not multi-line, we want to close the dialog
937 if( !textCtrl->IsMultiLine() )
938 {
939 wxPostEvent( this, wxCommandEvent( wxEVT_COMMAND_BUTTON_CLICKED, wxID_OK ) );
940 return;
941 }
942
943#if defined( __WXMAC__ ) || defined( __WXMSW__ )
944 wxString eol = "\r\n";
945#else
946 wxString eol = "\n";
947#endif
948
949 long pos = textCtrl->GetInsertionPoint();
950 textCtrl->WriteText( eol );
951 textCtrl->SetInsertionPoint( pos + eol.length() );
952 return;
953 }
954 else if( wxStyledTextCtrl* scintilla = dynamic_cast<wxStyledTextCtrl*>( eventSource ) )
955 {
956 wxString eol = "\n";
957 switch( scintilla->GetEOLMode() )
958 {
959 case wxSTC_EOL_CRLF: eol = "\r\n"; break;
960 case wxSTC_EOL_CR: eol = "\r"; break;
961 case wxSTC_EOL_LF: eol = "\n"; break;
962 }
963
964 long pos = scintilla->GetCurrentPos();
965 scintilla->InsertText( pos, eol );
966 scintilla->GotoPos( pos + eol.length() );
967 return;
968 }
969 return;
970 }
971 // command-return (Mac default) or Ctrl-Return (GTK) for OK
972 else if( ( aEvt.GetKeyCode() == WXK_RETURN || aEvt.GetKeyCode() == WXK_NUMPAD_ENTER ) && aEvt.ControlDown() )
973 {
974 wxPostEvent( this, wxCommandEvent( wxEVT_COMMAND_BUTTON_CLICKED, wxID_OK ) );
975 return;
976 }
977 else if( aEvt.GetKeyCode() == WXK_TAB && !aEvt.ControlDown() )
978 {
979 wxWindow* currentWindow = wxWindow::FindFocus();
980 int currentIdx = -1;
981 int delta = aEvt.ShiftDown() ? -1 : 1;
982
983 auto advance =
984 [&]( int& idx )
985 {
986 // Wrap-around modulus
987 int size = (int) m_tabOrder.size();
988 idx = ( ( idx + delta ) % size + size ) % size;
989 };
990
991 for( size_t i = 0; i < m_tabOrder.size(); ++i )
992 {
993 if( m_tabOrder[i] == currentWindow )
994 {
995 currentIdx = (int) i;
996 break;
997 }
998 }
999
1000 if( currentIdx >= 0 )
1001 {
1002 advance( currentIdx );
1003
1004 //todo: We don't currently have non-textentry dialog boxes but this will break if
1005 // we add them.
1006#ifdef __APPLE__
1007 while( dynamic_cast<wxTextEntry*>( m_tabOrder[ currentIdx ] ) == nullptr )
1008 advance( currentIdx );
1009#endif
1010
1011 m_tabOrder[ currentIdx ]->SetFocus();
1012 return;
1013 }
1014 }
1015 else if( aEvt.GetKeyCode() == WXK_ESCAPE )
1016 {
1017 wxObject* eventSource = aEvt.GetEventObject();
1018
1019 if( wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( eventSource ) )
1020 {
1021 // First escape after an edit cancels edit
1022 if( textCtrl->GetValue() != m_beforeEditValues[ textCtrl ] )
1023 {
1024 textCtrl->SetValue( m_beforeEditValues[ textCtrl ] );
1025 textCtrl->SelectAll();
1026 return;
1027 }
1028 }
1029 else if( wxStyledTextCtrl* scintilla = dynamic_cast<wxStyledTextCtrl*>( eventSource ) )
1030 {
1031 // First escape after an edit cancels edit
1032 if( scintilla->GetText() != m_beforeEditValues[ scintilla ] )
1033 {
1034 scintilla->SetText( m_beforeEditValues[ scintilla ] );
1035 scintilla->SelectAll();
1036 return;
1037 }
1038 }
1039 }
1040
1041 aEvt.Skip();
1042}
1043
1044
1045static void recursiveDescent( wxSizer* aSizer, std::map<int, wxString>& aLabels )
1046{
1047 wxStdDialogButtonSizer* sdbSizer = dynamic_cast<wxStdDialogButtonSizer*>( aSizer );
1048
1049 auto setupButton =
1050 [&]( wxButton* aButton )
1051 {
1052 if( aLabels.count( aButton->GetId() ) > 0 )
1053 {
1054 aButton->SetLabel( aLabels[ aButton->GetId() ] );
1055 }
1056 else
1057 {
1058 // wxWidgets has an uneven track record when the language is changed on
1059 // the fly so we set them even when they don't appear in the label map
1060 switch( aButton->GetId() )
1061 {
1062 case wxID_OK: aButton->SetLabel( _( "&OK" ) ); break;
1063 case wxID_CANCEL: aButton->SetLabel( _( "&Cancel" ) ); break;
1064 case wxID_YES: aButton->SetLabel( _( "&Yes" ) ); break;
1065 case wxID_NO: aButton->SetLabel( _( "&No" ) ); break;
1066 case wxID_APPLY: aButton->SetLabel( _( "&Apply" ) ); break;
1067 case wxID_SAVE: aButton->SetLabel( _( "&Save" ) ); break;
1068 case wxID_HELP: aButton->SetLabel( _( "&Help" ) ); break;
1069 case wxID_CONTEXT_HELP: aButton->SetLabel( _( "&Help" ) ); break;
1070 }
1071 }
1072 };
1073
1074 if( sdbSizer )
1075 {
1076 if( sdbSizer->GetAffirmativeButton() )
1077 setupButton( sdbSizer->GetAffirmativeButton() );
1078
1079 if( sdbSizer->GetApplyButton() )
1080 setupButton( sdbSizer->GetApplyButton() );
1081
1082 if( sdbSizer->GetNegativeButton() )
1083 setupButton( sdbSizer->GetNegativeButton() );
1084
1085 if( sdbSizer->GetCancelButton() )
1086 setupButton( sdbSizer->GetCancelButton() );
1087
1088 if( sdbSizer->GetHelpButton() )
1089 setupButton( sdbSizer->GetHelpButton() );
1090
1091 sdbSizer->Layout();
1092
1093 if( sdbSizer->GetAffirmativeButton() )
1094 sdbSizer->GetAffirmativeButton()->SetDefault();
1095 }
1096
1097 for( wxSizerItem* item : aSizer->GetChildren() )
1098 {
1099 if( item->GetSizer() )
1100 recursiveDescent( item->GetSizer(), aLabels );
1101 }
1102}
1103
1104
1105void DIALOG_SHIM::SetupStandardButtons( std::map<int, wxString> aLabels )
1106{
1107 recursiveDescent( GetSizer(), aLabels );
1108}
const char * name
Definition: DXF_plotter.cpp:62
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:61
void SelectAllInTextCtrls(wxWindowList &children)
bool m_isClosing
Definition: dialog_shim.h:247
std::vector< wxWindow * > m_tabOrder
Definition: dialog_shim.h:256
void OnPaint(wxPaintEvent &event)
bool m_qmodal_showing
Definition: dialog_shim.h:251
virtual void TearDownQuasiModal()
Override this method to perform dialog tear down actions not suitable for object dtor.
Definition: dialog_shim.h:208
int vertPixelsFromDU(int y) const
Convert an integer number of dialog units to pixels, vertically.
bool Show(bool show) override
wxGUIEventLoop * m_qmodal_loop
Definition: dialog_shim.h:249
void onChildSetFocus(wxFocusEvent &aEvent)
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
Definition: dialog_shim.h:252
void onInitDialog(wxInitDialogEvent &aEvent)
int ShowQuasiModal()
std::string m_hash_key
Definition: dialog_shim.h:236
bool m_firstPaintEvent
Definition: dialog_shim.h:245
bool m_userResized
Definition: dialog_shim.h:261
int horizPixelsFromDU(int x) const
Convert an integer number of dialog units to pixels, horizontally.
void resetSize()
Clear the existing dialog size and position.
void ClearModify()
std::map< wxWindow *, wxString > m_beforeEditValues
Definition: dialog_shim.h:264
void setSizeInDU(int x, int y)
Set the dialog to the given dimensions in "dialog units".
bool IsQuasiModal() const
Definition: dialog_shim.h:86
bool m_useCalculatedSize
Definition: dialog_shim.h:240
std::map< wxWindow *, UNIT_BINDER * > m_unitBinders
Definition: dialog_shim.h:265
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 CleanupAfterModalSubDialog()
std::string generateKey(const wxWindow *aWin) const
void PrepareForModalSubDialog()
void OnModify()
void OnButton(wxCommandEvent &aEvent)
Properly handle the default button events when in the quasimodal mode when not calling EndQuasiModal ...
void finishDialogSettings()
In all dialogs, we must call the same functions to fix minimal dlg size, the default position and per...
wxWindow * m_initialFocusTarget
Definition: dialog_shim.h:246
void OnSize(wxSizeEvent &aEvent)
bool Enable(bool enable) override
void SetPosition(const wxPoint &aNewPosition)
Force the position of the dialog to a new position.
bool m_userPositioned
Definition: dialog_shim.h:260
void OnCloseWindow(wxCloseEvent &aEvent)
Properly handle the wxCloseEvent when in the quasimodal mode when not calling EndQuasiModal which is ...
wxSize m_initialSize
Definition: dialog_shim.h:259
EDA_BASE_FRAME * m_parentFrame
Definition: dialog_shim.h:254
virtual void OnCharHook(wxKeyEvent &aEvt)
EDA_UNITS GetUserUnits() const
Definition: dialog_shim.h:111
int ShowModal() override
The base frame for deriving all KiCad main window classes.
virtual void ToggleUserUnits()
A mix in class which holds the location of a wxWindow's KIWAY.
Definition: kiway_holder.h:39
KIWAY & Kiway() const
Return a reference to the KIWAY that this object has an opportunity to participate in.
Definition: kiway_holder.h:55
bool HasKiway() const
Safety check before asking for the Kiway reference.
Definition: kiway_holder.h:65
HOLDER_TYPE GetType() const
Definition: kiway_holder.h:48
void SetBlockingDialog(wxWindow *aWin)
Definition: kiway.cpp:643
Definition: raii.h:38
virtual wxApp & App()
Return a bare naked wxApp which may come from wxPython, SINGLE_TOP, or kicad.exe.
Definition: pgm_base.cpp:183
virtual SETTINGS_MANAGER & GetSettingsManager() const
Definition: pgm_base.h:125
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*.
COMMON_SETTINGS * GetCommonSettings() const
Retrieve the common settings shared by all applications.
Master controller class:
Definition: tool_manager.h:62
bool IsContextMenuActive() const
True while processing a context menu.
Definition: tool_manager.h:504
void VetoContextMenuMouseWarp()
Disable mouse warping after the current context menu is closed.
Definition: tool_manager.h:515
EDA_UNITS GetUserUnits() const
Temporarily disable a window, and then re-enable on destruction.
Definition: raii.h:87
void SuspendForTrueModal()
Definition: raii.h:105
void ResumeAfterTrueModal()
Definition: raii.h:111
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:157
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:902
see class PGM_BASE
KIWAY Kiway(KFCTL_STANDALONE)
int delta