Don't hard-code the exhaustive list of sub-view types...

... in Wave track context menu and SetTrackVisualsCommand

Instead, discover them through a registry.

This eliminates some duplication of string constants and prepares for
non-intrusive generalization to more kinds of sub-views.

This makes the command agnostic about which subview types are known, but the
context menu still has special case treatment for Spectrogram Settings and
Wave Colors.
This commit is contained in:
Paul Licameli 2020-01-18 14:37:49 -05:00
parent fb8ba0ce43
commit 36aad4d1c6
7 changed files with 164 additions and 53 deletions

View File

@ -87,9 +87,10 @@ public:
const wxString &Internal() const { return mInternal; }
const TranslatableString &Msgid() const { return mMsgid; }
const TranslatableString Stripped() const { return mMsgid.Stripped(); }
const wxString Translation() const { return mMsgid.Translation(); }
const wxString StrippedTranslation() const
{ return mMsgid.Stripped().Translation(); }
{ return Stripped().Translation(); }
bool empty() const { return mInternal.empty(); }

View File

@ -248,20 +248,6 @@ static const EnumValueSymbol kColourStrings[nColours] =
};
enum kDisplayTypes
{
kWaveform,
kSpectrogram,
nDisplayTypes
};
static const EnumValueSymbol kDisplayTypeStrings[nDisplayTypes] =
{
// These are acceptable dual purpose internal/visible names
{ XO("Waveform") },
{ XO("Spectrogram") },
};
enum kScaleTypes
{
kLinear,
@ -292,10 +278,22 @@ static const EnumValueSymbol kZoomTypeStrings[nZoomTypes] =
{ XO("HalfWave") },
};
static EnumValueSymbols DiscoverSubViewTypes()
{
const auto &types = WaveTrackSubView::AllTypes();
return transform_container< EnumValueSymbols >(
types, std::mem_fn( &WaveTrackSubView::Type::name ) );
}
bool SetTrackVisualsCommand::DefineParams( ShuttleParams & S ){
SetTrackBase::DefineParams( S );
S.OptionalN( bHasHeight ).Define( mHeight, wxT("Height"), 120, 44, 2000 );
S.OptionalN( bHasDisplayType ).DefineEnum( mDisplayType, wxT("Display"), kWaveform, kDisplayTypeStrings, nDisplayTypes );
{
auto symbols = DiscoverSubViewTypes();
S.OptionalN( bHasDisplayType ).DefineEnum( mDisplayType, wxT("Display"), 0, symbols.data(), symbols.size() );
}
S.OptionalN( bHasScaleType ).DefineEnum( mScaleType, wxT("Scale"), kLinear, kScaleTypeStrings, nScaleTypes );
S.OptionalN( bHasColour ).DefineEnum( mColour, wxT("Color"), kColour0, kColourStrings, nColours );
S.OptionalN( bHasVZoom ).DefineEnum( mVZoom, wxT("VZoom"), kReset, kZoomTypeStrings, nZoomTypes );
@ -318,8 +316,15 @@ void SetTrackVisualsCommand::PopulateOrExchange(ShuttleGui & S)
S.Optional( bHasHeight ).TieNumericTextBox( XO("Height:"), mHeight );
S.Optional( bHasColour ).TieChoice( XO("Color:"), mColour,
Msgids( kColourStrings, nColours ) );
S.Optional( bHasDisplayType ).TieChoice( XO("Display:"), mDisplayType,
Msgids( kDisplayTypeStrings, nDisplayTypes ) );
{
auto symbols = DiscoverSubViewTypes();
auto typeNames = transform_container<TranslatableStrings>(
symbols, std::mem_fn( &EnumValueSymbol::Stripped ) );
S.Optional( bHasDisplayType ).TieChoice( XO("Display:"), mDisplayType,
typeNames );
}
S.Optional( bHasScaleType ).TieChoice( XO("Scale:"), mScaleType,
Msgids( kScaleTypeStrings, nScaleTypes ) );
S.Optional( bHasVZoom ).TieChoice( XO("VZoom:"), mVZoom,
@ -355,10 +360,7 @@ bool SetTrackVisualsCommand::ApplyInner(const CommandContext & context, Track *
if( wt && bHasDisplayType )
WaveTrackView::Get( *wt ).SetDisplay(
(mDisplayType == kWaveform) ?
WaveTrackViewConstants::Waveform
: WaveTrackViewConstants::Spectrum
);
WaveTrackSubView::AllTypes()[ mDisplayType ].id );
if( wt && bHasScaleType )
wt->GetIndependentWaveformSettings().scaleType =
(mScaleType==kLinear) ?

View File

@ -30,6 +30,11 @@ Paul Licameli split from WaveTrackView.cpp
#include <wx/dcmemory.h>
#include <wx/graphics.h>
static WaveTrackSubView::RegisteredType reg{ {
WaveTrackViewConstants::Spectrum,
{ wxT("Spectrogram"), XO("&Spectrogram") }
} };
SpectrumView::~SpectrumView() = default;
bool SpectrumView::IsSpectral() const

View File

@ -104,6 +104,8 @@ std::vector<UIHandlePtr> WaveTrackControls::HitTest
}
enum {
reserveDisplays = 100,
OnRate8ID = 30000, // <---
OnRate11ID, // |
OnRate16ID, // |
@ -123,8 +125,9 @@ enum {
OnFloatID, // <---
OnMultiViewID,
OnWaveformID,
OnSpectrumID,
OnSetDisplayId, lastDisplayId = (OnSetDisplayId + reserveDisplays - 1),
OnSpectrogramSettingsID,
OnChannelLeftID,
@ -620,6 +623,16 @@ void WaveTrackMenuTable::InitUserData(void *pUserData)
mpData = static_cast<PlayableTrackControls::InitMenuData*>(pUserData);
}
static WaveTrackSubView::Types AllTypes()
{
auto result = WaveTrackSubView::AllTypes();
if ( result.size() > reserveDisplays ) {
wxASSERT( false );
result.resize( reserveDisplays );
}
return result;
}
void WaveTrackMenuTable::InitMenu(Menu *pMenu)
{
WaveTrack *const pTrack = static_cast<WaveTrack*>(mpData->pTrack);
@ -631,16 +644,40 @@ void WaveTrackMenuTable::InitMenu(Menu *pMenu)
if (multiView)
checkedIds.push_back( OnMultiViewID );
bool hasSpectrum = false;
int uniqueDisplay = 0;
{
// Find the set of type ids of the shown displays, disregarding their
// top-to-bottom arrangement
auto displays = view.GetDisplays();
const auto end = displays.end();
auto iter = displays.begin();
std::sort( iter, end );
const auto displays = view.GetDisplays();
for ( auto display : displays ) {
auto id = (display == WaveTrackViewConstants::Waveform)
? OnWaveformID
: OnSpectrumID;
checkedIds.push_back( id );
if ( displays.size() == 1 )
uniqueDisplay = id;
// Check the corresponding menu items, and decide which if any has
// the unique check
int displayId = OnSetDisplayId;
int nDisplays = 0;
for ( const auto &type : AllTypes() ) {
if ( iter != end && *iter == type.id ) {
checkedIds.push_back( displayId );
uniqueDisplay = displayId;
++iter;
++nDisplays;
// Unfortunately this special knowledge of the Spectrum view type
// remains. In future, either let a registry system insert this
// menu item, or (better) move it to the context menu of the
// Spectrum vertical ruler.
// (But the latter won't be satisfactory without a means to
// open that other context menu with keystrokes only, and that
// would require some notion of a focused sub-view.)
hasSpectrum = ( type.id == WaveTrackViewConstants::Spectrum );
}
++displayId;
}
if ( nDisplays > 1 )
uniqueDisplay = 0;
}
if ( multiView && uniqueDisplay )
@ -652,9 +689,6 @@ void WaveTrackMenuTable::InitMenu(Menu *pMenu)
// We can't change them on the fly yet anyway.
auto gAudioIO = AudioIOBase::Get();
const bool bAudioBusy = gAudioIO->IsBusy();
bool hasSpectrum =
make_iterator_range( displays.begin(), displays.end() )
.contains( WaveTrackViewConstants::Spectrum );
pMenu->Enable(OnSpectrogramSettingsID, hasSpectrum && !bAudioBusy);
AudacityProject *const project = &mpData->project;
@ -733,13 +767,14 @@ BEGIN_POPUP_MENU(WaveTrackMenuTable)
if ( WaveTrackSubViews::slots() > 1 )
POPUP_MENU_CHECK_ITEM(OnMultiViewID, XO("&Multi-view"), OnMultiView)
if ( view.GetMultiView() ) {
POPUP_MENU_CHECK_ITEM(OnWaveformID, XO("Wa&veform"), OnSetDisplay)
POPUP_MENU_CHECK_ITEM(OnSpectrumID, XO("&Spectrogram"), OnSetDisplay)
}
else {
POPUP_MENU_RADIO_ITEM(OnWaveformID, XO("Wa&veform"), OnSetDisplay)
POPUP_MENU_RADIO_ITEM(OnSpectrumID, XO("&Spectrogram"), OnSetDisplay)
int id = OnSetDisplayId;
for ( const auto &type : AllTypes() ) {
if ( view.GetMultiView() ) {
POPUP_MENU_CHECK_ITEM(id++, type.name.Msgid(), OnSetDisplay)
}
else {
POPUP_MENU_RADIO_ITEM(id++, type.name.Msgid(), OnSetDisplay)
}
}
POPUP_MENU_ITEM(OnSpectrogramSettingsID, XO("S&pectrogram Settings..."), OnSpectrogramSettings)
@ -799,19 +834,12 @@ void WaveTrackMenuTable::OnMultiView(wxCommandEvent & event)
/// Set the Display mode based on the menu choice in the Track Menu.
void WaveTrackMenuTable::OnSetDisplay(wxCommandEvent & event)
{
using namespace WaveTrackViewConstants;
int idInt = event.GetId();
wxASSERT(idInt >= OnWaveformID && idInt <= OnSpectrumID);
wxASSERT(idInt >= OnSetDisplayId &&
idInt <= lastDisplayId);
const auto pTrack = static_cast<WaveTrack*>(mpData->pTrack);
WaveTrackView::Display id;
switch (idInt) {
default:
case OnWaveformID:
id = Waveform; break;
case OnSpectrumID:
id = Spectrum; break;
}
auto id = AllTypes()[ idInt - OnSetDisplayId ].id;
auto &view = WaveTrackView::Get( *pTrack );
if ( view.GetMultiView() ) {
@ -887,7 +915,7 @@ void WaveTrackMenuTable::OnSpectrogramSettings(wxCommandEvent &)
// factories.push_back(WaveformPrefsFactory( pTrack ));
factories.push_back(SpectrumPrefsFactory( pTrack ));
const int page =
// (pTrack->GetDisplay() == WaveTrack::Spectrum) ? 1 :
// (pTrack->GetDisplay() == WaveTrackViewConstants::Spectrum) ? 1 :
0;
auto title = XO("%s:").Format( pTrack->GetName() );

View File

@ -40,6 +40,53 @@ Paul Licameli split from TrackPanel.cpp
#include "../../../ui/ButtonHandle.h"
#include "../../../../TrackInfo.h"
namespace {
class Registry {
public:
using Type = WaveTrackSubView::Type;
using Types = std::vector< Type >;
void Append( Type type )
{
types.emplace_back( std::move( type ) );
sorted = false;
}
Types &Get()
{
if ( !sorted ) {
auto begin = types.begin(), end = types.end();
std::sort( begin, end );
// We don't want duplicate ids!
wxASSERT( end == std::adjacent_find( begin, end ) );
sorted = true;
}
return types;
}
private:
Types types;
bool sorted = false;
};
Registry &GetRegistry()
{
static Registry result;
return result;
}
}
WaveTrackSubView::RegisteredType::RegisteredType( Type type )
{
GetRegistry().Append( std::move( type ) );
}
// static
auto WaveTrackSubView::AllTypes() -> const Types &
{
return GetRegistry().Get();
}
namespace {
using WaveTrackSubViewPtrs = std::vector< std::shared_ptr< WaveTrackSubView > >;

View File

@ -13,16 +13,39 @@ Paul Licameli split from class WaveTrack
#include "../../../ui/CommonTrackView.h"
#include "../../../../ClientData.h"
#include "audacity/ComponentInterface.h"
namespace WaveTrackViewConstants{ enum Display : int; }
class CutlineHandle;
class TranslatableString;
class WaveTrack;
class WaveTrackView;
class WaveTrackSubView : public CommonTrackView
{
public:
using Display = WaveTrackViewConstants::Display;
struct Type {
// Identifies the type session-wide, and determines relative position in
// menus listing all types
Display id;
// The translation is suitable for the track control panel drop-down,
// and it may contain a menu accelerator
EnumValueSymbol name;
bool operator < ( const Type &other ) const { return id < other.id; }
bool operator == ( const Type &other ) const { return id == other.id; }
};
using Types = std::vector< Type >;
// Typically a file scope statically constructed object
struct RegisteredType {
RegisteredType( Type type );
};
// Discover all registered types
static const Types &AllTypes();
explicit
WaveTrackSubView( WaveTrackView &waveTrackView );

View File

@ -36,6 +36,11 @@ Paul Licameli split from WaveTrackView.cpp
#include <wx/graphics.h>
#include <wx/dc.h>
static WaveTrackSubView::RegisteredType reg{ {
WaveTrackViewConstants::Waveform,
{ wxT("Waveform"), XO("Wa&veform") }
} };
WaveformView::~WaveformView() = default;
std::vector<UIHandlePtr> WaveformView::DetailedHitTest(