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