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