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