Audacity 3.2.0
The Shuttle System

Introduction

The classes ShuttleGui, ShuttleCli, ShuttleGuiBase and Shuttle were designed to simplify repetitive code that moves data around. Common repetitive tasks in Audacity are creating dialogs, moving data in and out of dialogs and converting from binary to text formats.

ShuttleGui Vs ShuttleGuiBase

ShuttleGuiBase is slated for Migration of Generic Code To wxWidgets . It contains functions that work with widgets that are in wxWidgets. The derived class, ShuttleGui, contains extensions that are specific to Audacity widgets.
It isn't slated for widget migration.

Initialising ShuttleGui

A common idiom in using the ShuttleGui is as follows - this example comes from AudioIOPrefs::Populate() :

// Code is from a dialog class, so 'this' is a pointer to the dialog
ShuttleGui S(this, eIsCreatingFromPrefs); // Create the shuttle.
PopulateOrExchange(S); // Use it.
@ eIsCreatingFromPrefs
Definition: ShuttleGui.h:46
#define S(N)
Definition: ToChars.cpp:64
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI.
Definition: ShuttleGui.h:640

S is a temporary object, only kept for as long as needed to actually do the one exchange.

The first line creates and initialises the ShuttleGui object, setting it up for creating a dialog's contents using information about initial values from the global preferences.

The PopulateOrExchange() function is here being used to populate the dialog. The same function can be called from elsewhere, later, with a different S to exchange data.

The instance of ShuttleGui, S, shown in the example above isn't needed after returning from PopulateOrExchange().

ShuttleGui Methods

ShuttleGui has several kinds of methods.

  • Layout methods like ShuttleGui::StartHorizontalLay(), used to start a piece of GUI that is to be laid out horizontally. Use ShuttleGui::EndHorizontalLay() to end such a section. This is a simple wrapper for the wxWidgets wxBoxSizer. It has the advantage that the Shuttle keeps track of the sizer. You do not need to. You do not need to provide a name for the sizer. This shortens the code.
// Example of using a Start/End pair
S.StartHorizontalLay()
{
... Add controls that you want in the horizontal layout
}
S.EndHorizontalLay()
PROJECT_FILE_IO_API void Add(const FilePath &path)

The { } braces are optional, just add them where they improve readability.

  • Add methods, like ShuttleGui::AddCheckBox(). This adds the check box and returns a pointer to it. You use Add methods when you don't want ShuttleGui to handle the data exchange. It just creates the control and adds it into the current sizer.
// Example of calling an Add method
S.AddChoice( _("Script:"),_("(a) Basic Operation"), &mScripts );
#define _(s)
Definition: Internat.h:73
  • Tie methods, like ShuttleGui::TieCheckBox(). This creates the check box as above, returning a pointer to it. It also facilitates exchange of data with the control.
// Example of calling a Tie method
S.TieChoice( _("Device:"), mDevice,
wxT(""), mmPlayNames, mmPlayLabels );
wxT("CloseDown"))

So Tie methods and Add methods are very similar. The Tie methods have more parameters to them. You have to specify what you are exchanging with. There are many name overloaded variants on Tie methods to suit different circumstances. Exchanging an integer with a text box uses a different overload to exchanging a string with the text box.

In the example above, mDevice is a wxString variable passed by reference to the function. This allows ShuttleGui both to read and write to it. Which happens depends on how S has been configured.

With both Add and Tie you can optionally specify a windows Id. Use code like the following:

// Example of using a windows Id that we chose.
S.Id( idSplashScreen ).TieCheckBox( _("Show Splash Screen"),
wxT("\Inits\Splash"), true );

Where you don't specify an Id, ShuttleGui will assign the Id's sequentially.

ShuttleGui Layout Tips

ShuttleGui wraps wxWidget sizers, and the wxWidgets sizer system can sometimes itself be confusing. The most common problem is finding that controls don't resize at all for some reason.

Resizing requires that 'stretchiness' propagate all the way down from the ultimate parent window. Any sizers that is not using wxEXPAND will cause everything within in it to retain the size it had when the GUI was created, i.e. it will not resize when the window does. A very common idiom is to use wxEXPAND but with an expand proportion of '0'. That still allows expansion but not in the main direction. By contrast using wxALIGN_LEFT prevents expansion in either direction.

Many of the Add and Tie functions are designed with a two column layout in mind. So use an idiom like this:

// The '1' in the next line indicates a resizable wxStaticBox.
S.StartStatic( _("Recording"), 1 );
{
S.StartTwoColumn();
S.TieChoice( _("Device:"), wxT("RecordingDevice"),
wxT(""), mmPlayNames, mmPlayLabels );
S.TieChoice( _("Channels:"), wxT("RecordChannels"),
wxT("2"), mmChannelNames, mmChannelLabels );
S.EndTwoColumn();
}

The prompts 'Device:' and 'Channels:' will land in the first column and the actual choice controls will land in the second column. All of this is inside a wxStaticBox with the name 'Recording' on it.

To make the choice controls stretch when the wxStaticBox grows or shrinks, adjust the code to read:

// This idiom may be simplified when new functions are added to ShuttleGui
S.StartStatic( _("Recording"), 1 );
{
S.StartMultiColumn(2, wxEXPAND);
S.SetStretchyCol(1);
S.TieChoice( _("Device:"), wxT("RecordingDevice"),
wxT(""), mmPlayNames, mmPlayLabels );
S.TieChoice( _("Channels:"), wxT("RecordChannels"),
wxT("2"), mmChannelNames, mmChannelLabels );
S.EndMultiColumn();
}

ShuttleGui Internals

ShuttleGui cleans up the classes which use it. Parts of its own internal code are quite repetitive, doing the same thing for different widgets with slightly different options - e.g. a wxTextCtrl with an integer value or with a string. To make the internals of ShuttleGui cleaner, it uses class WrappedType. Also compound functions are formed by chaining together shorter functions. This makes it much easier to add new options.