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#include <grid_tricks.h>
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 else if( WX_GRID* grid = dynamic_cast<WX_GRID*>( win ) )
563 {
564 dlgMap[ key ] = grid->GetShownColumnsAsString();
565 }
566 }
567
568 for( wxWindow* child : win->GetChildren() )
569 saveFn( child );
570 };
571
572 if( PROPERTY_HOLDER* props = PROPERTY_HOLDER::SafeCast( GetClientData() ) )
573 {
574 if( !props->GetPropertyOr( "persist", false ) )
575 return;
576 }
577
578 for( wxWindow* child : GetChildren() )
579 saveFn( child );
580}
581
582
584{
585 COMMON_SETTINGS* settings = Pgm().GetCommonSettings();
586
587 if( !settings )
588 return;
589
590 std::string dialogKey = m_hash_key.empty() ? GetTitle().ToStdString() : m_hash_key;
591 auto dlgIt = settings->m_dialogControlValues.find( dialogKey );
592
593 if( dlgIt == settings->m_dialogControlValues.end() )
594 return;
595
596 const std::map<std::string, nlohmann::json>& dlgMap = dlgIt->second;
597
598 std::function<void( wxWindow* )> loadFn =
599 [&]( wxWindow* win )
600 {
601 if( PROPERTY_HOLDER* props = PROPERTY_HOLDER::SafeCast( win->GetClientData() ) )
602 {
603 if( !props->GetPropertyOr( "persist", false ) )
604 return;
605 }
606
607 std::string key = generateKey( win );
608
609 if( !key.empty() )
610 {
611 auto it = dlgMap.find( key );
612
613 if( it != dlgMap.end() )
614 {
615 const nlohmann::json& j = it->second;
616
617 if( m_unitBinders.contains( win ) && !m_unitBinders[ win ]->UnitsInvariant() )
618 {
619 if( j.is_number_integer() )
620 m_unitBinders[ win ]->ChangeValue( j.get<int>() );
621 }
622 else if( wxComboBox* combo = dynamic_cast<wxComboBox*>( win ) )
623 {
624 if( j.is_string() )
625 combo->SetValue( wxString::FromUTF8( j.get<std::string>().c_str() ) );
626 }
627 else if( wxOwnerDrawnComboBox* od_combo = dynamic_cast<wxOwnerDrawnComboBox*>( win ) )
628 {
629 if( j.is_number_integer() )
630 {
631 int index = j.get<int>();
632
633 if( index >= 0 && index < (int) od_combo->GetCount() )
634 od_combo->SetSelection( index );
635 }
636 }
637 else if( wxTextEntry* textEntry = dynamic_cast<wxTextEntry*>( win ) )
638 {
639 if( j.is_string() )
640 textEntry->ChangeValue( wxString::FromUTF8( j.get<std::string>().c_str() ) );
641 }
642 else if( wxChoice* choice = dynamic_cast<wxChoice*>( win ) )
643 {
644 if( j.is_number_integer() )
645 {
646 int index = j.get<int>();
647
648 if( index >= 0 && index < (int) choice->GetCount() )
649 choice->SetSelection( index );
650 }
651 }
652 else if( wxCheckBox* check = dynamic_cast<wxCheckBox*>( win ) )
653 {
654 if( j.is_boolean() )
655 check->SetValue( j.get<bool>() );
656 }
657 else if( wxSpinCtrl* spin = dynamic_cast<wxSpinCtrl*>( win ) )
658 {
659 if( j.is_number_integer() )
660 spin->SetValue( j.get<int>() );
661 }
662 else if( wxRadioButton* radio = dynamic_cast<wxRadioButton*>( win ) )
663 {
664 if( j.is_boolean() )
665 {
666 // Only set active radio buttons. Let wxWidgets handle unsetting the inactive
667 // ones. This prevents all from being unset, which trips up wxWidgets in some
668 // cases.
669 if( j.get<bool>() )
670 radio->SetValue( true );
671 }
672 }
673 else if( wxRadioBox* radioBox = dynamic_cast<wxRadioBox*>( win ) )
674 {
675 if( j.is_number_integer() )
676 {
677 int index = j.get<int>();
678
679 if( index >= 0 && index < (int) radioBox->GetCount() )
680 radioBox->SetSelection( index );
681 }
682 }
683 else if( wxSplitterWindow* splitter = dynamic_cast<wxSplitterWindow*>( win ) )
684 {
685 if( j.is_number_integer() )
686 splitter->SetSashPosition( j.get<int>() );
687 }
688 else if( wxScrolledWindow* scrolled = dynamic_cast<wxScrolledWindow*>( win ) )
689 {
690 if( j.is_number_integer() )
691 scrolled->SetScrollPos( wxVERTICAL, j.get<int>() );
692 }
693 else if( wxNotebook* notebook = dynamic_cast<wxNotebook*>( win ) )
694 {
695 if( j.is_string() )
696 {
697 wxString pageTitle = wxString::FromUTF8( j.get<std::string>().c_str() );
698
699 for( int page = 0; page < (int) notebook->GetPageCount(); ++page )
700 {
701 if( notebook->GetPageText( page ) == pageTitle )
702 notebook->SetSelection( page );
703 }
704 }
705 }
706 else if( WX_GRID* grid = dynamic_cast<WX_GRID*>( win ) )
707 {
708 if( j.is_string() )
709 grid->ShowHideColumns( wxString::FromUTF8( j.get<std::string>().c_str() ) );
710 }
711 }
712 }
713
714 for( wxWindow* child : win->GetChildren() )
715 loadFn( child );
716 };
717
718 if( PROPERTY_HOLDER* props = PROPERTY_HOLDER::SafeCast( GetClientData() ) )
719 {
720 if( !props->GetPropertyOr( "persist", false ) )
721 return;
722 }
723
724 for( wxWindow* child : GetChildren() )
725 loadFn( child );
726}
727
728
729void DIALOG_SHIM::OptOut( wxWindow* aWindow )
730{
731 PROPERTY_HOLDER* props = new PROPERTY_HOLDER();
732 props->SetProperty( "persist", false );
733 aWindow->SetClientData( props );
734}
735
736
737void DIALOG_SHIM::RegisterUnitBinder( UNIT_BINDER* aUnitBinder, wxWindow* aWindow )
738{
739 m_unitBinders[ aWindow ] = aUnitBinder;
740}
741
742
743// Recursive descent doing a SelectAll() in wxTextCtrls.
744// MacOS User Interface Guidelines state that when tabbing to a text control all its
745// text should be selected. Since wxWidgets fails to implement this, we do it here.
746void DIALOG_SHIM::SelectAllInTextCtrls( wxWindowList& children )
747{
748 for( wxWindow* child : children )
749 {
750 if( wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( child ) )
751 {
752 m_beforeEditValues[ textCtrl ] = textCtrl->GetValue();
753 textCtrl->Connect( wxEVT_SET_FOCUS, wxFocusEventHandler( DIALOG_SHIM::onChildSetFocus ),
754 nullptr, this );
755
756 // We don't currently run this on GTK because some window managers don't hide the
757 // selection in non-active controls, and other window managers do the selection
758 // automatically anyway.
759#if defined( __WXMAC__ ) || defined( __WXMSW__ )
760 if( !textCtrl->GetStringSelection().IsEmpty() )
761 {
762 // Respect an existing selection
763 }
764 else if( textCtrl->IsEditable() )
765 {
766 textCtrl->SelectAll();
767 }
768#else
769 ignore_unused( textCtrl );
770#endif
771 }
772 else if( wxStyledTextCtrl* scintilla = dynamic_cast<wxStyledTextCtrl*>( child ) )
773 {
774 m_beforeEditValues[ scintilla ] = scintilla->GetText();
775 scintilla->Connect( wxEVT_SET_FOCUS,
776 wxFocusEventHandler( DIALOG_SHIM::onChildSetFocus ),
777 nullptr, this );
778
779 if( !scintilla->GetSelectedText().IsEmpty() )
780 {
781 // Respect an existing selection
782 }
783 else if( scintilla->GetMarginWidth( 0 ) > 0 )
784 {
785 // Don't select-all in Custom Rules, etc.
786 }
787 else if( scintilla->IsEditable() )
788 {
789 scintilla->SelectAll();
790 }
791 }
792#ifdef __WXMAC__
793 // Temp hack for square (looking) buttons on OSX. Will likely be made redundant
794 // by the image store....
795 else if( dynamic_cast<wxBitmapButton*>( child ) != nullptr )
796 {
797 wxSize minSize( 29, 27 );
798 wxRect rect = child->GetRect();
799
800 child->ConvertDialogToPixels( minSize );
801
802 rect.Inflate( std::max( 0, minSize.x - rect.GetWidth() ),
803 std::max( 0, minSize.y - rect.GetHeight() ) );
804
805 child->SetMinSize( rect.GetSize() );
806 child->SetSize( rect );
807 }
808#endif
809 else
810 {
811 SelectAllInTextCtrls( child->GetChildren() );
812 }
813 }
814}
815
816
817void DIALOG_SHIM::registerUndoRedoHandlers( wxWindowList& children )
818{
819 for( wxWindow* child : children )
820 {
821 if( wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( child ) )
822 {
823 textCtrl->Bind( wxEVT_TEXT, &DIALOG_SHIM::onCommandEvent, this );
824 m_currentValues[ textCtrl ] = textCtrl->GetValue();
825 }
826 else if( wxStyledTextCtrl* scintilla = dynamic_cast<wxStyledTextCtrl*>( child ) )
827 {
828 scintilla->Bind( wxEVT_STC_CHANGE, &DIALOG_SHIM::onStyledTextChanged, this );
829 m_currentValues[ scintilla ] = scintilla->GetText();
830 }
831 else if( wxComboBox* combo = dynamic_cast<wxComboBox*>( child ) )
832 {
833 combo->Bind( wxEVT_TEXT, &DIALOG_SHIM::onCommandEvent, this );
834 combo->Bind( wxEVT_COMBOBOX, &DIALOG_SHIM::onCommandEvent, this );
835 m_currentValues[ combo ] = combo->GetValue();
836 }
837 else if( wxChoice* choice = dynamic_cast<wxChoice*>( child ) )
838 {
839 choice->Bind( wxEVT_CHOICE, &DIALOG_SHIM::onCommandEvent, this );
840 m_currentValues[ choice ] = static_cast<long>( choice->GetSelection() );
841 }
842 else if( wxCheckBox* check = dynamic_cast<wxCheckBox*>( child ) )
843 {
844 check->Bind( wxEVT_CHECKBOX, &DIALOG_SHIM::onCommandEvent, this );
845 m_currentValues[ check ] = check->GetValue();
846 }
847 else if( wxSpinCtrl* spin = dynamic_cast<wxSpinCtrl*>( child ) )
848 {
849 spin->Bind( wxEVT_SPINCTRL, &DIALOG_SHIM::onSpinEvent, this );
850 spin->Bind( wxEVT_TEXT, &DIALOG_SHIM::onCommandEvent, this );
851 m_currentValues[ spin ] = static_cast<long>( spin->GetValue() );
852 }
853 else if( wxSpinCtrlDouble* spinD = dynamic_cast<wxSpinCtrlDouble*>( child ) )
854 {
855 spinD->Bind( wxEVT_SPINCTRLDOUBLE, &DIALOG_SHIM::onSpinDoubleEvent, this );
856 spinD->Bind( wxEVT_TEXT, &DIALOG_SHIM::onCommandEvent, this );
857 m_currentValues[ spinD ] = spinD->GetValue();
858 }
859 else if( wxRadioButton* radio = dynamic_cast<wxRadioButton*>( child ) )
860 {
861 radio->Bind( wxEVT_RADIOBUTTON, &DIALOG_SHIM::onCommandEvent, this );
862 m_currentValues[ radio ] = radio->GetValue();
863 }
864 else if( wxRadioBox* radioBox = dynamic_cast<wxRadioBox*>( child ) )
865 {
866 radioBox->Bind( wxEVT_RADIOBOX, &DIALOG_SHIM::onCommandEvent, this );
867 m_currentValues[ radioBox ] = static_cast<long>( radioBox->GetSelection() );
868 }
869 else if( wxGrid* grid = dynamic_cast<wxGrid*>( child ) )
870 {
871 grid->Bind( wxEVT_GRID_CELL_CHANGED, &DIALOG_SHIM::onGridCellChanged, this );
873 }
874 else if( wxPropertyGrid* propGrid = dynamic_cast<wxPropertyGrid*>( child ) )
875 {
876 propGrid->Bind( wxEVT_PG_CHANGED, &DIALOG_SHIM::onPropertyGridChanged, this );
877 m_currentValues[ propGrid ] = getControlValue( propGrid );
878 }
879 else if( wxCheckListBox* checkList = dynamic_cast<wxCheckListBox*>( child ) )
880 {
881 checkList->Bind( wxEVT_CHECKLISTBOX, &DIALOG_SHIM::onCommandEvent, this );
882 m_currentValues[ checkList ] = getControlValue( checkList );
883 }
884 else if( wxDataViewListCtrl* dataList = dynamic_cast<wxDataViewListCtrl*>( child ) )
885 {
886 dataList->Bind( wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, &DIALOG_SHIM::onDataViewListChanged, this );
887 m_currentValues[ dataList ] = getControlValue( dataList );
888 }
889 else
890 {
891 registerUndoRedoHandlers( child->GetChildren() );
892 }
893 }
894}
895
896
897void DIALOG_SHIM::recordControlChange( wxWindow* aCtrl )
898{
899 wxVariant before = m_currentValues[ aCtrl ];
900 wxVariant after = getControlValue( aCtrl );
901
902 if( before != after )
903 {
904 m_undoStack.push_back( { aCtrl, before, after } );
905 m_redoStack.clear();
906 m_currentValues[ aCtrl ] = after;
907 }
908}
909
910
911void DIALOG_SHIM::onCommandEvent( wxCommandEvent& aEvent )
912{
913 if( !m_handlingUndoRedo )
914 recordControlChange( static_cast<wxWindow*>( aEvent.GetEventObject() ) );
915
916 aEvent.Skip();
917}
918
919
920void DIALOG_SHIM::onSpinEvent( wxSpinEvent& aEvent )
921{
922 if( !m_handlingUndoRedo )
923 recordControlChange( static_cast<wxWindow*>( aEvent.GetEventObject() ) );
924
925 aEvent.Skip();
926}
927
928
929void DIALOG_SHIM::onSpinDoubleEvent( wxSpinDoubleEvent& aEvent )
930{
931 if( !m_handlingUndoRedo )
932 recordControlChange( static_cast<wxWindow*>( aEvent.GetEventObject() ) );
933
934 aEvent.Skip();
935}
936
937
938void DIALOG_SHIM::onStyledTextChanged( wxStyledTextEvent& aEvent )
939{
940 if( !m_handlingUndoRedo )
941 recordControlChange( static_cast<wxWindow*>( aEvent.GetEventObject() ) );
942
943 aEvent.Skip();
944}
945
946
947void DIALOG_SHIM::onGridCellChanged( wxGridEvent& aEvent )
948{
949 if( !m_handlingUndoRedo )
950 recordControlChange( static_cast<wxWindow*>( aEvent.GetEventObject() ) );
951
952 aEvent.Skip();
953}
954
955void DIALOG_SHIM::onPropertyGridChanged( wxPropertyGridEvent& aEvent )
956{
957 if( !m_handlingUndoRedo )
958 recordControlChange( static_cast<wxWindow*>( aEvent.GetEventObject() ) );
959
960 aEvent.Skip();
961}
962
963void DIALOG_SHIM::onDataViewListChanged( wxDataViewEvent& aEvent )
964{
965 if( !m_handlingUndoRedo )
966 recordControlChange( static_cast<wxWindow*>( aEvent.GetEventObject() ) );
967
968 aEvent.Skip();
969}
970
971wxVariant DIALOG_SHIM::getControlValue( wxWindow* aCtrl )
972{
973 if( wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( aCtrl ) )
974 return wxVariant( textCtrl->GetValue() );
975 else if( wxStyledTextCtrl* scintilla = dynamic_cast<wxStyledTextCtrl*>( aCtrl ) )
976 return wxVariant( scintilla->GetText() );
977 else if( wxComboBox* combo = dynamic_cast<wxComboBox*>( aCtrl ) )
978 return wxVariant( combo->GetValue() );
979 else if( wxChoice* choice = dynamic_cast<wxChoice*>( aCtrl ) )
980 return wxVariant( (long) choice->GetSelection() );
981 else if( wxCheckBox* check = dynamic_cast<wxCheckBox*>( aCtrl ) )
982 return wxVariant( check->GetValue() );
983 else if( wxSpinCtrl* spin = dynamic_cast<wxSpinCtrl*>( aCtrl ) )
984 return wxVariant( (long) spin->GetValue() );
985 else if( wxSpinCtrlDouble* spinD = dynamic_cast<wxSpinCtrlDouble*>( aCtrl ) )
986 return wxVariant( spinD->GetValue() );
987 else if( wxRadioButton* radio = dynamic_cast<wxRadioButton*>( aCtrl ) )
988 return wxVariant( radio->GetValue() );
989 else if( wxRadioBox* radioBox = dynamic_cast<wxRadioBox*>( aCtrl ) )
990 return wxVariant( (long) radioBox->GetSelection() );
991 else if( wxGrid* grid = dynamic_cast<wxGrid*>( aCtrl ) )
992 {
993 nlohmann::json j = nlohmann::json::array();
994 int rows = grid->GetNumberRows();
995 int cols = grid->GetNumberCols();
996
997 for( int r = 0; r < rows; ++r )
998 {
999 nlohmann::json row = nlohmann::json::array();
1000
1001 for( int c = 0; c < cols; ++c )
1002 row.push_back( std::string( grid->GetCellValue( r, c ).ToUTF8() ) );
1003
1004 j.push_back( row );
1005 }
1006
1007 return wxVariant( wxString( j.dump() ) );
1008 }
1009 else if( wxPropertyGrid* propGrid = dynamic_cast<wxPropertyGrid*>( aCtrl ) )
1010 {
1011 nlohmann::json j;
1012
1013 for( wxPropertyGridIterator it = propGrid->GetIterator(); !it.AtEnd(); ++it )
1014 {
1015 wxPGProperty* prop = *it;
1016 j[ prop->GetName().ToStdString() ] = prop->GetValueAsString().ToStdString();
1017 }
1018
1019 return wxVariant( wxString( j.dump() ) );
1020 }
1021 else if( wxCheckListBox* checkList = dynamic_cast<wxCheckListBox*>( aCtrl ) )
1022 {
1023 nlohmann::json j = nlohmann::json::array();
1024 unsigned int count = checkList->GetCount();
1025
1026 for( unsigned int i = 0; i < count; ++i )
1027 {
1028 if( checkList->IsChecked( i ) )
1029 j.push_back( i );
1030 }
1031
1032 return wxVariant( wxString( j.dump() ) );
1033 }
1034 else if( wxDataViewListCtrl* dataList = dynamic_cast<wxDataViewListCtrl*>( aCtrl ) )
1035 {
1036 nlohmann::json j = nlohmann::json::array();
1037 unsigned int rows = dataList->GetItemCount();
1038 unsigned int cols = dataList->GetColumnCount();
1039
1040 for( unsigned int r = 0; r < rows; ++r )
1041 {
1042 nlohmann::json row = nlohmann::json::array();
1043
1044 for( unsigned int c = 0; c < cols; ++c )
1045 {
1046 wxVariant val;
1047 dataList->GetValue( val, r, c );
1048 row.push_back( std::string( val.GetString().ToUTF8() ) );
1049 }
1050
1051 j.push_back( row );
1052 }
1053
1054 return wxVariant( wxString( j.dump() ) );
1055 }
1056 else
1057 return wxVariant();
1058}
1059
1060
1061void DIALOG_SHIM::setControlValue( wxWindow* aCtrl, const wxVariant& aValue )
1062{
1063 if( wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( aCtrl ) )
1064 textCtrl->SetValue( aValue.GetString() );
1065 else if( wxStyledTextCtrl* scintilla = dynamic_cast<wxStyledTextCtrl*>( aCtrl ) )
1066 scintilla->SetText( aValue.GetString() );
1067 else if( wxComboBox* combo = dynamic_cast<wxComboBox*>( aCtrl ) )
1068 combo->SetValue( aValue.GetString() );
1069 else if( wxChoice* choice = dynamic_cast<wxChoice*>( aCtrl ) )
1070 choice->SetSelection( (int) aValue.GetLong() );
1071 else if( wxCheckBox* check = dynamic_cast<wxCheckBox*>( aCtrl ) )
1072 check->SetValue( aValue.GetBool() );
1073 else if( wxSpinCtrl* spin = dynamic_cast<wxSpinCtrl*>( aCtrl ) )
1074 spin->SetValue( (int) aValue.GetLong() );
1075 else if( wxSpinCtrlDouble* spinD = dynamic_cast<wxSpinCtrlDouble*>( aCtrl ) )
1076 spinD->SetValue( aValue.GetDouble() );
1077 else if( wxRadioButton* radio = dynamic_cast<wxRadioButton*>( aCtrl ) )
1078 radio->SetValue( aValue.GetBool() );
1079 else if( wxRadioBox* radioBox = dynamic_cast<wxRadioBox*>( aCtrl ) )
1080 radioBox->SetSelection( (int) aValue.GetLong() );
1081 else if( wxGrid* grid = dynamic_cast<wxGrid*>( aCtrl ) )
1082 {
1083 nlohmann::json j = nlohmann::json::parse( aValue.GetString().ToStdString(), nullptr, false );
1084
1085 if( j.is_array() )
1086 {
1087 int rows = std::min( (int) j.size(), grid->GetNumberRows() );
1088
1089 for( int r = 0; r < rows; ++r )
1090 {
1091 nlohmann::json row = j[r];
1092 int cols = std::min( (int) row.size(), grid->GetNumberCols() );
1093
1094 for( int c = 0; c < cols; ++c )
1095 grid->SetCellValue( r, c, wxString( row[c].get<std::string>() ) );
1096 }
1097 }
1098 }
1099 else if( wxPropertyGrid* propGrid = dynamic_cast<wxPropertyGrid*>( aCtrl ) )
1100 {
1101 nlohmann::json j = nlohmann::json::parse( aValue.GetString().ToStdString(), nullptr, false );
1102
1103 if( j.is_object() )
1104 {
1105 for( auto it = j.begin(); it != j.end(); ++it )
1106 propGrid->SetPropertyValue( wxString( it.key() ), wxString( it.value().get<std::string>() ) );
1107 }
1108 }
1109 else if( wxCheckListBox* checkList = dynamic_cast<wxCheckListBox*>( aCtrl ) )
1110 {
1111 nlohmann::json j = nlohmann::json::parse( aValue.GetString().ToStdString(), nullptr, false );
1112
1113 if( j.is_array() )
1114 {
1115 unsigned int count = checkList->GetCount();
1116
1117 for( unsigned int i = 0; i < count; ++i )
1118 checkList->Check( i, false );
1119
1120 for( auto& idx : j )
1121 {
1122 unsigned int i = idx.get<unsigned int>();
1123
1124 if( i < count )
1125 checkList->Check( i, true );
1126 }
1127 }
1128 }
1129 else if( wxDataViewListCtrl* dataList = dynamic_cast<wxDataViewListCtrl*>( aCtrl ) )
1130 {
1131 nlohmann::json j = nlohmann::json::parse( aValue.GetString().ToStdString(), nullptr, false );
1132
1133 if( j.is_array() )
1134 {
1135 unsigned int rows = std::min( static_cast<unsigned int>( j.size() ),
1136 static_cast<unsigned int>( dataList->GetItemCount() ) );
1137
1138 for( unsigned int r = 0; r < rows; ++r )
1139 {
1140 nlohmann::json row = j[r];
1141 unsigned int cols = std::min( (unsigned int) row.size(), dataList->GetColumnCount() );
1142
1143 for( unsigned int c = 0; c < cols; ++c )
1144 {
1145 wxVariant val( wxString( row[c].get<std::string>() ) );
1146 dataList->SetValue( val, r, c );
1147 }
1148 }
1149 }
1150 }
1151}
1152
1153
1155{
1156 if( m_undoStack.empty() )
1157 return;
1158
1159 m_handlingUndoRedo = true;
1160 UNDO_STEP step = m_undoStack.back();
1161 m_undoStack.pop_back();
1162 setControlValue( step.ctrl, step.before );
1163 m_currentValues[ step.ctrl ] = step.before;
1164 m_redoStack.push_back( step );
1165 m_handlingUndoRedo = false;
1166}
1167
1168
1170{
1171 if( m_redoStack.empty() )
1172 return;
1173
1174 m_handlingUndoRedo = true;
1175 UNDO_STEP step = m_redoStack.back();
1176 m_redoStack.pop_back();
1177 setControlValue( step.ctrl, step.after );
1178 m_currentValues[ step.ctrl ] = step.after;
1179 m_undoStack.push_back( step );
1180 m_handlingUndoRedo = false;
1181}
1182
1183
1184void DIALOG_SHIM::OnPaint( wxPaintEvent &event )
1185{
1186 if( m_firstPaintEvent )
1187 {
1189
1190 SelectAllInTextCtrls( GetChildren() );
1191 registerUndoRedoHandlers( GetChildren() );
1192
1195 else
1196 KIPLATFORM::UI::ForceFocus( this ); // Focus the dialog itself
1197
1198 m_firstPaintEvent = false;
1199 }
1200
1201 event.Skip();
1202}
1203
1204
1206{
1207 if( !GetTitle().StartsWith( wxS( "*" ) ) )
1208 SetTitle( wxS( "*" ) + GetTitle() );
1209}
1210
1211
1213{
1214 if( GetTitle().StartsWith( wxS( "*" ) ) )
1215 SetTitle( GetTitle().AfterFirst( '*' ) );
1216}
1217
1219{
1220 // Apple in its infinite wisdom will raise a disabled window before even passing
1221 // us the event, so we have no way to stop it. Instead, we must set an order on
1222 // the windows so that the modal will be pushed in front of the disabled
1223 // window when it is raised.
1225
1226 // Call the base class ShowModal() method
1227 return wxDialog::ShowModal();
1228}
1229
1230/*
1231 QuasiModal Mode Explained:
1232
1233 The gtk calls in wxDialog::ShowModal() cause event routing problems if that
1234 modal dialog then tries to use KIWAY_PLAYER::ShowModal(). The latter shows up
1235 and mostly works but does not respond to the window decoration close button.
1236 There is no way to get around this without reversing the gtk calls temporarily.
1237
1238 There are also issues with the Scintilla text editor putting up autocomplete
1239 popups, which appear behind the dialog window if QuasiModal is not used.
1240
1241 QuasiModal mode is our own almost modal mode which disables only the parent
1242 of the DIALOG_SHIM, leaving other frames operable and while staying captured in the
1243 nested event loop. This avoids the gtk calls and leaves event routing pure
1244 and sufficient to operate the KIWAY_PLAYER::ShowModal() properly. When using
1245 ShowQuasiModal() you have to use EndQuasiModal() in your dialogs and not
1246 EndModal(). There is also IsQuasiModal() but its value can only be true
1247 when the nested event loop is active. Do not mix the modal and quasi-modal
1248 functions. Use one set or the other.
1249
1250 You might find this behavior preferable over a pure modal mode, and it was said
1251 that only the Mac has this natively, but now other platforms have something
1252 similar. You CAN use it anywhere for any dialog. But you MUST use it when
1253 you want to use KIWAY_PLAYER::ShowModal() from a dialog event.
1254*/
1255
1257{
1258 NULLER raii_nuller( (void*&) m_qmodal_loop );
1259
1260 // release the mouse if it's currently captured as the window having it
1261 // will be disabled when this dialog is shown -- but will still keep the
1262 // capture making it impossible to do anything in the modal dialog itself
1263 wxWindow* win = wxWindow::GetCapture();
1264 if( win )
1265 win->ReleaseMouse();
1266
1267 // Get the optimal parent
1268 wxWindow* parent = GetParentForModalDialog( GetParent(), GetWindowStyle() );
1269
1270 wxASSERT_MSG( !m_qmodal_parent_disabler, wxT( "Caller using ShowQuasiModal() twice on same window?" ) );
1271
1272 // quasi-modal: disable only my "optimal" parent
1274
1275 // Apple in its infinite wisdom will raise a disabled window before even passing
1276 // us the event, so we have no way to stop it. Instead, we must set an order on
1277 // the windows so that the quasi-modal will be pushed in front of the disabled
1278 // window when it is raised.
1280
1281 Show( true );
1282
1283 m_qmodal_showing = true;
1284
1285 wxGUIEventLoop event_loop;
1286
1287 m_qmodal_loop = &event_loop;
1288
1289 event_loop.Run();
1290
1291 m_qmodal_showing = false;
1292
1293 if( parent )
1294 parent->SetFocus();
1295
1296 return GetReturnCode();
1297}
1298
1299
1301{
1303 m_qmodal_parent_disabler->SuspendForTrueModal();
1304}
1305
1306
1308{
1310 m_qmodal_parent_disabler->ResumeAfterTrueModal();
1311}
1312
1313
1315{
1316 // Hook up validator and transfer data from controls handling so quasi-modal dialogs
1317 // handle validation in the same way as other dialogs.
1318 if( ( retCode == wxID_OK ) && ( !Validate() || !TransferDataFromWindow() ) )
1319 return;
1320
1321 SetReturnCode( retCode );
1322
1323 if( !IsQuasiModal() )
1324 {
1325 wxFAIL_MSG( wxT( "Either DIALOG_SHIM::EndQuasiModal was called twice, or ShowQuasiModal"
1326 "wasn't called" ) );
1327 return;
1328 }
1329
1331
1332 if( m_qmodal_loop )
1333 {
1334 if( m_qmodal_loop->IsRunning() )
1335 m_qmodal_loop->Exit( 0 );
1336 else
1337 m_qmodal_loop->ScheduleExit( 0 );
1338 }
1339
1341 m_qmodal_parent_disabler = nullptr;
1342
1343 Show( false );
1344}
1345
1346
1347void DIALOG_SHIM::OnCloseWindow( wxCloseEvent& aEvent )
1348{
1349 wxString msg = wxString::Format( "Closing dialog %s", GetTitle() );
1350 APP_MONITOR::AddNavigationBreadcrumb( msg, "dialog.close" );
1351
1352 if( IsQuasiModal() )
1353 {
1354 EndQuasiModal( wxID_CANCEL );
1355 return;
1356 }
1357
1358 // This is mandatory to allow wxDialogBase::OnCloseWindow() to be called.
1359 aEvent.Skip();
1360}
1361
1362
1363void DIALOG_SHIM::OnButton( wxCommandEvent& aEvent )
1364{
1365 const int id = aEvent.GetId();
1366
1367 if( IsQuasiModal() )
1368 {
1369 if( id == GetAffirmativeId() )
1370 {
1371 EndQuasiModal( id );
1372 }
1373 else if( id == wxID_APPLY )
1374 {
1375 // Dialogs that provide Apply buttons should make sure data is valid before
1376 // allowing a transfer, as there is no other way to indicate failure
1377 // (i.e. the dialog can't refuse to close as it might with OK, because it
1378 // isn't closing anyway)
1379 if( Validate() )
1380 {
1381 ignore_unused( TransferDataFromWindow() );
1382 }
1383 }
1384 else if( id == wxID_CANCEL )
1385 {
1386 EndQuasiModal( wxID_CANCEL );
1387 }
1388 else // not a standard button
1389 {
1390 aEvent.Skip();
1391 }
1392
1393 return;
1394 }
1395
1396 // This is mandatory to allow wxDialogBase::OnButton() to be called.
1397 aEvent.Skip();
1398}
1399
1400
1401void DIALOG_SHIM::onChildSetFocus( wxFocusEvent& aEvent )
1402{
1403 // When setting focus to a text control reset the before-edit value.
1404
1405 if( !m_isClosing )
1406 {
1407 if( wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( aEvent.GetEventObject() ) )
1408 m_beforeEditValues[ textCtrl ] = textCtrl->GetValue();
1409 else if( wxStyledTextCtrl* scintilla = dynamic_cast<wxStyledTextCtrl*>( aEvent.GetEventObject() ) )
1410 m_beforeEditValues[ scintilla ] = scintilla->GetText();
1411 }
1412
1413 aEvent.Skip();
1414}
1415
1416
1417void DIALOG_SHIM::OnCharHook( wxKeyEvent& aEvt )
1418{
1419 int key = aEvt.GetKeyCode();
1420 int mods = 0;
1421
1422 if( aEvt.ControlDown() )
1423 mods |= MD_CTRL;
1424 if( aEvt.ShiftDown() )
1425 mods |= MD_SHIFT;
1426 if( aEvt.AltDown() )
1427 mods |= MD_ALT;
1428
1429 int hotkey = key | mods;
1430
1431 // Check for standard undo/redo hotkeys
1432 if( hotkey == (MD_CTRL + 'Z') )
1433 {
1434 doUndo();
1435 return;
1436 }
1437 else if( hotkey == (MD_CTRL + MD_SHIFT + 'Z') || hotkey == (MD_CTRL + 'Y') )
1438 {
1439 doRedo();
1440 return;
1441 }
1442
1443 if( aEvt.GetKeyCode() == 'U' && aEvt.GetModifiers() == wxMOD_CONTROL )
1444 {
1445 if( m_parentFrame )
1446 {
1447 m_parentFrame->ToggleUserUnits();
1448 return;
1449 }
1450 }
1451 // shift-return (Mac default) or Ctrl-Return (GTK) for new line input
1452 else if( ( aEvt.GetKeyCode() == WXK_RETURN || aEvt.GetKeyCode() == WXK_NUMPAD_ENTER ) && aEvt.ShiftDown() )
1453 {
1454 wxObject* eventSource = aEvt.GetEventObject();
1455
1456 if( wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( eventSource ) )
1457 {
1458 // If the text control is not multi-line, we want to close the dialog
1459 if( !textCtrl->IsMultiLine() )
1460 {
1461 wxPostEvent( this, wxCommandEvent( wxEVT_COMMAND_BUTTON_CLICKED, wxID_OK ) );
1462 return;
1463 }
1464
1465#if defined( __WXMAC__ ) || defined( __WXMSW__ )
1466 wxString eol = "\r\n";
1467#else
1468 wxString eol = "\n";
1469#endif
1470
1471 long pos = textCtrl->GetInsertionPoint();
1472 textCtrl->WriteText( eol );
1473 textCtrl->SetInsertionPoint( pos + eol.length() );
1474 return;
1475 }
1476 else if( wxStyledTextCtrl* scintilla = dynamic_cast<wxStyledTextCtrl*>( eventSource ) )
1477 {
1478 wxString eol = "\n";
1479 switch( scintilla->GetEOLMode() )
1480 {
1481 case wxSTC_EOL_CRLF: eol = "\r\n"; break;
1482 case wxSTC_EOL_CR: eol = "\r"; break;
1483 case wxSTC_EOL_LF: eol = "\n"; break;
1484 }
1485
1486 long pos = scintilla->GetCurrentPos();
1487 scintilla->InsertText( pos, eol );
1488 scintilla->GotoPos( pos + eol.length() );
1489 return;
1490 }
1491 return;
1492 }
1493 // command-return (Mac default) or Ctrl-Return (GTK) for OK
1494 else if( ( aEvt.GetKeyCode() == WXK_RETURN || aEvt.GetKeyCode() == WXK_NUMPAD_ENTER ) && aEvt.ControlDown() )
1495 {
1496 wxPostEvent( this, wxCommandEvent( wxEVT_COMMAND_BUTTON_CLICKED, wxID_OK ) );
1497 return;
1498 }
1499 else if( aEvt.GetKeyCode() == WXK_TAB && !aEvt.ControlDown() )
1500 {
1501 wxWindow* currentWindow = wxWindow::FindFocus();
1502 int currentIdx = -1;
1503 int delta = aEvt.ShiftDown() ? -1 : 1;
1504
1505 auto advance =
1506 [&]( int& idx )
1507 {
1508 // Wrap-around modulus
1509 int size = (int) m_tabOrder.size();
1510 idx = ( ( idx + delta ) % size + size ) % size;
1511 };
1512
1513 for( size_t i = 0; i < m_tabOrder.size(); ++i )
1514 {
1515 if( m_tabOrder[i] == currentWindow )
1516 {
1517 currentIdx = (int) i;
1518 break;
1519 }
1520 }
1521
1522 if( currentIdx >= 0 )
1523 {
1524 advance( currentIdx );
1525
1526 //todo: We don't currently have non-textentry dialog boxes but this will break if
1527 // we add them.
1528#ifdef __APPLE__
1529 while( dynamic_cast<wxTextEntry*>( m_tabOrder[ currentIdx ] ) == nullptr )
1530 advance( currentIdx );
1531#endif
1532
1533 m_tabOrder[ currentIdx ]->SetFocus();
1534 return;
1535 }
1536 }
1537 else if( aEvt.GetKeyCode() == WXK_ESCAPE )
1538 {
1539 wxObject* eventSource = aEvt.GetEventObject();
1540
1541 if( wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( eventSource ) )
1542 {
1543 // First escape after an edit cancels edit
1544 if( textCtrl->GetValue() != m_beforeEditValues[ textCtrl ] )
1545 {
1546 textCtrl->SetValue( m_beforeEditValues[ textCtrl ] );
1547 textCtrl->SelectAll();
1548 return;
1549 }
1550 }
1551 else if( wxStyledTextCtrl* scintilla = dynamic_cast<wxStyledTextCtrl*>( eventSource ) )
1552 {
1553 // First escape after an edit cancels edit
1554 if( scintilla->GetText() != m_beforeEditValues[ scintilla ] )
1555 {
1556 scintilla->SetText( m_beforeEditValues[ scintilla ] );
1557 scintilla->SelectAll();
1558 return;
1559 }
1560 }
1561 }
1562
1563 aEvt.Skip();
1564}
1565
1566
1567static void recursiveDescent( wxSizer* aSizer, std::map<int, wxString>& aLabels )
1568{
1569 wxStdDialogButtonSizer* sdbSizer = dynamic_cast<wxStdDialogButtonSizer*>( aSizer );
1570
1571 auto setupButton =
1572 [&]( wxButton* aButton )
1573 {
1574 if( aLabels.count( aButton->GetId() ) > 0 )
1575 {
1576 aButton->SetLabel( aLabels[ aButton->GetId() ] );
1577 }
1578 else
1579 {
1580 // wxWidgets has an uneven track record when the language is changed on
1581 // the fly so we set them even when they don't appear in the label map
1582 switch( aButton->GetId() )
1583 {
1584 case wxID_OK: aButton->SetLabel( _( "&OK" ) ); break;
1585 case wxID_CANCEL: aButton->SetLabel( _( "&Cancel" ) ); break;
1586 case wxID_YES: aButton->SetLabel( _( "&Yes" ) ); break;
1587 case wxID_NO: aButton->SetLabel( _( "&No" ) ); break;
1588 case wxID_APPLY: aButton->SetLabel( _( "&Apply" ) ); break;
1589 case wxID_SAVE: aButton->SetLabel( _( "&Save" ) ); break;
1590 case wxID_HELP: aButton->SetLabel( _( "&Help" ) ); break;
1591 case wxID_CONTEXT_HELP: aButton->SetLabel( _( "&Help" ) ); break;
1592 }
1593 }
1594 };
1595
1596 if( sdbSizer )
1597 {
1598 if( sdbSizer->GetAffirmativeButton() )
1599 setupButton( sdbSizer->GetAffirmativeButton() );
1600
1601 if( sdbSizer->GetApplyButton() )
1602 setupButton( sdbSizer->GetApplyButton() );
1603
1604 if( sdbSizer->GetNegativeButton() )
1605 setupButton( sdbSizer->GetNegativeButton() );
1606
1607 if( sdbSizer->GetCancelButton() )
1608 setupButton( sdbSizer->GetCancelButton() );
1609
1610 if( sdbSizer->GetHelpButton() )
1611 setupButton( sdbSizer->GetHelpButton() );
1612
1613 sdbSizer->Layout();
1614
1615 if( sdbSizer->GetAffirmativeButton() )
1616 sdbSizer->GetAffirmativeButton()->SetDefault();
1617 }
1618
1619 for( wxSizerItem* item : aSizer->GetChildren() )
1620 {
1621 if( item->GetSizer() )
1622 recursiveDescent( item->GetSizer(), aLabels );
1623 }
1624}
1625
1626
1627void DIALOG_SHIM::SetupStandardButtons( std::map<int, wxString> aLabels )
1628{
1629 recursiveDescent( GetSizer(), aLabels );
1630}
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
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:686
Definition raii.h:38
virtual COMMON_SETTINGS * GetCommonSettings() const
Definition pgm_base.cpp:537
virtual wxApp & App()
Return a bare naked wxApp which may come from wxPython, SINGLE_TOP, or kicad.exe.
Definition pgm_base.cpp:192
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:946
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