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