1 /**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 ShuttleGui.cpp
6
7 James Crook
8
9 Audacity is free software.
10 This file is licensed under the wxWidgets license, see License.txt
11
12 **********************************************************************//**
13
14 \file ShuttleGui.cpp
15 \brief Implements ShuttleGui, ShuttleGuiBase and InvisiblePanel.
16
17 *//***************************************************************//**
18
19 \class ShuttleGui
20 \brief
21 Derived from ShuttleGuiBase, an Audacity specific class for shuttling
22 data to and from GUI.
23
24 ShuttleGui extends the idea of the data Shuttle class to include creation
25 of dialog controls. As part of this it provides an interface to sizers
26 that leads to shorter more readable code.
27
28 It also allows the code that is used to create dialogs to be reused
29 to shuttle information in and out.
30
31 Most of the ShuttleGui functions are actually defined in
32 ShuttleGuiBase.
33 - wxWidgets widgets are dealt with by ShuttleGuiBase.
34 - Audacity specific widgets are dealt with by ShuttleGui
35
36 There is documentation on how to use this class in \ref ShuttleSystem
37
38 *//***************************************************************//**
39
40 \class ShuttleGuiBase
41 \brief Base class for shuttling data to and from a GUI.
42
43 see also ShuttleGui
44
45 Use the:
46 - \p Start / \p End methods for containers, like two-column-layout.
47 - \p Add methods if you are only interested in creating the controls.
48 - \p Tie methods if you also want to exchange data using ShuttleGui.
49
50 The code in this file is fairly repetitive. We are dealing with
51 - Many different types of Widget.
52 - Creation / Reading / Writing / Exporting / Importing
53 - int, float, string variants (for example of TextCtrl contents).
54
55 A technique used to reduce the size of the \p Tie functions is to
56 have one generic \p Tie function that uses WrappedType for its
57 data type. Type specific \p Tie functions themselves call the generic
58 variant.
59
60 A second technique used to reduce the size of \p Tie functions
61 only comes into play for two-step \p Tie functions. (A two step
62 \p Tie function is one that transfers data between the registry
63 and the GUI via an intermediate temporary variable). In the two
64 step style, a function ShuttleGuiBase::DoStep() determines which
65 transfers in the function are to be done, reducing repetitive
66 if-then-else's.
67
68 Although unusual, these two techniques make the code easier to
69 add to and much easier to check for correctness. The alternative
70 'more obvious' code that just repeats code as needed is
71 considerably longer.
72
73 You would rarely use ShuttleGuiBase directly, instead you'd use
74 ShuttleGui.
75
76 There is DOxygen documentation on how to use the ShuttleGui
77 class in \ref ShuttleSystem .
78
79 *//***************************************************************//**
80
81 \class InvisiblePanel
82 \brief An InvisiblePanel is a panel which does not repaint its
83 own background.
84
85 It is used (a) To group together widgets which need to be refreshed
86 together. A single refresh of the panel causes all the subwindows to
87 refresh. (b) as a base class for some flicker-free classes for which
88 the background is never repainted.
89
90 JKC: InvisiblePanel will probably be replaced in time by a mechanism
91 for registering for changes.
92
93 *//******************************************************************/
94
95
96
97 #include "ShuttleGui.h"
98
99
100
101 #include "Prefs.h"
102 #include "ShuttlePrefs.h"
103 #include "Theme.h"
104
105 #include <wx/setup.h> // for wxUSE_* macros
106 #include <wx/wx.h>
107 #include <wx/wxprec.h>
108 #include <wx/grid.h>
109 #include <wx/listctrl.h>
110 #include <wx/notebook.h>
111 #include <wx/simplebook.h>
112 #include <wx/treectrl.h>
113 #include <wx/spinctrl.h>
114 #include <wx/stattext.h>
115 #include <wx/bmpbuttn.h>
116 #include <wx/wrapsizer.h>
117
118 #include "ComponentInterface.h"
119 #include "widgets/ReadOnlyText.h"
120 #include "widgets/wxPanelWrapper.h"
121 #include "widgets/wxTextCtrlWrapper.h"
122 #include "AllThemeResources.h"
123
124 #if wxUSE_ACCESSIBILITY
125 #include "widgets/WindowAccessible.h"
126 #endif
127
ShuttleGuiBase(wxWindow * pParent,teShuttleMode ShuttleMode,bool vertical,wxSize minSize)128 ShuttleGuiBase::ShuttleGuiBase(
129 wxWindow * pParent, teShuttleMode ShuttleMode, bool vertical, wxSize minSize )
130 : mpDlg{ pParent }
131 {
132 wxASSERT( (pParent != NULL ) || ( ShuttleMode != eIsCreating));
133 mpbOptionalFlag = nullptr;
134 mpParent = pParent;
135 mShuttleMode = ShuttleMode;
136 Init( vertical, minSize );
137 }
138
~ShuttleGuiBase()139 ShuttleGuiBase::~ShuttleGuiBase()
140 {
141 }
142
Init(bool vertical,wxSize minSize)143 void ShuttleGuiBase::Init(bool vertical, wxSize minSize)
144 {
145 mpShuttle = NULL;
146 mpSizer = NULL;
147 mpWind = NULL;
148 mpSubSizer = NULL;
149
150 mRadioSettingName = wxT("");
151 mRadioCount = -1;
152
153 miBorder = 5;
154 miProp=0;
155 miPropSetByUser=-1;
156 miSizerProp=0;
157 mSizerDepth=-1;
158
159 ResetId();
160
161 miNoMatchSelector = 0;
162
163 if( mShuttleMode != eIsCreating )
164 return;
165
166 mpSizer = mpParent->GetSizer();
167
168 #if 0
169 if( mpSizer == NULL )
170 {
171 wxWindow * pGrandParent = mpParent->GetParent();
172 if( pGrandParent )
173 {
174 mpSizer = pGrandParent->GetSizer();
175 }
176 }
177 #endif
178
179 if( !mpSizer )
180 {
181 mpParent->SetSizer(
182 mpSizer = safenew wxBoxSizer(vertical ? wxVERTICAL : wxHORIZONTAL));
183 }
184 PushSizer();
185 mpSizer->SetMinSize(minSize);
186 }
187
ResetId()188 void ShuttleGuiBase::ResetId()
189 {
190 miIdSetByUser = -1;
191 miId = -1;
192 miIdNext = 3000;
193 }
194
195
GetBorder() const196 int ShuttleGuiBase::GetBorder() const noexcept
197 {
198 return miBorder;
199 }
200
201 /// Used to modify an already placed FlexGridSizer to make a column stretchy.
SetStretchyCol(int i)202 void ShuttleGuiBase::SetStretchyCol( int i )
203 {
204 if( mShuttleMode != eIsCreating )
205 return;
206 wxFlexGridSizer *pSizer = wxDynamicCast(mpSizer, wxFlexGridSizer);
207 wxASSERT( pSizer );
208 pSizer->AddGrowableCol( i, 1 );
209 }
210
211 /// Used to modify an already placed FlexGridSizer to make a row stretchy.
SetStretchyRow(int i)212 void ShuttleGuiBase::SetStretchyRow( int i )
213 {
214 if( mShuttleMode != eIsCreating )
215 return;
216 wxFlexGridSizer *pSizer = wxDynamicCast(mpSizer, wxFlexGridSizer);
217 wxASSERT( pSizer );
218 pSizer->AddGrowableRow( i, 1 );
219 }
220
221
222 //---- Add Functions.
223
HandleOptionality(const TranslatableString & Prompt)224 void ShuttleGuiBase::HandleOptionality(const TranslatableString &Prompt)
225 {
226 // If creating, will be handled by an AddPrompt.
227 if( mShuttleMode == eIsCreating )
228 return;
229 //wxLogDebug( "Optionality: [%s] Id:%i (%i)", Prompt.c_str(), miId, miIdSetByUser ) ;
230 if( mpbOptionalFlag ){
231 bool * pVar = mpbOptionalFlag;
232 mpbOptionalFlag = nullptr;
233 TieCheckBox( Prompt, *pVar);
234 }
235 }
236
237 /// Right aligned text string.
AddPrompt(const TranslatableString & Prompt,int wrapWidth)238 void ShuttleGuiBase::AddPrompt(const TranslatableString &Prompt, int wrapWidth)
239 {
240 if( mShuttleMode != eIsCreating )
241 return;
242 //wxLogDebug( "Prompt: [%s] Id:%i (%i)", Prompt.c_str(), miId, miIdSetByUser ) ;
243 if( mpbOptionalFlag ){
244 bool * pVar = mpbOptionalFlag;
245 mpbOptionalFlag = nullptr;
246 TieCheckBox( {}, *pVar);
247 //return;
248 }
249 if( Prompt.empty() )
250 return;
251 miProp=1;
252 const auto translated = Prompt.Translation();
253 auto text = safenew wxStaticText(GetParent(), -1, translated, wxDefaultPosition, wxDefaultSize,
254 GetStyle( wxALIGN_RIGHT ));
255 mpWind = text;
256 if (wrapWidth > 0)
257 text->Wrap(wrapWidth);
258 mpWind->SetName(wxStripMenuCodes(translated)); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs)
259 UpdateSizersCore( false, wxALL | wxALIGN_CENTRE_VERTICAL, true );
260 }
261
262 /// Left aligned text string.
AddUnits(const TranslatableString & Prompt,int wrapWidth)263 void ShuttleGuiBase::AddUnits(const TranslatableString &Prompt, int wrapWidth)
264 {
265 if( Prompt.empty() )
266 return;
267 if( mShuttleMode != eIsCreating )
268 return;
269 miProp = 1;
270 const auto translated = Prompt.Translation();
271 auto text = safenew wxStaticText(GetParent(), -1, translated, wxDefaultPosition, wxDefaultSize,
272 GetStyle( wxALIGN_LEFT ));
273 mpWind = text;
274 if (wrapWidth > 0)
275 text->Wrap(wrapWidth);
276 mpWind->SetName(translated); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs)
277 UpdateSizersCore( false, wxALL | wxALIGN_CENTRE_VERTICAL );
278 }
279
280 /// Centred text string.
AddTitle(const TranslatableString & Prompt,int wrapWidth)281 void ShuttleGuiBase::AddTitle(const TranslatableString &Prompt, int wrapWidth)
282 {
283 if( Prompt.empty() )
284 return;
285 if( mShuttleMode != eIsCreating )
286 return;
287 const auto translated = Prompt.Translation();
288 auto text = safenew wxStaticText(GetParent(), -1, translated, wxDefaultPosition, wxDefaultSize,
289 GetStyle( wxALIGN_CENTRE ));
290 mpWind = text;
291 if (wrapWidth > 0)
292 text->Wrap(wrapWidth);
293 mpWind->SetName(translated); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs)
294 UpdateSizers();
295 }
296
297 /// Very generic 'Add' function. We can add anything we like.
298 /// Useful for unique controls
AddWindow(wxWindow * pWindow,int PositionFlags)299 wxWindow* ShuttleGuiBase::AddWindow(wxWindow* pWindow, int PositionFlags)
300 {
301 if( mShuttleMode != eIsCreating )
302 return pWindow;
303 mpWind = pWindow;
304 SetProportions( 0 );
305 UpdateSizersCore(false, PositionFlags | wxALL);
306 return pWindow;
307 }
308
AddCheckBox(const TranslatableString & Prompt,bool Selected)309 wxCheckBox * ShuttleGuiBase::AddCheckBox( const TranslatableString &Prompt, bool Selected)
310 {
311 HandleOptionality( Prompt );
312 auto realPrompt = Prompt.Translation();
313 if( mpbOptionalFlag )
314 {
315 AddPrompt( {} );
316 //realPrompt = wxT("");
317 }
318
319 UseUpId();
320 if( mShuttleMode != eIsCreating )
321 return wxDynamicCast(wxWindow::FindWindowById( miId, mpDlg), wxCheckBox);
322 wxCheckBox * pCheckBox;
323 miProp=0;
324 mpWind = pCheckBox = safenew wxCheckBox(GetParent(), miId, realPrompt, wxDefaultPosition, wxDefaultSize,
325 GetStyle( 0 ));
326 pCheckBox->SetValue(Selected);
327 if (realPrompt.empty()) {
328 // NVDA 2018.3 does not read controls which are buttons, check boxes or radio buttons which have
329 // an accessibility name which is empty. Bug 1980.
330 #if wxUSE_ACCESSIBILITY
331 // so that name can be set on a standard control
332 pCheckBox->SetAccessible(safenew WindowAccessible(pCheckBox));
333 #endif
334 pCheckBox->SetName(wxT("\a")); // non-empty string which screen readers do not read
335 }
336 UpdateSizers();
337 return pCheckBox;
338 }
339
340 /// For a consistent two-column layout we want labels on the left and
341 /// controls on the right. CheckBoxes break that rule, so we fake it by
342 /// placing a static text label and then a tick box with an empty label.
AddCheckBoxOnRight(const TranslatableString & Prompt,bool Selected)343 wxCheckBox * ShuttleGuiBase::AddCheckBoxOnRight( const TranslatableString &Prompt, bool Selected)
344 {
345 HandleOptionality( Prompt );
346 AddPrompt( Prompt );
347 UseUpId();
348 if( mShuttleMode != eIsCreating )
349 return wxDynamicCast(wxWindow::FindWindowById( miId, mpDlg), wxCheckBox);
350 wxCheckBox * pCheckBox;
351 miProp=0;
352 mpWind = pCheckBox = safenew wxCheckBox(GetParent(), miId, wxT(""), wxDefaultPosition, wxDefaultSize,
353 GetStyle( 0 ));
354 pCheckBox->SetValue(Selected);
355 pCheckBox->SetName(Prompt.Stripped().Translation());
356 UpdateSizers();
357 return pCheckBox;
358 }
359
AddButton(const TranslatableString & Text,int PositionFlags,bool setDefault)360 wxButton * ShuttleGuiBase::AddButton(
361 const TranslatableString &Text, int PositionFlags, bool setDefault)
362 {
363 UseUpId();
364 if( mShuttleMode != eIsCreating )
365 return wxDynamicCast(wxWindow::FindWindowById( miId, mpDlg), wxButton);
366 wxButton * pBtn;
367 const auto translated = Text.Translation();
368 mpWind = pBtn = safenew wxButton(GetParent(), miId,
369 translated, wxDefaultPosition, wxDefaultSize,
370 GetStyle( 0 ) );
371 mpWind->SetName(wxStripMenuCodes(translated));
372 miProp=0;
373 UpdateSizersCore(false, PositionFlags | wxALL);
374 if (setDefault)
375 pBtn->SetDefault();
376 return pBtn;
377 }
378
AddBitmapButton(const wxBitmap & Bitmap,int PositionFlags,bool setDefault)379 wxBitmapButton * ShuttleGuiBase::AddBitmapButton(
380 const wxBitmap &Bitmap, int PositionFlags, bool setDefault)
381 {
382 UseUpId();
383 if( mShuttleMode != eIsCreating )
384 return wxDynamicCast(wxWindow::FindWindowById( miId, mpDlg), wxBitmapButton);
385 wxBitmapButton * pBtn;
386 mpWind = pBtn = safenew wxBitmapButton(GetParent(), miId, Bitmap,
387 wxDefaultPosition, wxDefaultSize, GetStyle( wxBU_AUTODRAW ) );
388 pBtn->SetBackgroundColour(
389 wxColour( 246,246,243));
390 // wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE));
391 miProp=0;
392 UpdateSizersCore(false, PositionFlags | wxALL);
393 if (setDefault)
394 pBtn->SetDefault();
395 return pBtn;
396 }
397
AddChoice(const TranslatableString & Prompt,const TranslatableStrings & choices,int Selected)398 wxChoice * ShuttleGuiBase::AddChoice( const TranslatableString &Prompt,
399 const TranslatableStrings &choices, int Selected )
400 {
401 HandleOptionality( Prompt );
402 AddPrompt( Prompt );
403 UseUpId();
404 if( mShuttleMode != eIsCreating )
405 return wxDynamicCast(wxWindow::FindWindowById( miId, mpDlg), wxChoice);
406 wxChoice * pChoice;
407 miProp=0;
408
409 mpWind = pChoice = safenew wxChoice(
410 GetParent(),
411 miId,
412 wxDefaultPosition,
413 wxDefaultSize,
414 transform_container<wxArrayString>(
415 choices, std::mem_fn( &TranslatableString::StrippedTranslation ) ),
416 GetStyle( 0 ) );
417
418 pChoice->SetMinSize( { 180, -1 } );// Use -1 for 'default size' - Platform specific.
419 #ifdef __WXMAC__
420 #if wxUSE_ACCESSIBILITY
421 // so that name can be set on a standard control
422 mpWind->SetAccessible(safenew WindowAccessible(mpWind));
423 #endif
424 #endif
425 pChoice->SetName(Prompt.Stripped().Translation());
426 if ( Selected >= 0 && Selected < (int)choices.size() )
427 pChoice->SetSelection( Selected );
428
429 UpdateSizers();
430 return pChoice;
431 }
432
AddChoice(const TranslatableString & Prompt,const TranslatableStrings & choices,const TranslatableString & Selected)433 wxChoice * ShuttleGuiBase::AddChoice( const TranslatableString &Prompt,
434 const TranslatableStrings &choices, const TranslatableString &Selected )
435 {
436 return AddChoice(
437 Prompt, choices, make_iterator_range( choices ).index( Selected ) );
438 }
439
AddFixedText(const TranslatableString & Str,bool bCenter,int wrapWidth)440 void ShuttleGuiBase::AddFixedText(
441 const TranslatableString &Str, bool bCenter, int wrapWidth)
442 {
443 const auto translated = Str.Translation();
444 UseUpId();
445 if( mShuttleMode != eIsCreating )
446 return;
447 auto text = safenew wxStaticText(GetParent(),
448 miId, translated, wxDefaultPosition, wxDefaultSize,
449 GetStyle( wxALIGN_LEFT ));
450 mpWind = text;
451 if ( wrapWidth > 0 )
452 text->Wrap( wrapWidth );
453 mpWind->SetName(wxStripMenuCodes(translated)); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs)
454 if( bCenter )
455 {
456 miProp=1;
457 UpdateSizersC();
458 }
459 else
460 UpdateSizers();
461 }
462
AddVariableText(const TranslatableString & Str,bool bCenter,int PositionFlags,int wrapWidth)463 wxStaticText * ShuttleGuiBase::AddVariableText(
464 const TranslatableString &Str,
465 bool bCenter, int PositionFlags, int wrapWidth )
466 {
467 const auto translated = Str.Translation();
468 UseUpId();
469 if( mShuttleMode != eIsCreating )
470 return wxDynamicCast(wxWindow::FindWindowById( miId, mpDlg), wxStaticText);
471
472 wxStaticText *pStatic;
473 auto text = pStatic = safenew wxStaticText(GetParent(), miId, translated,
474 wxDefaultPosition, wxDefaultSize,
475 GetStyle( wxALIGN_LEFT ));
476 mpWind = text;
477 if ( wrapWidth > 0 )
478 text->Wrap( wrapWidth );
479 mpWind->SetName(wxStripMenuCodes(translated)); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs)
480 if( bCenter )
481 {
482 miProp=1;
483 if( PositionFlags )
484 UpdateSizersCore( false, PositionFlags );
485 else
486 UpdateSizersC();
487 }
488 else
489 if( PositionFlags )
490 UpdateSizersCore( false, PositionFlags );
491 else
492 UpdateSizers();
493 return pStatic;
494 }
495
AddReadOnlyText(const TranslatableString & Caption,const wxString & Value)496 ReadOnlyText * ShuttleGuiBase::AddReadOnlyText(
497 const TranslatableString &Caption, const wxString &Value)
498 {
499 const auto translated = Caption.Translation();
500 auto style = GetStyle( wxBORDER_NONE );
501 HandleOptionality( Caption );
502 mItem.miStyle = wxALIGN_CENTER_VERTICAL;
503 AddPrompt( Caption );
504 UseUpId();
505 if( mShuttleMode != eIsCreating )
506 return wxDynamicCast(wxWindow::FindWindowById( miId, mpDlg), ReadOnlyText);
507 ReadOnlyText * pReadOnlyText;
508 miProp=0;
509
510 mpWind = pReadOnlyText = safenew ReadOnlyText(GetParent(), miId, Value,
511 wxDefaultPosition, wxDefaultSize, GetStyle( style ));
512 mpWind->SetName(wxStripMenuCodes(translated));
513 UpdateSizers();
514 return pReadOnlyText;
515 }
516
AddCombo(const TranslatableString & Prompt,const wxString & Selected,const wxArrayStringEx & choices)517 wxComboBox * ShuttleGuiBase::AddCombo(
518 const TranslatableString &Prompt,
519 const wxString &Selected, const wxArrayStringEx & choices )
520 {
521 const auto translated = Prompt.Translation();
522 HandleOptionality( Prompt );
523 AddPrompt( Prompt );
524 UseUpId();
525 if( mShuttleMode != eIsCreating )
526 return wxDynamicCast(wxWindow::FindWindowById( miId, mpDlg), wxComboBox);
527 wxComboBox * pCombo;
528 miProp=0;
529
530 int n = choices.size();
531 if( n>50 ) n=50;
532 int i;
533 wxString Choices[50];
534 for(i=0;i<n;i++)
535 {
536 Choices[i] = choices[i];
537 }
538
539 mpWind = pCombo = safenew wxComboBox(GetParent(), miId, Selected, wxDefaultPosition, wxDefaultSize,
540 n, Choices, GetStyle( 0 ));
541 mpWind->SetName(wxStripMenuCodes(translated));
542
543 UpdateSizers();
544 return pCombo;
545 }
546
547
DoAddRadioButton(const TranslatableString & Prompt,int style,int selector,int initValue)548 wxRadioButton * ShuttleGuiBase::DoAddRadioButton(
549 const TranslatableString &Prompt, int style, int selector, int initValue)
550 {
551 const auto translated = Prompt.Translation();
552 /// \todo This function and the next two, suitably adapted, could be
553 /// used by TieRadioButton.
554 UseUpId();
555 if( mShuttleMode != eIsCreating )
556 return wxDynamicCast(wxWindow::FindWindowById( miId, mpDlg), wxRadioButton);
557 wxRadioButton * pRad;
558 mpWind = pRad = safenew wxRadioButton(GetParent(), miId, translated,
559 wxDefaultPosition, wxDefaultSize, GetStyle( style ) );
560 mpWind->SetName(wxStripMenuCodes(translated));
561 if ( style )
562 pRad->SetValue( true );
563 UpdateSizers();
564 pRad->SetValue( selector == initValue );
565 return pRad;
566 }
567
AddRadioButton(const TranslatableString & Prompt,int selector,int initValue)568 wxRadioButton * ShuttleGuiBase::AddRadioButton(
569 const TranslatableString &Prompt, int selector, int initValue)
570 {
571 return DoAddRadioButton( Prompt, wxRB_GROUP, selector, initValue );
572 }
573
AddRadioButtonToGroup(const TranslatableString & Prompt,int selector,int initValue)574 wxRadioButton * ShuttleGuiBase::AddRadioButtonToGroup(
575 const TranslatableString &Prompt, int selector, int initValue)
576 {
577 return DoAddRadioButton( Prompt, 0, selector, initValue );
578 }
579
580 #ifdef __WXMAC__
SetFocus()581 void wxSliderWrapper::SetFocus()
582 {
583 // bypassing the override in wxCompositeWindow<wxSliderBase> which ends up
584 // doing nothing
585 return wxSliderBase::SetFocus();
586 }
587 #endif
588
AddSlider(const TranslatableString & Prompt,int pos,int Max,int Min)589 wxSlider * ShuttleGuiBase::AddSlider(
590 const TranslatableString &Prompt, int pos, int Max, int Min)
591 {
592 HandleOptionality( Prompt );
593 AddPrompt( Prompt );
594 UseUpId();
595 if( mShuttleMode != eIsCreating )
596 return wxDynamicCast(wxWindow::FindWindowById( miId, mpDlg), wxSlider);
597 wxSlider * pSlider;
598 mpWind = pSlider = safenew wxSliderWrapper(GetParent(), miId,
599 pos, Min, Max,
600 wxDefaultPosition,
601 // Bug2289: On Linux at least, sliders like to be constructed with the
602 // proper size, not reassigned size later
603 ( ( mItem.mWindowSize == wxSize{} ) ? wxDefaultSize : mItem.mWindowSize ),
604 GetStyle( wxSL_HORIZONTAL | wxSL_LABELS | wxSL_AUTOTICKS )
605 );
606 #if wxUSE_ACCESSIBILITY
607 // so that name can be set on a standard control
608 mpWind->SetAccessible(safenew WindowAccessible(mpWind));
609 #endif
610 mpWind->SetName(wxStripMenuCodes(Prompt.Translation()));
611 miProp=1;
612 UpdateSizers();
613 return pSlider;
614 }
615
AddSpinCtrl(const TranslatableString & Prompt,int Value,int Max,int Min)616 wxSpinCtrl * ShuttleGuiBase::AddSpinCtrl(
617 const TranslatableString &Prompt, int Value, int Max, int Min)
618 {
619 const auto translated = Prompt.Translation();
620 HandleOptionality( Prompt );
621 AddPrompt( Prompt );
622 UseUpId();
623 if( mShuttleMode != eIsCreating )
624 return wxDynamicCast(wxWindow::FindWindowById( miId, mpDlg), wxSpinCtrl);
625 wxSpinCtrl * pSpinCtrl;
626 mpWind = pSpinCtrl = safenew wxSpinCtrl(GetParent(), miId,
627 wxEmptyString,
628 wxDefaultPosition, wxDefaultSize,
629 GetStyle( wxSP_VERTICAL | wxSP_ARROW_KEYS ),
630 Min, Max, Value
631 );
632 mpWind->SetName(wxStripMenuCodes(translated));
633 miProp=1;
634 UpdateSizers();
635 return pSpinCtrl;
636 }
637
AddTextBox(const TranslatableString & Caption,const wxString & Value,const int nChars)638 wxTextCtrl * ShuttleGuiBase::AddTextBox(
639 const TranslatableString &Caption, const wxString &Value, const int nChars)
640 {
641 const auto translated = Caption.Translation();
642 HandleOptionality( Caption );
643 AddPrompt( Caption );
644 UseUpId();
645 if( mShuttleMode != eIsCreating )
646 return wxDynamicCast(wxWindow::FindWindowById( miId, mpDlg), wxTextCtrl);
647 wxTextCtrl * pTextCtrl;
648 wxSize Size(wxDefaultSize);
649 if( nChars > 0 )
650 {
651 int width;
652 mpDlg->GetTextExtent( wxT("9"), &width, nullptr );
653 Size.SetWidth( nChars * width );
654 }
655 miProp=0;
656
657 #ifdef EXPERIMENTAL_RIGHT_ALIGNED_TEXTBOXES
658 long flags = wxTE_RIGHT;
659 #else
660 long flags = wxTE_LEFT;
661 #endif
662
663 mpWind = pTextCtrl = safenew wxTextCtrlWrapper(GetParent(), miId, Value,
664 wxDefaultPosition, Size, GetStyle( flags ));
665 #if wxUSE_ACCESSIBILITY
666 // so that name can be set on a standard control
667 mpWind->SetAccessible(safenew WindowAccessible(mpWind));
668 #endif
669 mpWind->SetName(wxStripMenuCodes(translated));
670 UpdateSizers();
671 return pTextCtrl;
672 }
673
AddNumericTextBox(const TranslatableString & Caption,const wxString & Value,const int nChars)674 wxTextCtrl * ShuttleGuiBase::AddNumericTextBox(
675 const TranslatableString &Caption, const wxString &Value, const int nChars)
676 {
677 const auto translated = Caption.Translation();
678 HandleOptionality( Caption );
679 AddPrompt( Caption );
680 UseUpId();
681 if( mShuttleMode != eIsCreating )
682 return wxDynamicCast(wxWindow::FindWindowById( miId, mpDlg), wxTextCtrl);
683 wxTextCtrl * pTextCtrl;
684 wxSize Size(wxDefaultSize);
685 if( nChars > 0 )
686 {
687 Size.SetWidth( nChars *5 );
688 }
689 miProp=0;
690
691 #ifdef EXPERIMENTAL_RIGHT_ALIGNED_TEXTBOXES
692 long flags = wxTE_RIGHT;
693 #else
694 long flags = wxTE_LEFT;
695 #endif
696
697 wxTextValidator Validator(wxFILTER_NUMERIC);
698 mpWind = pTextCtrl = safenew wxTextCtrl(GetParent(), miId, Value,
699 wxDefaultPosition, Size, GetStyle( flags ),
700 Validator // It's OK to pass this. It will be cloned.
701 );
702 #if wxUSE_ACCESSIBILITY
703 // so that name can be set on a standard control
704 mpWind->SetAccessible(safenew WindowAccessible(mpWind));
705 #endif
706 mpWind->SetName(wxStripMenuCodes(translated));
707 UpdateSizers();
708 return pTextCtrl;
709 }
710
711 /// Multiline text box that grows.
AddTextWindow(const wxString & Value)712 wxTextCtrl * ShuttleGuiBase::AddTextWindow(const wxString &Value)
713 {
714 UseUpId();
715 if( mShuttleMode != eIsCreating )
716 return wxDynamicCast(wxWindow::FindWindowById( miId, mpDlg), wxTextCtrl);
717 wxTextCtrl * pTextCtrl;
718 SetProportions( 1 );
719 mpWind = pTextCtrl = safenew wxTextCtrl(GetParent(), miId, Value,
720 wxDefaultPosition, wxDefaultSize, GetStyle( wxTE_MULTILINE ));
721 #if wxUSE_ACCESSIBILITY
722 // so that name can be set on a standard control
723 mpWind->SetAccessible(safenew WindowAccessible(mpWind));
724 #endif
725 UpdateSizers();
726 // Start off at start of window...
727 pTextCtrl->SetInsertionPoint( 0 );
728 pTextCtrl->ShowPosition( 0 );
729 return pTextCtrl;
730 }
731
732 /// Single line text box of fixed size.
AddConstTextBox(const TranslatableString & Prompt,const TranslatableString & Value)733 void ShuttleGuiBase::AddConstTextBox(
734 const TranslatableString &Prompt, const TranslatableString &Value)
735 {
736 HandleOptionality( Prompt );
737 AddPrompt( Prompt );
738 UseUpId();
739 if( mShuttleMode != eIsCreating )
740 return;
741 // return wxDynamicCast(wxWindow::FindWindowById( miId, mpDlg), wx);
742 miProp=0;
743 UpdateSizers();
744 miProp=0;
745 const auto translatedValue = Value.Translation();
746 mpWind = safenew wxStaticText(GetParent(), miId,
747 translatedValue, wxDefaultPosition, wxDefaultSize,
748 GetStyle( 0 ));
749 mpWind->SetName(translatedValue); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs)
750 UpdateSizers();
751 }
752
AddListBox(const wxArrayStringEx & choices)753 wxListBox * ShuttleGuiBase::AddListBox(const wxArrayStringEx &choices)
754 {
755 UseUpId();
756 if( mShuttleMode != eIsCreating )
757 return wxDynamicCast(wxWindow::FindWindowById( miId, mpDlg), wxListBox);
758 wxListBox * pListBox;
759 SetProportions( 1 );
760 mpWind = pListBox = safenew wxListBox(GetParent(), miId,
761 wxDefaultPosition, wxDefaultSize, choices, GetStyle(0));
762 pListBox->SetMinSize( wxSize( 120,150 ));
763 UpdateSizers();
764 return pListBox;
765 }
766
767
AddGrid()768 wxGrid * ShuttleGuiBase::AddGrid()
769 {
770 UseUpId();
771 if( mShuttleMode != eIsCreating )
772 return wxDynamicCast(wxWindow::FindWindowById( miId, mpDlg), wxGrid);
773 wxGrid * pGrid;
774 SetProportions( 1 );
775 mpWind = pGrid = safenew wxGrid(GetParent(), miId, wxDefaultPosition,
776 wxDefaultSize, GetStyle( wxWANTS_CHARS ));
777 pGrid->SetMinSize( wxSize( 120, 150 ));
778 UpdateSizers();
779 return pGrid;
780 }
781
AddListControl(std::initializer_list<const ListControlColumn> columns,long listControlStyles)782 wxListCtrl * ShuttleGuiBase::AddListControl(
783 std::initializer_list<const ListControlColumn> columns,
784 long listControlStyles
785 )
786 {
787 UseUpId();
788 if( mShuttleMode != eIsCreating )
789 return wxDynamicCast(wxWindow::FindWindowById( miId, mpDlg), wxListCtrl);
790 wxListCtrl * pListCtrl;
791 SetProportions( 1 );
792 mpWind = pListCtrl = safenew wxListCtrl(GetParent(), miId,
793 wxDefaultPosition, wxDefaultSize, GetStyle( wxLC_ICON ));
794 pListCtrl->SetMinSize( wxSize( 120,150 ));
795 UpdateSizers();
796
797 DoInsertListColumns( pListCtrl, listControlStyles, columns );
798
799 return pListCtrl;
800 }
801
AddListControlReportMode(std::initializer_list<const ListControlColumn> columns,long listControlStyles)802 wxListCtrl * ShuttleGuiBase::AddListControlReportMode(
803 std::initializer_list<const ListControlColumn> columns,
804 long listControlStyles
805 )
806 {
807 UseUpId();
808 if( mShuttleMode != eIsCreating )
809 return wxDynamicCast(wxWindow::FindWindowById( miId, mpDlg), wxListCtrl);
810 wxListCtrl * pListCtrl;
811 SetProportions( 1 );
812 mpWind = pListCtrl = safenew wxListCtrl(GetParent(), miId,
813 wxDefaultPosition, wxSize(230,120),//wxDefaultSize,
814 GetStyle( wxLC_REPORT | wxLC_HRULES | wxLC_VRULES | wxSUNKEN_BORDER ));
815 // pListCtrl->SetMinSize( wxSize( 120,150 ));
816 UpdateSizers();
817
818 DoInsertListColumns( pListCtrl, listControlStyles, columns );
819
820 return pListCtrl;
821 }
822
DoInsertListColumns(wxListCtrl * pListCtrl,long listControlStyles,std::initializer_list<const ListControlColumn> columns)823 void ShuttleGuiBase::DoInsertListColumns(
824 wxListCtrl *pListCtrl,
825 long listControlStyles,
826 std::initializer_list<const ListControlColumn> columns )
827 {
828 // Old comment from HistoryWindow.cpp follows
829 // -- is it still correct for wxWidgets 3?
830
831 // Do this BEFORE inserting the columns. On the Mac at least, the
832 // columns are deleted and later InsertItem()s will cause Audacity to crash.
833 for ( auto style = 1l; style <= listControlStyles; style <<= 1 )
834 if ( (style & listControlStyles) )
835 pListCtrl->SetSingleStyle(style, true);
836
837 long iCol = 0;
838 bool dummyColumn =
839 columns.size() > 0 && begin(columns)->format == wxLIST_FORMAT_RIGHT;
840
841 //A dummy first column, which is then deleted, is a workaround -
842 // under Windows the first column can't be right aligned.
843 if (dummyColumn)
844 pListCtrl->InsertColumn( iCol++, wxString{} );
845
846 for (auto &column : columns)
847 pListCtrl->InsertColumn(
848 iCol++, column.heading.Translation(), column.format, column.width );
849
850 if (dummyColumn)
851 pListCtrl->DeleteColumn( 0 );
852 }
853
AddTree()854 wxTreeCtrl * ShuttleGuiBase::AddTree()
855 {
856 UseUpId();
857 if( mShuttleMode != eIsCreating )
858 return wxDynamicCast(wxWindow::FindWindowById( miId, mpDlg), wxTreeCtrl);
859 wxTreeCtrl * pTreeCtrl;
860 SetProportions( 1 );
861 mpWind = pTreeCtrl = safenew wxTreeCtrl(GetParent(), miId, wxDefaultPosition, wxDefaultSize,
862 GetStyle( wxTR_HAS_BUTTONS ));
863 pTreeCtrl->SetMinSize( wxSize( 120,650 ));
864 UpdateSizers();
865 return pTreeCtrl;
866 }
867
AddIcon(wxBitmap * pBmp)868 void ShuttleGuiBase::AddIcon(wxBitmap *pBmp)
869 {
870 UseUpId();
871 if( mShuttleMode != eIsCreating )
872 // return wxDynamicCast(wxWindow::FindWindowById( miId, mpDlg), wx);
873 return;
874 wxBitmapButton * pBtn;
875 mpWind = pBtn = safenew wxBitmapButton(GetParent(), miId, *pBmp,
876 wxDefaultPosition, wxDefaultSize, GetStyle( wxBU_AUTODRAW ) );
877 pBtn->SetWindowStyle( wxBORDER_NONE );
878 pBtn->SetCanFocus(false);
879 UpdateSizersC();
880 }
881
Prop(int iProp)882 ShuttleGuiBase & ShuttleGuiBase::Prop( int iProp )
883 {
884 miPropSetByUser = iProp;
885 return *this;
886 }
887
888 /// Starts a static box around a number of controls.
889 /// @param Str The text of the title for the box.
890 /// @param iProp The resizing proportion value.
891 /// Use iProp == 0 for a minimum sized static box.
892 /// Use iProp == 1 for a box that grows if there is space to spare.
StartStatic(const TranslatableString & Str,int iProp)893 wxStaticBox * ShuttleGuiBase::StartStatic(const TranslatableString &Str, int iProp)
894 {
895 UseUpId();
896 if( mShuttleMode != eIsCreating )
897 return NULL;
898 auto translated = Str.Translation();
899 wxStaticBox * pBox = safenew wxStaticBoxWrapper(
900 GetParent(), miId, translated );
901 pBox->SetLabel( translated );
902 if (Str.empty()) {
903 // NVDA 2018.3 or later does not read the controls in a group box which has
904 // an accessibility name which is empty. Bug 2169.
905 #if wxUSE_ACCESSIBILITY
906 // so that name can be set on a standard control
907 pBox->SetAccessible(safenew WindowAccessible(pBox));
908 #endif
909 pBox->SetName(wxT("\a")); // non-empty string which screen readers do not read
910 }
911 else
912 pBox->SetName( wxStripMenuCodes( translated ) );
913 mpSubSizer = std::make_unique<wxStaticBoxSizer>(
914 pBox,
915 wxVERTICAL );
916 miSizerProp = iProp;
917 UpdateSizers();
918 mpParent = pBox;
919 return pBox;
920 }
921
EndStatic()922 void ShuttleGuiBase::EndStatic()
923 {
924 if( mShuttleMode != eIsCreating )
925 return;
926 PopSizer();
927 mpParent = mpParent->GetParent();
928 }
929
930 /// This allows subsequent controls and static boxes to be in
931 /// a scrolled panel. Very handy if you are running out of space
932 /// on a dialog.
933 ///
934 /// The iStyle parameter is used in some very hacky code that
935 /// dynamically repopulates a dialog. It also controls the
936 /// background colour. Look at the code for details.
937 /// @param istyle deprecated parameter, but has been used for hacking.
StartScroller(int iStyle)938 wxScrolledWindow * ShuttleGuiBase::StartScroller(int iStyle)
939 {
940 UseUpId();
941 if( mShuttleMode != eIsCreating )
942 return wxDynamicCast(wxWindow::FindWindowById( miId, mpDlg), wxScrolledWindow);
943
944 wxScrolledWindow * pScroller;
945 mpWind = pScroller = safenew wxScrolledWindow(GetParent(), miId, wxDefaultPosition, wxDefaultSize,
946 GetStyle( wxSUNKEN_BORDER ) );
947 pScroller->SetScrollRate( 20,20 );
948
949 // This fools NVDA into not saying "Panel" when the dialog gets focus
950 pScroller->SetName(wxT("\a"));
951 pScroller->SetLabel(wxT("\a"));
952
953 SetProportions( 1 );
954 if( iStyle==2 )
955 {
956 UpdateSizersAtStart();
957 }
958 else
959 {
960 // mpWind->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_MENUBAR));
961 UpdateSizers(); // adds window in to current sizer.
962 }
963
964 // create a sizer within the window...
965 mpParent = pScroller;
966 pScroller->SetSizer(mpSizer = safenew wxBoxSizer(wxVERTICAL));
967 PushSizer();
968 return pScroller;
969 }
970
EndScroller()971 void ShuttleGuiBase::EndScroller()
972 {
973 if( mShuttleMode != eIsCreating )
974 return;
975 wxSize ScrollSize = mpSizer->GetMinSize();
976 int yMin = ScrollSize.y+4;
977 int xMin = ScrollSize.x+4;
978 if( yMin > 400)
979 {
980 yMin = 400;
981 xMin+=50;// extra space for vertical scrollbar.
982 }
983
984 mpParent->SetMinSize( wxSize(xMin, yMin) );
985
986 PopSizer();
987 mpParent = mpParent->GetParent();
988 }
989
StartPanel(int iStyle)990 wxPanel * ShuttleGuiBase::StartPanel(int iStyle)
991 {
992 UseUpId();
993 if( mShuttleMode != eIsCreating )
994 return wxDynamicCast(wxWindow::FindWindowById( miId, mpDlg), wxPanel);
995 wxPanel * pPanel;
996 mpWind = pPanel = safenew wxPanelWrapper( GetParent(), miId, wxDefaultPosition, wxDefaultSize,
997 GetStyle( wxNO_BORDER ));
998
999 if( iStyle != 0 )
1000 {
1001 mpWind->SetBackgroundColour(
1002 iStyle==1
1003 ? wxColour( 190,200,230) :
1004 wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)
1005 );
1006 }
1007 SetProportions(0);
1008 miBorder=2;
1009 UpdateSizers(); // adds window in to current sizer.
1010
1011 // create a sizer within the window...
1012 mpParent = pPanel;
1013 pPanel->SetSizer(mpSizer = safenew wxBoxSizer(wxVERTICAL));
1014 PushSizer();
1015 return pPanel;
1016 }
1017
EndPanel()1018 void ShuttleGuiBase::EndPanel()
1019 {
1020 if( mShuttleMode != eIsCreating )
1021 return;
1022 PopSizer();
1023 mpParent = mpParent->GetParent();
1024 }
1025
StartNotebook()1026 wxNotebook * ShuttleGuiBase::StartNotebook()
1027 {
1028 UseUpId();
1029 if( mShuttleMode != eIsCreating )
1030 return wxDynamicCast(wxWindow::FindWindowById( miId, mpDlg), wxNotebook);
1031 wxNotebook * pNotebook;
1032 mpWind = pNotebook = safenew wxNotebook(GetParent(),
1033 miId, wxDefaultPosition, wxDefaultSize, GetStyle( 0 ));
1034 SetProportions( 1 );
1035 UpdateSizers();
1036 mpParent = pNotebook;
1037 return pNotebook;
1038 }
1039
EndNotebook()1040 void ShuttleGuiBase::EndNotebook()
1041 {
1042 //PopSizer();
1043 mpParent = mpParent->GetParent();
1044 }
1045
1046
StartSimplebook()1047 wxSimplebook * ShuttleGuiBase::StartSimplebook()
1048 {
1049 UseUpId();
1050 if( mShuttleMode != eIsCreating )
1051 return wxDynamicCast(wxWindow::FindWindowById( miId, mpDlg), wxSimplebook);
1052 wxSimplebook * pNotebook;
1053 mpWind = pNotebook = safenew wxSimplebook(GetParent(),
1054 miId, wxDefaultPosition, wxDefaultSize, GetStyle( 0 ));
1055 SetProportions( 1 );
1056 UpdateSizers();
1057 mpParent = pNotebook;
1058 return pNotebook;
1059 }
1060
EndSimplebook()1061 void ShuttleGuiBase::EndSimplebook()
1062 {
1063 //PopSizer();
1064 mpParent = mpParent->GetParent();
1065 }
1066
1067
StartNotebookPage(const TranslatableString & Name)1068 wxNotebookPage * ShuttleGuiBase::StartNotebookPage(
1069 const TranslatableString & Name )
1070 {
1071 if( mShuttleMode != eIsCreating )
1072 return NULL;
1073 // return wxDynamicCast(wxWindow::FindWindowById( miId, mpDlg), wx);
1074 auto pNotebook = static_cast< wxBookCtrlBase* >( mpParent );
1075 wxNotebookPage * pPage = safenew wxPanelWrapper(GetParent());
1076 const auto translated = Name.Translation();
1077 pPage->SetName(translated);
1078
1079 pNotebook->AddPage(
1080 pPage,
1081 translated);
1082
1083 SetProportions( 1 );
1084 mpParent = pPage;
1085 pPage->SetSizer(mpSizer = safenew wxBoxSizer(wxVERTICAL));
1086 PushSizer();
1087 // UpdateSizers();
1088 return pPage;
1089 }
1090
EndNotebookPage()1091 void ShuttleGuiBase::EndNotebookPage()
1092 {
1093 if( mShuttleMode != eIsCreating )
1094 return;
1095 PopSizer();
1096 mpParent = mpParent->GetParent();
1097 }
1098
1099 // Doxygen description is at the start of the file
1100 // this is a wxPanel with erase background disabled.
1101 class InvisiblePanel final : public wxPanelWrapper
1102 {
1103 public:
InvisiblePanel(wxWindow * parent,wxWindowID id=-1,const wxPoint & pos=wxDefaultPosition,const wxSize & size=wxDefaultSize,long style=wxTAB_TRAVERSAL)1104 InvisiblePanel(
1105 wxWindow* parent,
1106 wxWindowID id = -1,
1107 const wxPoint& pos = wxDefaultPosition,
1108 const wxSize& size = wxDefaultSize,
1109 long style = wxTAB_TRAVERSAL ) :
1110 wxPanelWrapper( parent, id, pos, size, style )
1111 {
1112 };
~InvisiblePanel()1113 ~InvisiblePanel(){;};
1114 void OnPaint( wxPaintEvent &event );
OnErase(wxEraseEvent &)1115 void OnErase(wxEraseEvent &/*evt*/){;};
1116 DECLARE_EVENT_TABLE()
1117 };
1118
1119
BEGIN_EVENT_TABLE(InvisiblePanel,wxPanelWrapper)1120 BEGIN_EVENT_TABLE(InvisiblePanel, wxPanelWrapper)
1121 // EVT_PAINT(InvisiblePanel::OnPaint)
1122 EVT_ERASE_BACKGROUND( InvisiblePanel::OnErase)
1123 END_EVENT_TABLE()
1124
1125 void InvisiblePanel::OnPaint( wxPaintEvent & WXUNUSED(event))
1126 {
1127 // Don't repaint my background.
1128 wxPaintDC dc(this);
1129 // event.Skip(); // swallow the paint event.
1130 }
1131
StartInvisiblePanel()1132 wxPanel * ShuttleGuiBase::StartInvisiblePanel()
1133 {
1134 UseUpId();
1135 if( mShuttleMode != eIsCreating )
1136 return wxDynamicCast(wxWindow::FindWindowById( miId, mpDlg), wxPanel);
1137 wxPanel * pPanel;
1138 mpWind = pPanel = safenew wxPanelWrapper(GetParent(), miId, wxDefaultPosition, wxDefaultSize,
1139 wxNO_BORDER);
1140
1141 mpWind->SetBackgroundColour(
1142 wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE)
1143 );
1144 SetProportions( 1 );
1145 miBorder=0;
1146 UpdateSizers(); // adds window in to current sizer.
1147
1148 // create a sizer within the window...
1149 mpParent = pPanel;
1150 pPanel->SetSizer(mpSizer = safenew wxBoxSizer(wxVERTICAL));
1151 PushSizer();
1152 return pPanel;
1153 }
1154
EndInvisiblePanel()1155 void ShuttleGuiBase::EndInvisiblePanel()
1156 {
1157 EndPanel();
1158 }
1159
1160
1161 /// Starts a Horizontal Layout.
1162 /// - Use wxEXPAND and 0 to expand horizontally but not vertically.
1163 /// - Use wxEXPAND and 1 to expand horizontally and vertically.
1164 /// - Use wxCENTRE and 1 for no expansion.
1165 /// @param PositionFlag Typically wxEXPAND or wxALIGN_CENTER.
1166 /// @param iProp Proportionality for resizing.
StartHorizontalLay(int PositionFlags,int iProp)1167 void ShuttleGuiBase::StartHorizontalLay( int PositionFlags, int iProp)
1168 {
1169 if( mShuttleMode != eIsCreating )
1170 return;
1171 miSizerProp=iProp;
1172 mpSubSizer = std::make_unique<wxBoxSizer>( wxHORIZONTAL );
1173 // PRL: wxALL has no effect because UpdateSizersCore ignores border
1174 UpdateSizersCore( false, PositionFlags | wxALL );
1175 }
1176
EndHorizontalLay()1177 void ShuttleGuiBase::EndHorizontalLay()
1178 {
1179 if( mShuttleMode != eIsCreating )
1180 return;
1181 PopSizer();
1182 }
1183
StartVerticalLay(int iProp)1184 void ShuttleGuiBase::StartVerticalLay(int iProp)
1185 {
1186 if( mShuttleMode != eIsCreating )
1187 return;
1188 miSizerProp=iProp;
1189 mpSubSizer = std::make_unique<wxBoxSizer>( wxVERTICAL );
1190 UpdateSizers();
1191 }
1192
StartVerticalLay(int PositionFlags,int iProp)1193 void ShuttleGuiBase::StartVerticalLay(int PositionFlags, int iProp)
1194 {
1195 if( mShuttleMode != eIsCreating )
1196 return;
1197 miSizerProp=iProp;
1198 mpSubSizer = std::make_unique<wxBoxSizer>( wxVERTICAL );
1199 // PRL: wxALL has no effect because UpdateSizersCore ignores border
1200 UpdateSizersCore( false, PositionFlags | wxALL );
1201 }
1202
EndVerticalLay()1203 void ShuttleGuiBase::EndVerticalLay()
1204 {
1205 if( mShuttleMode != eIsCreating )
1206 return;
1207 PopSizer();
1208 }
1209
StartWrapLay(int PositionFlags,int iProp)1210 void ShuttleGuiBase::StartWrapLay(int PositionFlags, int iProp)
1211 {
1212 if (mShuttleMode != eIsCreating)
1213 return;
1214
1215 miSizerProp = iProp;
1216 mpSubSizer = std::make_unique<wxWrapSizer>(wxHORIZONTAL, 0);
1217
1218 UpdateSizersCore(false, PositionFlags | wxALL);
1219 }
1220
EndWrapLay()1221 void ShuttleGuiBase::EndWrapLay()
1222 {
1223 if (mShuttleMode != eIsCreating)
1224 return;
1225
1226 PopSizer();
1227 }
1228
StartMultiColumn(int nCols,int PositionFlags)1229 void ShuttleGuiBase::StartMultiColumn(int nCols, int PositionFlags)
1230 {
1231 if( mShuttleMode != eIsCreating )
1232 return;
1233 mpSubSizer = std::make_unique<wxFlexGridSizer>( nCols );
1234 // PRL: wxALL has no effect because UpdateSizersCore ignores border
1235 UpdateSizersCore( false, PositionFlags | wxALL );
1236 }
1237
EndMultiColumn()1238 void ShuttleGuiBase::EndMultiColumn()
1239 {
1240 if( mShuttleMode != eIsCreating )
1241 return;
1242 PopSizer();
1243 }
1244
1245 /// When we're exchanging with the configured shuttle rather than with the GUI
1246 /// We use this function.
DoDataShuttle(const wxString & Name,WrappedType & WrappedRef)1247 void ShuttleGuiBase::DoDataShuttle( const wxString &Name, WrappedType & WrappedRef )
1248 {
1249 wxASSERT( mpShuttle );
1250 mpShuttle->TransferWrappedType( Name, WrappedRef );
1251 }
1252
1253 //-----------------------------------------------------------------------//
1254
1255 // We now have a group of tie functions which are generic in the type
1256 // they bind to (i.e. WrappedType).
1257 // The type specific versions are much shorter and are later
1258 // in this file.
DoTieCheckBox(const TranslatableString & Prompt,WrappedType & WrappedRef)1259 wxCheckBox * ShuttleGuiBase::DoTieCheckBox(const TranslatableString &Prompt, WrappedType & WrappedRef)
1260 {
1261 HandleOptionality( Prompt );
1262 // The Add function does a UseUpId(), so don't do it here in that case.
1263 if( mShuttleMode == eIsCreating )
1264 return AddCheckBox( Prompt, WrappedRef.ReadAsString() == wxT("true"));
1265
1266 UseUpId();
1267
1268 wxWindow * pWnd = wxWindow::FindWindowById( miId, mpDlg);
1269 wxCheckBox * pCheckBox = wxDynamicCast(pWnd, wxCheckBox);
1270
1271 switch( mShuttleMode )
1272 {
1273 // IF setting internal storage from the controls.
1274 case eIsGettingMetadata:
1275 break;
1276 case eIsGettingFromDialog:
1277 {
1278 wxASSERT( pCheckBox );
1279 WrappedRef.WriteToAsBool( pCheckBox->GetValue() );
1280 }
1281 break;
1282 case eIsSettingToDialog:
1283 {
1284 wxASSERT( pCheckBox );
1285 pCheckBox->SetValue( WrappedRef.ReadAsBool() );
1286 }
1287 break;
1288 default:
1289 wxASSERT( false );
1290 break;
1291 }
1292 return pCheckBox;
1293 }
1294
DoTieCheckBoxOnRight(const TranslatableString & Prompt,WrappedType & WrappedRef)1295 wxCheckBox * ShuttleGuiBase::DoTieCheckBoxOnRight(const TranslatableString &Prompt, WrappedType & WrappedRef)
1296 {
1297 HandleOptionality( Prompt );
1298 // The Add function does a UseUpId(), so don't do it here in that case.
1299 if( mShuttleMode == eIsCreating )
1300 return AddCheckBoxOnRight( Prompt, WrappedRef.ReadAsString() == wxT("true"));
1301
1302 UseUpId();
1303
1304 wxWindow * pWnd = wxWindow::FindWindowById( miId, mpDlg);
1305 wxCheckBox * pCheckBox = wxDynamicCast(pWnd, wxCheckBox);
1306
1307 switch( mShuttleMode )
1308 {
1309 // IF setting internal storage from the controls.
1310 case eIsGettingMetadata:
1311 break;
1312 case eIsGettingFromDialog:
1313 {
1314 wxASSERT( pCheckBox );
1315 WrappedRef.WriteToAsBool( pCheckBox->GetValue() );
1316 }
1317 break;
1318 case eIsSettingToDialog:
1319 {
1320 wxASSERT( pCheckBox );
1321 pCheckBox->SetValue( WrappedRef.ReadAsBool() );
1322 }
1323 break;
1324 default:
1325 wxASSERT( false );
1326 break;
1327 }
1328 return pCheckBox;
1329 }
1330
DoTieSpinCtrl(const TranslatableString & Prompt,WrappedType & WrappedRef,const int max,const int min)1331 wxSpinCtrl * ShuttleGuiBase::DoTieSpinCtrl(
1332 const TranslatableString &Prompt,
1333 WrappedType & WrappedRef, const int max, const int min )
1334 {
1335 HandleOptionality( Prompt );
1336 // The Add function does a UseUpId(), so don't do it here in that case.
1337 if( mShuttleMode == eIsCreating )
1338 return AddSpinCtrl( Prompt, WrappedRef.ReadAsInt(), max, min );
1339
1340 UseUpId();
1341 wxSpinCtrl * pSpinCtrl=NULL;
1342
1343 wxWindow * pWnd = wxWindow::FindWindowById( miId, mpDlg);
1344 pSpinCtrl = wxDynamicCast(pWnd, wxSpinCtrl);
1345
1346 switch( mShuttleMode )
1347 {
1348 // IF setting internal storage from the controls.
1349 case eIsGettingMetadata:
1350 break;
1351 case eIsGettingFromDialog:
1352 {
1353 wxASSERT( pSpinCtrl );
1354 WrappedRef.WriteToAsInt( pSpinCtrl->GetValue() );
1355 }
1356 break;
1357 case eIsSettingToDialog:
1358 {
1359 wxASSERT( pSpinCtrl );
1360 pSpinCtrl->SetValue( WrappedRef.ReadAsInt() );
1361 }
1362 break;
1363 default:
1364 wxASSERT( false );
1365 break;
1366 }
1367 return pSpinCtrl;
1368 }
1369
DoTieTextBox(const TranslatableString & Prompt,WrappedType & WrappedRef,const int nChars)1370 wxTextCtrl * ShuttleGuiBase::DoTieTextBox(
1371 const TranslatableString &Prompt, WrappedType & WrappedRef, const int nChars)
1372 {
1373 HandleOptionality( Prompt );
1374 // The Add function does a UseUpId(), so don't do it here in that case.
1375 if( mShuttleMode == eIsCreating )
1376 return AddTextBox( Prompt, WrappedRef.ReadAsString(), nChars );
1377
1378 UseUpId();
1379 wxTextCtrl * pTextBox=NULL;
1380
1381 wxWindow * pWnd = wxWindow::FindWindowById( miId, mpDlg);
1382 pTextBox = wxDynamicCast(pWnd, wxTextCtrl);
1383
1384 switch( mShuttleMode )
1385 {
1386 // IF setting internal storage from the controls.
1387 case eIsGettingMetadata:
1388 break;
1389 case eIsGettingFromDialog:
1390 {
1391 wxASSERT( pTextBox );
1392 WrappedRef.WriteToAsString( pTextBox->GetValue() );
1393 }
1394 break;
1395 case eIsSettingToDialog:
1396 {
1397 wxASSERT( pTextBox );
1398 pTextBox->SetValue( WrappedRef.ReadAsString() );
1399 }
1400 break;
1401 default:
1402 wxASSERT( false );
1403 break;
1404 }
1405 return pTextBox;
1406 }
1407
DoTieNumericTextBox(const TranslatableString & Prompt,WrappedType & WrappedRef,const int nChars)1408 wxTextCtrl * ShuttleGuiBase::DoTieNumericTextBox(
1409 const TranslatableString &Prompt, WrappedType & WrappedRef, const int nChars)
1410 {
1411 HandleOptionality( Prompt );
1412 // The Add function does a UseUpId(), so don't do it here in that case.
1413 if( mShuttleMode == eIsCreating )
1414 return AddNumericTextBox( Prompt, WrappedRef.ReadAsString(), nChars );
1415
1416 UseUpId();
1417 wxTextCtrl * pTextBox=NULL;
1418
1419 wxWindow * pWnd = wxWindow::FindWindowById( miId, mpDlg);
1420 pTextBox = wxDynamicCast(pWnd, wxTextCtrl);
1421
1422 switch( mShuttleMode )
1423 {
1424 // IF setting internal storage from the controls.
1425 case eIsGettingMetadata:
1426 break;
1427 case eIsGettingFromDialog:
1428 {
1429 wxASSERT( pTextBox );
1430 WrappedRef.WriteToAsString( pTextBox->GetValue() );
1431 }
1432 break;
1433 case eIsSettingToDialog:
1434 {
1435 wxASSERT( pTextBox );
1436 pTextBox->SetValue( WrappedRef.ReadAsString() );
1437 }
1438 break;
1439 default:
1440 wxASSERT( false );
1441 break;
1442 }
1443 return pTextBox;
1444 }
1445
DoTieSlider(const TranslatableString & Prompt,WrappedType & WrappedRef,const int max,int min)1446 wxSlider * ShuttleGuiBase::DoTieSlider(
1447 const TranslatableString &Prompt,
1448 WrappedType & WrappedRef, const int max, int min )
1449 {
1450 HandleOptionality( Prompt );
1451 // The Add function does a UseUpId(), so don't do it here in that case.
1452 if( mShuttleMode != eIsCreating )
1453 UseUpId();
1454 wxSlider * pSlider=NULL;
1455 switch( mShuttleMode )
1456 {
1457 case eIsCreating:
1458 {
1459 pSlider = AddSlider( Prompt, WrappedRef.ReadAsInt(), max, min );
1460 }
1461 break;
1462 // IF setting internal storage from the controls.
1463 case eIsGettingMetadata:
1464 break;
1465 case eIsGettingFromDialog:
1466 {
1467 wxWindow * pWnd = wxWindow::FindWindowById( miId, mpDlg);
1468 pSlider = wxDynamicCast(pWnd, wxSlider);
1469 wxASSERT( pSlider );
1470 WrappedRef.WriteToAsInt( pSlider->GetValue() );
1471 }
1472 break;
1473 case eIsSettingToDialog:
1474 {
1475 wxWindow * pWnd = wxWindow::FindWindowById( miId, mpDlg);
1476 pSlider = wxDynamicCast(pWnd, wxSlider);
1477 wxASSERT( pSlider );
1478 pSlider->SetValue( WrappedRef.ReadAsInt() );
1479 }
1480 break;
1481 default:
1482 wxASSERT( false );
1483 break;
1484 }
1485 return pSlider;
1486 }
1487
1488
TieChoice(const TranslatableString & Prompt,int & Selected,const TranslatableStrings & choices)1489 wxChoice * ShuttleGuiBase::TieChoice(
1490 const TranslatableString &Prompt,
1491 int &Selected,
1492 const TranslatableStrings &choices )
1493 {
1494 HandleOptionality( Prompt );
1495
1496 // The Add function does a UseUpId(), so don't do it here in that case.
1497 if( mShuttleMode != eIsCreating )
1498 UseUpId();
1499
1500 wxChoice * pChoice=NULL;
1501 switch( mShuttleMode )
1502 {
1503 case eIsCreating:
1504 {
1505 pChoice = AddChoice( Prompt, choices, Selected );
1506 ShuttleGui::SetMinSize(pChoice, choices);
1507 }
1508 break;
1509 // IF setting internal storage from the controls.
1510 case eIsGettingMetadata:
1511 break;
1512 case eIsGettingFromDialog:
1513 {
1514 wxWindow * pWnd = wxWindow::FindWindowById( miId, mpDlg);
1515 pChoice = wxDynamicCast(pWnd, wxChoice);
1516 wxASSERT( pChoice );
1517 Selected = pChoice->GetSelection();
1518 }
1519 break;
1520 case eIsSettingToDialog:
1521 {
1522 wxWindow * pWnd = wxWindow::FindWindowById( miId, mpDlg);
1523 pChoice = wxDynamicCast(pWnd, wxChoice);
1524 wxASSERT( pChoice );
1525 pChoice->SetSelection( Selected );
1526 }
1527 break;
1528 default:
1529 wxASSERT( false );
1530 break;
1531 }
1532 return pChoice;
1533 }
1534
1535 /// This function must be within a StartRadioButtonGroup - EndRadioButtonGroup pair.
TieRadioButton()1536 wxRadioButton * ShuttleGuiBase::TieRadioButton()
1537 {
1538 wxASSERT( mRadioCount >= 0); // Did you remember to use StartRadioButtonGroup() ?
1539
1540 EnumValueSymbol symbol;
1541 if (mRadioCount >= 0 && mRadioCount < (int)mRadioSymbols.size() )
1542 symbol = mRadioSymbols[ mRadioCount ];
1543
1544 // In what follows, WrappedRef is used in read only mode, but we
1545 // don't have a 'read-only' version, so we copy to deal with the constness.
1546 auto Temp = symbol.Internal();
1547 wxASSERT( !Temp.empty() ); // More buttons than values?
1548
1549 WrappedType WrappedRef( Temp );
1550
1551 mRadioCount++;
1552
1553 UseUpId();
1554 wxRadioButton * pRadioButton = NULL;
1555
1556 switch( mShuttleMode )
1557 {
1558 case eIsCreating:
1559 {
1560 const auto &Prompt = symbol.Translation();
1561
1562 mpWind = pRadioButton = safenew wxRadioButton(GetParent(), miId, Prompt,
1563 wxDefaultPosition, wxDefaultSize,
1564 (mRadioCount==1)?wxRB_GROUP:0);
1565
1566 wxASSERT( WrappedRef.IsString() );
1567 wxASSERT( mRadioValue->IsString() );
1568 const bool value =
1569 (WrappedRef.ReadAsString() == mRadioValue->ReadAsString() );
1570 pRadioButton->SetValue( value );
1571
1572 pRadioButton->SetName(wxStripMenuCodes(Prompt));
1573 UpdateSizers();
1574 }
1575 break;
1576 case eIsGettingMetadata:
1577 break;
1578 case eIsGettingFromDialog:
1579 {
1580 wxWindow * pWnd = wxWindow::FindWindowById( miId, mpDlg);
1581 pRadioButton = wxDynamicCast(pWnd, wxRadioButton);
1582 wxASSERT( pRadioButton );
1583 if( pRadioButton->GetValue() )
1584 mRadioValue->WriteToAsString( WrappedRef.ReadAsString() );
1585 }
1586 break;
1587 default:
1588 wxASSERT( false );
1589 break;
1590 }
1591 return pRadioButton;
1592 }
1593
1594 /// Call this before any TieRadioButton calls.
StartRadioButtonGroup(const ChoiceSetting & Setting)1595 void ShuttleGuiBase::StartRadioButtonGroup( const ChoiceSetting &Setting )
1596 {
1597 mRadioSymbols = Setting.GetSymbols();
1598
1599 // Configure the generic type mechanism to use OUR string.
1600 mRadioValueString = Setting.Default().Internal();
1601 mRadioValue.emplace( mRadioValueString );
1602
1603 // Now actually start the radio button group.
1604 mRadioSettingName = Setting.Key();
1605 mRadioCount = 0;
1606 if( mShuttleMode == eIsCreating )
1607 DoDataShuttle( Setting.Key(), *mRadioValue );
1608 }
1609
1610 /// Call this after any TieRadioButton calls.
1611 /// It's generic too. We don't need type-specific ones.
EndRadioButtonGroup()1612 void ShuttleGuiBase::EndRadioButtonGroup()
1613 {
1614 // too few buttons?
1615 wxASSERT( mRadioCount == mRadioSymbols.size() );
1616
1617 if( mShuttleMode == eIsGettingFromDialog )
1618 DoDataShuttle( mRadioSettingName, *mRadioValue );
1619 mRadioValue.reset();// Clear it out...
1620 mRadioSettingName = wxT("");
1621 mRadioCount = -1; // So we detect a problem.
1622 mRadioSymbols = {};
1623 }
1624
1625 //-----------------------------------------------------------------------//
1626 //-- Now we are into type specific Tie() functions.
1627 //-- These are all 'one-step' tie functions.
1628
TieCheckBox(const TranslatableString & Prompt,bool & Var)1629 wxCheckBox * ShuttleGuiBase::TieCheckBox(const TranslatableString &Prompt, bool &Var)
1630 {
1631 WrappedType WrappedRef( Var );
1632 return DoTieCheckBox( Prompt, WrappedRef );
1633 }
1634
1635 // See comment in AddCheckBoxOnRight() for why we have this variant.
TieCheckBoxOnRight(const TranslatableString & Prompt,bool & Var)1636 wxCheckBox * ShuttleGuiBase::TieCheckBoxOnRight(const TranslatableString &Prompt, bool &Var)
1637 {
1638 // Only does anything different if it's creating.
1639 WrappedType WrappedRef( Var );
1640 if( mShuttleMode == eIsCreating )
1641 return AddCheckBoxOnRight( Prompt, WrappedRef.ReadAsString() == wxT("true") );
1642 return DoTieCheckBox( Prompt, WrappedRef );
1643 }
1644
TieSpinCtrl(const TranslatableString & Prompt,int & Value,const int max,const int min)1645 wxSpinCtrl * ShuttleGuiBase::TieSpinCtrl(
1646 const TranslatableString &Prompt, int &Value, const int max, const int min )
1647 {
1648 WrappedType WrappedRef(Value);
1649 return DoTieSpinCtrl( Prompt, WrappedRef, max, min );
1650 }
1651
TieTextBox(const TranslatableString & Prompt,wxString & Selected,const int nChars)1652 wxTextCtrl * ShuttleGuiBase::TieTextBox(
1653 const TranslatableString &Prompt, wxString &Selected, const int nChars)
1654 {
1655 WrappedType WrappedRef(Selected);
1656 return DoTieTextBox( Prompt, WrappedRef, nChars );
1657 }
1658
TieTextBox(const TranslatableString & Prompt,int & Selected,const int nChars)1659 wxTextCtrl * ShuttleGuiBase::TieTextBox(
1660 const TranslatableString &Prompt, int &Selected, const int nChars)
1661 {
1662 WrappedType WrappedRef( Selected );
1663 return DoTieTextBox( Prompt, WrappedRef, nChars );
1664 }
1665
TieTextBox(const TranslatableString & Prompt,double & Value,const int nChars)1666 wxTextCtrl * ShuttleGuiBase::TieTextBox(
1667 const TranslatableString &Prompt, double &Value, const int nChars)
1668 {
1669 WrappedType WrappedRef( Value );
1670 return DoTieTextBox( Prompt, WrappedRef, nChars );
1671 }
1672
TieNumericTextBox(const TranslatableString & Prompt,int & Value,const int nChars)1673 wxTextCtrl * ShuttleGuiBase::TieNumericTextBox(
1674 const TranslatableString &Prompt, int &Value, const int nChars)
1675 {
1676 WrappedType WrappedRef( Value );
1677 return DoTieNumericTextBox( Prompt, WrappedRef, nChars );
1678 }
1679
TieNumericTextBox(const TranslatableString & Prompt,double & Value,const int nChars)1680 wxTextCtrl * ShuttleGuiBase::TieNumericTextBox(
1681 const TranslatableString &Prompt, double &Value, const int nChars)
1682 {
1683 WrappedType WrappedRef( Value );
1684 return DoTieNumericTextBox( Prompt, WrappedRef, nChars );
1685 }
1686
TieSlider(const TranslatableString & Prompt,int & pos,const int max,const int min)1687 wxSlider * ShuttleGuiBase::TieSlider(
1688 const TranslatableString &Prompt, int &pos, const int max, const int min )
1689 {
1690 WrappedType WrappedRef( pos );
1691 return DoTieSlider( Prompt, WrappedRef, max, min );
1692 }
1693
TieSlider(const TranslatableString & Prompt,double & pos,const double max,const double min)1694 wxSlider * ShuttleGuiBase::TieSlider(
1695 const TranslatableString &Prompt,
1696 double &pos, const double max, const double min )
1697 {
1698 WrappedType WrappedRef( pos );
1699 return DoTieSlider( Prompt, WrappedRef, max, min );
1700 }
1701
TieSlider(const TranslatableString & Prompt,float & pos,const float fMin,const float fMax)1702 wxSlider * ShuttleGuiBase::TieSlider(
1703 const TranslatableString &Prompt,
1704 float &pos, const float fMin, const float fMax)
1705 {
1706 const float RoundFix=0.0000001f;
1707 int iVal=(pos-fMin+RoundFix)*100.0/(fMax-fMin);
1708 wxSlider * pWnd = TieSlider( Prompt, iVal, 100 );
1709 pos = iVal*(fMax-fMin)*0.01+fMin;
1710 return pWnd;
1711 }
1712
TieVSlider(const TranslatableString & Prompt,float & pos,const float fMin,const float fMax)1713 wxSlider * ShuttleGuiBase::TieVSlider(
1714 const TranslatableString &Prompt,
1715 float &pos, const float fMin, const float fMax)
1716 {
1717 int iVal=(pos-fMin)*100.0/(fMax-fMin);
1718 // if( mShuttleMode == eIsCreating )
1719 // {
1720 // return AddVSlider( Prompt, iVal, 100 );
1721 // }
1722 wxSlider * pWnd = TieSlider( Prompt, iVal, 100 );
1723 pos = iVal*(fMax-fMin)*0.01+fMin;
1724 return pWnd;
1725 }
1726
TieChoice(const TranslatableString & Prompt,TranslatableString & Selected,const TranslatableStrings & choices)1727 wxChoice * ShuttleGuiBase::TieChoice(
1728 const TranslatableString &Prompt,
1729 TranslatableString &Selected,
1730 const TranslatableStrings &choices )
1731 {
1732 int Index = make_iterator_range( choices ).index( Selected );
1733 auto result = TieChoice( Prompt, Index, choices );
1734 if ( Index >= 0 && Index < choices.size() )
1735 Selected = choices[ Index ];
1736 else
1737 Selected = {};
1738 return result;
1739 }
1740
1741 //-----------------------------------------------------------------------//
1742
1743 // ShuttleGui utility functions to look things up in a list.
1744 // If not present, we use the configured default index value.
1745
1746 //-----------------------------------------------------------------------//
1747
1748 /// String-to-Index
TranslateToIndex(const wxString & Value,const wxArrayStringEx & Choices)1749 int ShuttleGuiBase::TranslateToIndex( const wxString &Value, const wxArrayStringEx &Choices )
1750 {
1751 int n = make_iterator_range( Choices ).index( Value );
1752 if( n == wxNOT_FOUND )
1753 n=miNoMatchSelector;
1754 miNoMatchSelector = 0;
1755 return n;
1756 }
1757
1758 /// Index-to-String
TranslateFromIndex(const int nIn,const wxArrayStringEx & Choices)1759 wxString ShuttleGuiBase::TranslateFromIndex( const int nIn, const wxArrayStringEx &Choices )
1760 {
1761 int n = nIn;
1762 if( n== wxNOT_FOUND )
1763 n=miNoMatchSelector;
1764 miNoMatchSelector = 0;
1765 if( n < (int)Choices.size() )
1766 {
1767 return Choices[n];
1768 }
1769 return wxT("");
1770 }
1771
1772 //-----------------------------------------------------------------------//
1773
1774
1775 // ShuttleGui code uses the model that you read into program variables
1776 // and write out from program variables.
1777
1778 // In programs like Audacity which don't use internal program variables
1779 // you have to do both steps in one go, using variants of the standard
1780 // 'Tie' functions which call the underlying Tie functions twice.
1781
1782 //----------------------------------------------------------------------//
1783
1784
1785 /**
1786 Code-Condenser function.
1787
1788 We have functions which need to do:
1789
1790 \code
1791 // Either: Values are coming in:
1792 DoDataShuttle( SettingName, WrappedRef );
1793 TieMyControl( Prompt, WrappedRef );
1794
1795 // Or: Values are going out:
1796 TieMyControl( Prompt, WrappedRef );
1797 DoDataShuttle( SettingName, WrappedRef );
1798 \endcode
1799
1800 So we make a list of all the possible steps,
1801 and have DoStep choose which ones are actually done,
1802 like this:
1803
1804 \code
1805 if( DoStep(1) ) DoFirstThing();
1806 if( DoStep(2) ) DoSecondThing();
1807 if( DoStep(3) ) DoThirdThing();
1808 \endcode
1809
1810 The repeated choice logic can then be taken out of those
1811 functions.
1812
1813 JKC: This paves the way for doing data validation too,
1814 though when we add that we will need to renumber the
1815 steps.
1816 */
DoStep(int iStep)1817 bool ShuttleGuiBase::DoStep( int iStep )
1818 {
1819 // Get value and create
1820 if( mShuttleMode == eIsCreating )
1821 {
1822 return (iStep==1) || (iStep==2);
1823 }
1824 // Like creating, get the value and set.
1825 if( mShuttleMode == eIsSettingToDialog )
1826 {
1827 return (iStep==1) || (iStep==2);
1828 }
1829 if( mShuttleMode == eIsGettingFromDialog )
1830 {
1831 return (iStep==2) || (iStep==3);
1832 }
1833 if( mShuttleMode == eIsGettingMetadata )
1834 return iStep ==2;
1835 wxASSERT( false );
1836 return false;
1837 }
1838
1839
1840 /// Variant of the standard TieCheckBox which does the two step exchange
1841 /// between gui and stack variable and stack variable and shuttle.
TieCheckBox(const TranslatableString & Prompt,const BoolSetting & Setting)1842 wxCheckBox * ShuttleGuiBase::TieCheckBox(
1843 const TranslatableString &Prompt,
1844 const BoolSetting &Setting)
1845 {
1846 wxCheckBox * pCheck=NULL;
1847
1848 auto Value = Setting.GetDefault();
1849 WrappedType WrappedRef( Value );
1850 if( DoStep(1) ) DoDataShuttle( Setting.GetPath(), WrappedRef );
1851 if( DoStep(2) ) pCheck = DoTieCheckBox( Prompt, WrappedRef );
1852 if( DoStep(3) ) DoDataShuttle( Setting.GetPath(), WrappedRef );
1853
1854 return pCheck;
1855 }
1856
1857 /// Variant of the standard TieCheckBox which does the two step exchange
1858 /// between gui and stack variable and stack variable and shuttle.
TieCheckBoxOnRight(const TranslatableString & Prompt,const BoolSetting & Setting)1859 wxCheckBox * ShuttleGuiBase::TieCheckBoxOnRight(
1860 const TranslatableString &Prompt,
1861 const BoolSetting & Setting)
1862 {
1863 wxCheckBox * pCheck=NULL;
1864
1865 auto Value = Setting.GetDefault();
1866 WrappedType WrappedRef( Value );
1867 if( DoStep(1) ) DoDataShuttle( Setting.GetPath(), WrappedRef );
1868 if( DoStep(2) ) pCheck = DoTieCheckBoxOnRight( Prompt, WrappedRef );
1869 if( DoStep(3) ) DoDataShuttle( Setting.GetPath(), WrappedRef );
1870
1871 return pCheck;
1872 }
1873
1874 /// Variant of the standard TieSlider which does the two step exchange
1875 /// between gui and stack variable and stack variable and shuttle.
TieSlider(const TranslatableString & Prompt,const IntSetting & Setting,const int max,const int min)1876 wxSlider * ShuttleGuiBase::TieSlider(
1877 const TranslatableString &Prompt,
1878 const IntSetting & Setting,
1879 const int max,
1880 const int min)
1881 {
1882 wxSlider * pSlider=NULL;
1883
1884 auto Value = Setting.GetDefault();
1885 WrappedType WrappedRef( Value );
1886 if( DoStep(1) ) DoDataShuttle( Setting.GetPath(), WrappedRef );
1887 if( DoStep(2) ) pSlider = DoTieSlider( Prompt, WrappedRef, max, min );
1888 if( DoStep(3) ) DoDataShuttle( Setting.GetPath(), WrappedRef );
1889
1890 return pSlider;
1891 }
1892
1893 /// Variant of the standard TieSpinCtrl which does the two step exchange
1894 /// between gui and stack variable and stack variable and shuttle.
TieSpinCtrl(const TranslatableString & Prompt,const IntSetting & Setting,const int max,const int min)1895 wxSpinCtrl * ShuttleGuiBase::TieSpinCtrl(
1896 const TranslatableString &Prompt,
1897 const IntSetting &Setting,
1898 const int max,
1899 const int min)
1900 {
1901 wxSpinCtrl * pSpinCtrl=NULL;
1902
1903 auto Value = Setting.GetDefault();
1904 WrappedType WrappedRef( Value );
1905 if( DoStep(1) ) DoDataShuttle( Setting.GetPath(), WrappedRef );
1906 if( DoStep(2) ) pSpinCtrl = DoTieSpinCtrl( Prompt, WrappedRef, max, min );
1907 if( DoStep(3) ) DoDataShuttle( Setting.GetPath(), WrappedRef );
1908
1909 return pSpinCtrl;
1910 }
1911
1912 /// Variant of the standard TieTextBox which does the two step exchange
1913 /// between gui and stack variable and stack variable and shuttle.
TieTextBox(const TranslatableString & Prompt,const StringSetting & Setting,const int nChars)1914 wxTextCtrl * ShuttleGuiBase::TieTextBox(
1915 const TranslatableString & Prompt,
1916 const StringSetting & Setting,
1917 const int nChars)
1918 {
1919 wxTextCtrl * pText=(wxTextCtrl*)NULL;
1920
1921 auto Value = Setting.GetDefault();
1922 WrappedType WrappedRef( Value );
1923 if( DoStep(1) ) DoDataShuttle( Setting.GetPath(), WrappedRef );
1924 if( DoStep(2) ) pText = DoTieTextBox( Prompt, WrappedRef, nChars );
1925 if( DoStep(3) ) DoDataShuttle( Setting.GetPath(), WrappedRef );
1926 return pText;
1927 }
1928
1929 /// Variant of the standard TieTextBox which does the two step exchange
1930 /// between gui and stack variable and stack variable and shuttle.
1931 /// This one does it for double values...
TieIntegerTextBox(const TranslatableString & Prompt,const IntSetting & Setting,const int nChars)1932 wxTextCtrl * ShuttleGuiBase::TieIntegerTextBox(
1933 const TranslatableString & Prompt,
1934 const IntSetting &Setting,
1935 const int nChars)
1936 {
1937 wxTextCtrl * pText=(wxTextCtrl*)NULL;
1938
1939 auto Value = Setting.GetDefault();
1940 WrappedType WrappedRef( Value );
1941 if( DoStep(1) ) DoDataShuttle( Setting.GetPath(), WrappedRef );
1942 if( DoStep(2) ) pText = DoTieNumericTextBox( Prompt, WrappedRef, nChars );
1943 if( DoStep(3) ) DoDataShuttle( Setting.GetPath(), WrappedRef );
1944 return pText;
1945 }
1946
1947 /// Variant of the standard TieTextBox which does the two step exchange
1948 /// between gui and stack variable and stack variable and shuttle.
1949 /// This one does it for double values...
TieNumericTextBox(const TranslatableString & Prompt,const DoubleSetting & Setting,const int nChars)1950 wxTextCtrl * ShuttleGuiBase::TieNumericTextBox(
1951 const TranslatableString & Prompt,
1952 const DoubleSetting & Setting,
1953 const int nChars)
1954 {
1955 wxTextCtrl * pText=(wxTextCtrl*)NULL;
1956
1957 auto Value = Setting.GetDefault();
1958 WrappedType WrappedRef( Value );
1959 if( DoStep(1) ) DoDataShuttle( Setting.GetPath(), WrappedRef );
1960 if( DoStep(2) ) pText = DoTieNumericTextBox( Prompt, WrappedRef, nChars );
1961 if( DoStep(3) ) DoDataShuttle( Setting.GetPath(), WrappedRef );
1962 return pText;
1963 }
1964
1965 /// Variant of the standard TieChoice which does the two step exchange
1966 /// between gui and stack variable and stack variable and shuttle.
1967 /// @param Prompt The prompt shown beside the control.
1968 /// @param Setting Encapsulates setting name, internal and visible
1969 /// choice strings, and a designation of one of
1970 /// those as default.
TieChoice(const TranslatableString & Prompt,const ChoiceSetting & choiceSetting)1971 wxChoice *ShuttleGuiBase::TieChoice(
1972 const TranslatableString &Prompt,
1973 const ChoiceSetting &choiceSetting )
1974 {
1975 // Do this to force any needed migrations first
1976 choiceSetting.Read();
1977
1978 const auto &symbols = choiceSetting.GetSymbols();
1979 const auto &SettingName = choiceSetting.Key();
1980 const auto &Default = choiceSetting.Default().Internal();
1981 const auto &Choices = symbols.GetMsgids();
1982 const auto &InternalChoices = symbols.GetInternals();
1983
1984 wxChoice * pChoice=(wxChoice*)NULL;
1985
1986 int TempIndex=0;
1987 // int TempIndex = TranslateToIndex( Default, InternalChoices );
1988 wxString TempStr = Default;
1989 WrappedType WrappedRef( TempStr );
1990 // Get from prefs does 1 and 2.
1991 // Put to prefs does 2 and 3.
1992 if( DoStep(1) ) DoDataShuttle( SettingName, WrappedRef ); // Get Index from Prefs.
1993 if( DoStep(1) ) TempIndex = TranslateToIndex( TempStr, InternalChoices ); // To an index
1994 if( DoStep(2) ) pChoice = TieChoice( Prompt, TempIndex, Choices );
1995 if( DoStep(3) ) TempStr = TranslateFromIndex( TempIndex, InternalChoices ); // To a string
1996 if( DoStep(3) ) DoDataShuttle( SettingName, WrappedRef ); // Put into Prefs.
1997 return pChoice;
1998 }
1999
2000 /// Variant of the standard TieChoice which does the two step exchange
2001 /// between gui and stack variable and stack variable and shuttle.
2002 /// The Translated choices and default are integers, not Strings.
2003 /// Behaves identically to the previous, but is meant for use when the choices
2004 /// are non-exhaustive and there is a companion control for arbitrary entry.
2005 /// @param Prompt The prompt shown beside the control.
2006 /// @param SettingName The setting name as stored in gPrefs
2007 /// @param Default The default integer value for this control
2008 /// @param Choices An array of choices that appear on screen.
2009 /// @param pInternalChoices The corresponding values (as an integer array)
2010 /// if null, then use 0, 1, 2, ...
TieNumberAsChoice(const TranslatableString & Prompt,const IntSetting & Setting,const TranslatableStrings & Choices,const std::vector<int> * pInternalChoices,int iNoMatchSelector)2011 wxChoice * ShuttleGuiBase::TieNumberAsChoice(
2012 const TranslatableString &Prompt,
2013 const IntSetting & Setting,
2014 const TranslatableStrings & Choices,
2015 const std::vector<int> * pInternalChoices,
2016 int iNoMatchSelector)
2017 {
2018 auto fn = [](int arg){ return wxString::Format( "%d", arg ); };
2019
2020 wxArrayStringEx InternalChoices;
2021 if ( pInternalChoices )
2022 InternalChoices =
2023 transform_container<wxArrayStringEx>(*pInternalChoices, fn);
2024 else
2025 for ( int ii = 0; ii < (int)Choices.size(); ++ii )
2026 InternalChoices.push_back( fn( ii ) );
2027
2028
2029 const auto Default = Setting.GetDefault();
2030
2031 miNoMatchSelector = iNoMatchSelector;
2032
2033 long defaultIndex;
2034 if ( pInternalChoices )
2035 defaultIndex = make_iterator_range( *pInternalChoices ).index( Default );
2036 else
2037 defaultIndex = Default;
2038 if ( defaultIndex < 0 || defaultIndex >= (int)Choices.size() )
2039 defaultIndex = -1;
2040
2041 ChoiceSetting choiceSetting{
2042 Setting.GetPath(),
2043 {
2044 ByColumns,
2045 Choices,
2046 InternalChoices,
2047 },
2048 defaultIndex
2049 };
2050
2051 return ShuttleGuiBase::TieChoice( Prompt, choiceSetting );
2052 }
2053
2054 //------------------------------------------------------------------//
2055
2056 // We're now into ShuttleGuiBase sizer and misc functions.
2057
2058 /// The Ids increment as we add NEW controls.
2059 /// However, the user can force the id manually, for example
2060 /// if they need a specific Id for a button, and then let it
2061 /// resume normal numbering later.
2062 /// UseUpId() sets miId to the next Id, either using the
2063 /// user specicfied one, or resuming the sequence.
UseUpId()2064 void ShuttleGuiBase::UseUpId()
2065 {
2066 if( miIdSetByUser > 0)
2067 {
2068 miId = miIdSetByUser;
2069 miIdSetByUser = -1;
2070 return;
2071 }
2072 miId = miIdNext++;
2073 }
2074
SetProportions(int Default)2075 void ShuttleGuiBase::SetProportions( int Default )
2076 {
2077 if( miPropSetByUser >=0 )
2078 {
2079 miProp = miPropSetByUser;
2080 miPropSetByUser =-1;
2081 return;
2082 }
2083 miProp = Default;
2084 }
2085
2086
ApplyItem(int step,const DialogDefinition::Item & item,wxWindow * pWind,wxWindow * pDlg)2087 void ShuttleGuiBase::ApplyItem( int step, const DialogDefinition::Item &item,
2088 wxWindow *pWind, wxWindow *pDlg )
2089 {
2090 if ( step == 0 ) {
2091 // Do these steps before adding the window to the sizer
2092 if( item.mUseBestSize )
2093 pWind->SetMinSize( pWind->GetBestSize() );
2094 else if( item.mHasMinSize )
2095 pWind->SetMinSize( item.mMinSize );
2096
2097 if ( item.mWindowSize != wxSize{} )
2098 pWind->SetSize( item.mWindowSize );
2099 }
2100 else if ( step == 1) {
2101 // Apply certain other optional window attributes here
2102
2103 if ( item.mValidatorSetter )
2104 item.mValidatorSetter( pWind );
2105
2106 if ( !item.mToolTip.empty() )
2107 pWind->SetToolTip( item.mToolTip.Translation() );
2108
2109 if ( !item.mName.empty() ) {
2110 pWind->SetName( item.mName.Stripped().Translation() );
2111 #ifndef __WXMAC__
2112 if (auto pButton = dynamic_cast< wxBitmapButton* >( pWind ))
2113 pButton->SetLabel( item.mName.Translation() );
2114 #endif
2115 }
2116
2117 if ( !item.mNameSuffix.empty() )
2118 pWind->SetName(
2119 pWind->GetName() + " " + item.mNameSuffix.Translation() );
2120
2121 if (item.mFocused)
2122 pWind->SetFocus();
2123
2124 if (item.mDisabled)
2125 pWind->Enable( false );
2126
2127 for (auto &pair : item.mRootConnections)
2128 pWind->Connect( pair.first, pair.second, nullptr, pDlg );
2129 }
2130 }
2131
2132
UpdateSizersCore(bool bPrepend,int Flags,bool prompt)2133 void ShuttleGuiBase::UpdateSizersCore(bool bPrepend, int Flags, bool prompt)
2134 {
2135 if( mpWind && mpParent )
2136 {
2137 int useFlags = Flags;
2138
2139 if ( !prompt && mItem.mWindowPositionFlags )
2140 // override the given Flags
2141 useFlags = mItem.mWindowPositionFlags;
2142
2143 if (!prompt)
2144 ApplyItem( 0, mItem, mpWind, mpDlg );
2145
2146 if( mpSizer){
2147 if( bPrepend )
2148 {
2149 mpSizer->Prepend(mpWind, miProp, useFlags, miBorder);
2150 }
2151 else
2152 {
2153 mpSizer->Add(mpWind, miProp, useFlags, miBorder);
2154 }
2155 }
2156
2157 if (!prompt) {
2158 ApplyItem( 1, mItem, mpWind, mpDlg );
2159 // Reset to defaults
2160 mItem = {};
2161 }
2162 }
2163
2164 if( mpSubSizer && mpSizer )
2165 {
2166 // When adding sizers into sizers, don't add a border.
2167 // unless it's a static box sizer.
2168 wxSizer *const pSubSizer = mpSubSizer.get();
2169 if (wxDynamicCast(pSubSizer, wxStaticBoxSizer))
2170 {
2171 mpSizer->Add( mpSubSizer.release(), miSizerProp, Flags , miBorder);
2172 }
2173 else
2174 {
2175 mpSizer->Add( mpSubSizer.release(), miSizerProp, Flags ,0);//miBorder);
2176 }
2177 mpSizer = pSubSizer;
2178 PushSizer();
2179 }
2180
2181 mpWind = NULL;
2182 miProp = 0;
2183 miSizerProp =0;
2184 }
2185
2186 // Sizer is added into parent sizer, and will expand/shrink.
UpdateSizers()2187 void ShuttleGuiBase::UpdateSizers()
2188 { UpdateSizersCore( false, wxEXPAND | wxALL );}
2189
2190 // Sizer is added into parent sizer, centred
UpdateSizersC()2191 void ShuttleGuiBase::UpdateSizersC()
2192 { UpdateSizersCore( false, wxALIGN_CENTRE | wxALL );}
2193
2194 // Sizer is added into parent sizer, and will expand/shrink.
2195 // added to start of sizer list.
UpdateSizersAtStart()2196 void ShuttleGuiBase::UpdateSizersAtStart()
2197 { UpdateSizersCore( true, wxEXPAND | wxALL );}
2198
PopSizer()2199 void ShuttleGuiBase::PopSizer()
2200 {
2201 mSizerDepth--;
2202 wxASSERT( mSizerDepth >=0 );
2203 mpSizer = pSizerStack[ mSizerDepth ];
2204 }
2205
PushSizer()2206 void ShuttleGuiBase::PushSizer()
2207 {
2208 mSizerDepth++;
2209 wxASSERT( mSizerDepth < nMaxNestedSizers );
2210 pSizerStack[ mSizerDepth ] = mpSizer;
2211 }
2212
GetStyle(long style)2213 long ShuttleGuiBase::GetStyle( long style )
2214 {
2215 if( mItem.miStyle )
2216 style = mItem.miStyle;
2217 mItem.miStyle = 0;
2218 return style;
2219 }
2220
2221 // A rarely used helper function that sets a pointer
2222 // ONLY if the value it is to be set to is non NULL.
SetIfCreated(wxChoice * & Var,wxChoice * Val)2223 void SetIfCreated( wxChoice * &Var, wxChoice * Val )
2224 {
2225 if( Val != NULL )
2226 Var = Val;
2227 };
SetIfCreated(wxTextCtrl * & Var,wxTextCtrl * Val)2228 void SetIfCreated( wxTextCtrl * &Var, wxTextCtrl * Val )
2229 {
2230 if( Val != NULL )
2231 Var = Val;
2232 };
SetIfCreated(wxStaticText * & Var,wxStaticText * Val)2233 void SetIfCreated( wxStaticText *&Var, wxStaticText * Val )
2234 {
2235 if( Val != NULL )
2236 Var = Val;
2237 };
2238
2239 #ifdef EXPERIMENTAL_TRACK_PANEL
2240 // Additional includes down here, to make it easier to split this into
2241 // two files at some later date.
2242 #include "../extnpanel-src/GuiWaveTrack.h"
2243 #endif
2244
ShuttleGui(wxWindow * pParent,teShuttleMode ShuttleMode,bool vertical,wxSize minSize)2245 ShuttleGui::ShuttleGui(
2246 wxWindow * pParent, teShuttleMode ShuttleMode, bool vertical, wxSize minSize)
2247 : ShuttleGuiBase( pParent, ShuttleMode, vertical, minSize )
2248 {
2249 if( ShuttleMode == eIsCreatingFromPrefs )
2250 {
2251 mShuttleMode = eIsCreating;
2252 Init( vertical, minSize ); // Wasn't fully done in base constructor because it is only done when eIsCreating is set.
2253 }
2254 else if( ShuttleMode == eIsSavingToPrefs )
2255 {
2256 mShuttleMode = eIsGettingFromDialog;
2257 }
2258 else
2259 {
2260 return;
2261 }
2262
2263 mpShuttle = std::make_unique<ShuttlePrefs>();
2264 // In this case the client is the GUI, so if creating we do want to
2265 // store in the client.
2266 mpShuttle->mbStoreInClient = (mShuttleMode == eIsCreating );
2267 };
2268
~ShuttleGui()2269 ShuttleGui::~ShuttleGui()
2270 {
2271 }
2272
2273 // Now we have Audacity specific shuttle functions.
Id(int id)2274 ShuttleGui & ShuttleGui::Id(int id )
2275 {
2276 miIdSetByUser = id;
2277 return *this;
2278 }
2279
Optional(bool & bVar)2280 ShuttleGui & ShuttleGui::Optional( bool &bVar ){
2281 mpbOptionalFlag = &bVar;
2282 return *this;
2283 };
2284
2285
CreateStdButtonSizer(wxWindow * parent,long buttons,wxWindow * extra)2286 std::unique_ptr<wxSizer> CreateStdButtonSizer(wxWindow *parent, long buttons, wxWindow *extra)
2287 {
2288 wxASSERT(parent != NULL); // To justify safenew
2289
2290 int margin;
2291 {
2292 #if defined(__WXMAC__)
2293 margin = 12;
2294 #elif defined(__WXGTK20__)
2295 margin = 12;
2296 #elif defined(__WXMSW__)
2297 wxButton b(parent, 0, wxEmptyString);
2298 margin = b.ConvertDialogToPixels(wxSize(2, 0)).x;
2299 #else
2300 wxButton b(parent, 0, wxEmptyString);
2301 margin = b->ConvertDialogToPixels(wxSize(4, 0)).x;
2302 #endif
2303 }
2304
2305 wxButton *b = NULL;
2306 auto bs = std::make_unique<wxStdDialogButtonSizer>();
2307
2308 const auto makeButton =
2309 [parent]( wxWindowID id, const wxString label = {} ) {
2310 auto result = safenew wxButton( parent, id, label );
2311 result->SetName( result->GetLabel() );
2312 return result;
2313 };
2314
2315 if( buttons & eOkButton )
2316 {
2317 b = makeButton( wxID_OK );
2318 b->SetDefault();
2319 bs->AddButton( b );
2320 }
2321
2322 if( buttons & eCancelButton )
2323 {
2324 bs->AddButton( makeButton( wxID_CANCEL ) );
2325 }
2326
2327 if( buttons & eYesButton )
2328 {
2329 b = makeButton( wxID_YES );
2330 b->SetDefault();
2331 bs->AddButton( b );
2332 }
2333
2334 if( buttons & eNoButton )
2335 {
2336 bs->AddButton( makeButton( wxID_NO ) );
2337 }
2338
2339 if( buttons & eApplyButton )
2340 {
2341 b = makeButton( wxID_APPLY );
2342 b->SetDefault();
2343 bs->AddButton( b );
2344 }
2345
2346 if( buttons & eCloseButton )
2347 {
2348 bs->AddButton( makeButton( wxID_CANCEL, XO("&Close").Translation() ) );
2349 }
2350
2351 #if defined(__WXMSW__)
2352 // See below for explanation
2353 if( buttons & eHelpButton )
2354 {
2355 // Replace standard Help button with smaller icon button.
2356 // bs->AddButton(safenew wxButton(parent, wxID_HELP));
2357 b = safenew wxBitmapButton(parent, wxID_HELP, theTheme.Bitmap( bmpHelpIcon ));
2358 b->SetToolTip( XO("Help").Translation() );
2359 b->SetLabel(XO("Help").Translation()); // for screen readers
2360 b->SetName( b->GetLabel() );
2361 bs->AddButton( b );
2362 }
2363 #endif
2364
2365 if (buttons & ePreviewButton)
2366 {
2367 bs->Add( makeButton( ePreviewID, XO("&Preview").Translation() ),
2368 0, wxALIGN_CENTER | wxLEFT | wxRIGHT, margin);
2369 }
2370 if (buttons & ePreviewDryButton)
2371 {
2372 bs->Add( makeButton( ePreviewDryID, XO("Dry Previe&w").Translation() ),
2373 0, wxALIGN_CENTER | wxLEFT | wxRIGHT, margin);
2374 bs->Add( 20, 0 );
2375 }
2376
2377 if( buttons & eSettingsButton )
2378 {
2379 bs->Add( makeButton( eSettingsID, XO("&Settings").Translation() ),
2380 0, wxALIGN_CENTER | wxLEFT | wxRIGHT, margin);
2381 bs->Add( 20, 0 );
2382 }
2383
2384 if( extra )
2385 {
2386 bs->Add( extra, 0, wxALIGN_CENTER | wxLEFT | wxRIGHT, margin );
2387 bs->Add( 40, 0 );
2388 }
2389
2390 bs->AddStretchSpacer();
2391 bs->Realize();
2392
2393 size_t lastLastSpacer = 0;
2394 size_t lastSpacer = 0;
2395 wxSizerItemList & list = bs->GetChildren();
2396 for( size_t i = 0, cnt = list.size(); i < cnt; i++ )
2397 {
2398 if( list[i]->IsSpacer() )
2399 {
2400 lastSpacer = i;
2401 }
2402 else
2403 {
2404 lastLastSpacer = lastSpacer;
2405 }
2406 }
2407
2408 // Add any buttons that need to cuddle up to the right hand cluster
2409 if( buttons & eDebugButton )
2410 {
2411 b = makeButton( eDebugID, XO("Debu&g").Translation() );
2412 bs->Insert( ++lastLastSpacer, b, 0, wxALIGN_CENTER | wxLEFT | wxRIGHT, margin );
2413 }
2414
2415 #if !defined(__WXMSW__)
2416 // Bug #2432: Couldn't find GTK guidelines, but Mac HIGs state:
2417 //
2418 // View style Help button position
2419 // Dialog with dismissal buttons (like OK and Cancel). Lower-left corner, vertically aligned with the dismissal buttons.
2420 // Dialog without dismissal buttons. Lower-left or lower-right corner.
2421 // Preference window or pane. Lower-left or lower-right corner.
2422 //
2423 // So, we're gonna cheat a little and use the lower-right corner.
2424 if( buttons & eHelpButton )
2425 {
2426 // Replace standard Help button with smaller icon button.
2427 // bs->AddButton(safenew wxButton(parent, wxID_HELP));
2428 b = safenew wxBitmapButton(parent, wxID_HELP, theTheme.Bitmap( bmpHelpIcon ));
2429 b->SetToolTip( XO("Help").Translation() );
2430 b->SetLabel(XO("Help").Translation()); // for screen readers
2431 b->SetName( b->GetLabel() );
2432 bs->Add( b, 0, wxALIGN_CENTER );
2433 }
2434 #endif
2435
2436
2437 auto s = std::make_unique<wxBoxSizer>( wxVERTICAL );
2438 s->Add( bs.release(), 1, wxEXPAND | wxALL, 7 );
2439 s->Add( 0, 3 ); // a little extra space
2440
2441 return std::unique_ptr<wxSizer>{ s.release() };
2442 }
2443
AddStandardButtons(long buttons,wxWindow * extra)2444 void ShuttleGui::AddStandardButtons(long buttons, wxWindow *extra)
2445 {
2446 if( mShuttleMode != eIsCreating )
2447 return;
2448
2449 StartVerticalLay( false );
2450
2451 miSizerProp = false;
2452 mpSubSizer = CreateStdButtonSizer( mpParent, buttons, extra );
2453 UpdateSizers();
2454 PopSizer();
2455
2456 EndVerticalLay();
2457 }
2458
AddSpace(int width,int height,int prop)2459 wxSizerItem * ShuttleGui::AddSpace( int width, int height, int prop )
2460 {
2461 if( mShuttleMode != eIsCreating )
2462 return NULL;
2463
2464 // SetProportions(0);
2465 // return mpSizer->Add( width, height, miProp);
2466
2467 return mpSizer->Add( width, height, prop );
2468 }
2469
SetMinSize(wxWindow * window,const TranslatableStrings & items)2470 void ShuttleGui::SetMinSize( wxWindow *window, const TranslatableStrings & items )
2471 {
2472 SetMinSize( window,
2473 transform_container<wxArrayStringEx>(
2474 items, std::mem_fn( &TranslatableString::StrippedTranslation ) ) );
2475 }
2476
SetMinSize(wxWindow * window,const wxArrayStringEx & items)2477 void ShuttleGui::SetMinSize( wxWindow *window, const wxArrayStringEx & items )
2478 {
2479 int maxw = 0;
2480
2481 for( size_t i = 0; i < items.size(); i++ )
2482 {
2483 int x;
2484 int y;
2485
2486 window->GetTextExtent(items[i], &x, &y );
2487 if( x > maxw )
2488 {
2489 maxw = x;
2490 }
2491 }
2492
2493 // Would be nice to know the sizes of the button and borders, but this is
2494 // the best we can do for now.
2495 #if defined(__WXMAC__)
2496 maxw += 50;
2497 #elif defined(__WXMSW__)
2498 maxw += 50;
2499 #elif defined(__WXGTK__)
2500 maxw += 50;
2501 #else
2502 maxw += 50;
2503 #endif
2504
2505 window->SetMinSize( { maxw, -1 } );
2506 }
2507
2508 /*
2509 void ShuttleGui::SetMinSize( wxWindow *window, const std::vector<int> & items )
2510 {
2511 wxArrayStringEx strs;
2512
2513 for( size_t i = 0; i < items.size(); i++ )
2514 {
2515 strs.Add( wxString::Format( wxT("%d"), items[i] ) );
2516 }
2517
2518 SetMinSize( window, strs );
2519 }
2520 */
2521
Msgids(const EnumValueSymbol strings[],size_t nStrings)2522 TranslatableStrings Msgids(
2523 const EnumValueSymbol strings[], size_t nStrings)
2524 {
2525 return transform_range<TranslatableStrings>(
2526 strings, strings + nStrings,
2527 std::mem_fn( &EnumValueSymbol::Msgid )
2528 );
2529 }
2530
Msgids(const std::vector<EnumValueSymbol> & strings)2531 TranslatableStrings Msgids( const std::vector<EnumValueSymbol> &strings )
2532 {
2533 return Msgids( strings.data(), strings.size() );
2534 }
2535