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