1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-07-04 22:49:07 +02:00

Error messages for disallowed commands are table-driven too

This commit is contained in:
Paul Licameli 2019-06-14 10:40:59 -04:00
parent b09a1af564
commit 1b329b0e36
2 changed files with 166 additions and 56 deletions

View File

@ -448,13 +448,62 @@ ReservedCommandFlag::ReservedCommandFlag(
Options().emplace_back( options ); Options().emplace_back( options );
} }
static const CommandFlagOptions cutCopyOptions{
// In reporting the issue with cut or copy, we don't tell the user they could also select some text in a label.
[]( const wxString &Name ) {
// PRL: These strings have hard-coded mention of a certain shortcut key,
// thus assuming the default shortcuts. That is questionable.
wxString format;
#ifdef EXPERIMENTAL_DA
// i18n-hint: %s will be replaced by the name of an action, such as Normalize, Cut, Fade.
format = _("You must first select some audio for '%s' to act on.\n\nCtrl + A selects all audio.");
#else
#ifdef __WXMAC__
// i18n-hint: %s will be replaced by the name of an action, such as Normalize, Cut, Fade.
format = _("Select the audio for %s to use (for example, Cmd + A to Select All) then try again."
// No need to explain what a help button is for.
// "\n\nClick the Help button to learn more about selection methods."
);
#else
// i18n-hint: %s will be replaced by the name of an action, such as Normalize, Cut, Fade.
format = _("Select the audio for %s to use (for example, Ctrl + A to Select All) then try again."
// No need to explain what a help button is for.
// "\n\nClick the Help button to learn more about selection methods."
);
#endif
return wxString::Format( format, Name );
#endif
},
"Selecting_Audio_-_the_basics",
XO("No Audio Selected")
};
const ReservedCommandFlag const ReservedCommandFlag
// This flag has higher priority than others for purposes of help messages.
// It was important to arrange that for reasons of breaking dependency
// cycles, giving more freedom in the choice of file in which to implement
// this flag.
AudioIONotBusyFlag{ AudioIONotBusyFlag{
[](const AudacityProject &project ){ [](const AudacityProject &project ){
return !AudioIOBusyPred( project ); return !AudioIOBusyPred( project );
}, },
CommandFlagOptions{}.QuickTest() CommandFlagOptions{ []( const wxString& ) { return
}, //lll // This reason will not be shown, because options that require it will be greyed out.
_("You can only do this when playing and recording are\nstopped. (Pausing is not sufficient.)");
} }
.QuickTest()
.Priority( 1 )
}; //lll
const ReservedCommandFlag
// The sequence of these definitions has a minor significance in determining
// which user error message has precedence if more than one might apply, so
// they should be kept in this sequence in one .cpp file if it is important
// to preserve that behavior. If they are dispersed to more than one file,
// then the precedence will be unspecified.
// The ordering of the flags that only disable the default message is not
// significant.
StereoRequiredFlag{ StereoRequiredFlag{
[](const AudacityProject &project){ [](const AudacityProject &project){
// True iff at least one stereo track is selected, i.e., at least // True iff at least one stereo track is selected, i.e., at least
@ -463,10 +512,15 @@ const ReservedCommandFlag
auto range = TrackList::Get( project ).Selected<const WaveTrack>() auto range = TrackList::Get( project ).Selected<const WaveTrack>()
- &Track::IsLeader; - &Track::IsLeader;
return !range.empty(); return !range.empty();
} },
{ []( const wxString& ) { return
// This reason will not be shown, because the stereo-to-mono is greyed out if not allowed.
_("You must first select some stereo audio to perform this\naction. (You cannot use this with mono.)");
} }
}, //lda }, //lda
TimeSelectedFlag{ TimeSelectedFlag{
TimeSelectedPred TimeSelectedPred,
cutCopyOptions
}, },
CutCopyAvailableFlag{ CutCopyAvailableFlag{
[](const AudacityProject &project){ [](const AudacityProject &project){
@ -485,20 +539,30 @@ const ReservedCommandFlag
return true; return true;
return false; return false;
} },
cutCopyOptions
}, },
WaveTracksSelectedFlag{ WaveTracksSelectedFlag{
[](const AudacityProject &project){ [](const AudacityProject &project){
return !TrackList::Get( project ).Selected<const WaveTrack>().empty(); return !TrackList::Get( project ).Selected<const WaveTrack>().empty();
} },
{ []( const wxString& ) { return
_("You must first select some audio to perform this action.\n(Selecting other kinds of track won't work.)");
} }
}, },
TracksExistFlag{ TracksExistFlag{
[](const AudacityProject &project){ [](const AudacityProject &project){
return !TrackList::Get( project ).Any().empty(); return !TrackList::Get( project ).Any().empty();
} },
CommandFlagOptions{}.DisableDefaultMessage()
}, },
TracksSelectedFlag{ TracksSelectedFlag{
TracksSelectedPred TracksSelectedPred,
{ []( const wxString &Name ){ return wxString::Format(
// i18n-hint: %s will be replaced by the name of an action, such as "Remove Tracks".
_("\"%s\" requires one or more tracks to be selected."),
Name
); } }
}, },
TrackPanelHasFocus{ TrackPanelHasFocus{
[](const AudacityProject &project){ [](const AudacityProject &project){
@ -507,7 +571,8 @@ const ReservedCommandFlag
return true; return true;
} }
return false; return false;
} },
CommandFlagOptions{}.DisableDefaultMessage()
}; //lll }; //lll
const ReservedCommandFlag const ReservedCommandFlag
@ -987,60 +1052,69 @@ void MenuManager::TellUserWhyDisallowed(
{ {
// The default string for 'reason' is a catch all. I hope it won't ever be seen // The default string for 'reason' is a catch all. I hope it won't ever be seen
// and that we will get something more specific. // and that we will get something more specific.
wxString reason = _("There was a problem with your last action. If you think\nthis is a bug, please tell us exactly where it occurred."); auto reason = _("There was a problem with your last action. If you think\nthis is a bug, please tell us exactly where it occurred.");
// The default title string is 'Disallowed'. // The default title string is 'Disallowed'.
wxString title = _("Disallowed"); auto untranslatedTitle = XO("Disallowed");
wxString helpPage; wxString helpPage;
auto missingFlags = flagsRequired & ~flagsGot; bool enableDefaultMessage = true;
if( (missingFlags & AudioIONotBusyFlag).any() ) bool defaultMessage = true;
// This reason will not be shown, because options that require it will be greyed our.
reason = _("You can only do this when playing and recording are\nstopped. (Pausing is not sufficient.)");
else if( (missingFlags & StereoRequiredFlag).any() )
// This reason will not be shown, because the stereo-to-mono is greyed out if not allowed.
reason = _("You must first select some stereo audio to perform this\naction. (You cannot use this with mono.)");
// In reporting the issue with cut or copy, we don't tell the user they could also select some text in a label.
else if( (
( missingFlags & TimeSelectedFlag ) |
( missingFlags & CutCopyAvailableFlag )
).any() ){
title = _("No Audio Selected");
#ifdef EXPERIMENTAL_DA
// i18n-hint: %s will be replaced by the name of an action, such as Normalize, Cut, Fade.
reason = wxString::Format( _("You must first select some audio for '%s' to act on.\n\nCtrl + A selects all audio."), Name );
#else
#ifdef __WXMAC__
// i18n-hint: %s will be replaced by the name of an action, such as Normalize, Cut, Fade.
reason = wxString::Format( _("Select the audio for %s to use (for example, Cmd + A to Select All) then try again."
// No need to explain what a help button is for.
// "\n\nClick the Help button to learn more about selection methods."
), Name );
#else auto doOption = [&](const CommandFlagOptions &options) {
// i18n-hint: %s will be replaced by the name of an action, such as Normalize, Cut, Fade. if ( options.message ) {
reason = wxString::Format( _("Select the audio for %s to use (for example, Ctrl + A to Select All) then try again." reason = options.message( Name );
// No need to explain what a help button is for. defaultMessage = false;
// "\n\nClick the Help button to learn more about selection methods." if ( !options.title.empty() )
), Name ); untranslatedTitle = options.title;
#endif helpPage = options.helpPage;
#endif return true;
helpPage = "Selecting_Audio_-_the_basics"; }
else {
enableDefaultMessage =
enableDefaultMessage && options.enableDefaultMessage;
return false;
}
};
const auto &alloptions = Options();
auto missingFlags = flagsRequired & ~flagsGot;
// Find greatest priority
unsigned priority = 0;
for ( const auto &options : alloptions )
priority = std::max( priority, options.priority );
// Visit all unsatisfied conditions' options, by descending priority,
// stopping when we find a message
++priority;
while( priority-- ) {
size_t ii = 0;
for ( const auto &options : alloptions ) {
if (
priority == options.priority
&&
missingFlags[ii]
&&
doOption( options ) )
goto done;
++ii;
}
} }
else if( (missingFlags & WaveTracksSelectedFlag).any() ) done:
reason = _("You must first select some audio to perform this action.\n(Selecting other kinds of track won't work.)");
else if ( (missingFlags & TracksSelectedFlag).any() ) if (
// i18n-hint: %s will be replaced by the name of an action, such as "Remove Tracks". // didn't find a message
reason = wxString::Format(_("\"%s\" requires one or more tracks to be selected."), Name); defaultMessage
// If the only thing wrong was no tracks, we do nothing and don't report a problem &&
else if( missingFlags == TracksExistFlag ) // did find a condition that suppresses the default message
return; !enableDefaultMessage
// Likewise return if it was just no tracks, and track panel did not have focus. (e.g. up-arrow to move track) )
else if( missingFlags == (TracksExistFlag | TrackPanelHasFocus) )
return;
// Likewise as above too...
else if( missingFlags == TrackPanelHasFocus )
return; return;
// Message is already translated but title is not yet
auto title = ::GetCustomTranslation( untranslatedTitle );
// Does not have the warning icon... // Does not have the warning icon...
ShowErrorDialog( ShowErrorDialog(
NULL, NULL,

View File

@ -14,6 +14,7 @@
#include <bitset> #include <bitset>
#include <functional> #include <functional>
#include <utility> #include <utility>
#include <wx/string.h>
class AudacityProject; class AudacityProject;
@ -33,9 +34,44 @@ constexpr CommandFlag
NoFlagsSpecified{ ~0ULL }; // all ones NoFlagsSpecified{ ~0ULL }; // all ones
struct CommandFlagOptions{ struct CommandFlagOptions{
// Supplied the translated name of the command, returns a translated
// error message
using MessageFormatter = std::function< wxString( const wxString& ) >;
CommandFlagOptions() = default; CommandFlagOptions() = default;
CommandFlagOptions(
const MessageFormatter &message_,
const wxString &helpPage_ = {},
const wxString &title_ = {}
) : message{ message_ }, helpPage{ helpPage_ }, title{ title_ }
{}
CommandFlagOptions && QuickTest() && CommandFlagOptions && QuickTest() &&
{ quickTest = true; return std::move( *this ); } { quickTest = true; return std::move( *this ); }
CommandFlagOptions && DisableDefaultMessage() &&
{ enableDefaultMessage = false; return std::move( *this ); }
CommandFlagOptions && Priority( unsigned priority_ ) &&
{ priority = priority_; return std::move( *this ); }
// null, or else computes non-default message for the dialog box when the
// condition is not satisfied for the selected command
MessageFormatter message;
// Title and help page are used only if a message function is given
wxString helpPage;
// Empty, or non-default title for the dialog box when the
// condition is not satisfied for the selected command
// This string must be given UN-translated.
wxString title;
// Conditions with higher "priority" are preferred over others in choosing
// the help message
unsigned priority = 0;
// If false, and no other condition with a message is unsatisfied, then
// display no dialog box at all when this condition is not satisfied
bool enableDefaultMessage = true;
// If true, assume this is a cheap test to be done always. If false, the // If true, assume this is a cheap test to be done always. If false, the
// test may be skipped and the condition assumed to be unchanged since the // test may be skipped and the condition assumed to be unchanged since the