diff --git a/src/Menus.cpp b/src/Menus.cpp index bd04e9b14..63b44600e 100644 --- a/src/Menus.cpp +++ b/src/Menus.cpp @@ -2184,7 +2184,7 @@ void AudacityProject::UpdateMenus(bool checkActive) //ANSWER: Because flags2 is used in the menu enable/disable. //The effect still needs flags to determine whether it will need //to actually do the 'select all' to make the command valid. - if (mSelectAllOnNone) + if (mWhatIfNoSelection != 0) { if ((flags & TracksExistFlag)) { @@ -2215,7 +2215,8 @@ void AudacityProject::UpdateMenus(bool checkActive) // With select-all-on-none, some items that we don't want enabled may have // been enabled, since we changed the flags. Here we manually disable them. - if (mSelectAllOnNone) + // 0 is grey out, 1 is Autoselect, 2 is Give warnings. + if (mWhatIfNoSelection != 0) { if (!(flags & TimeSelectedFlag) | !(flags & TracksSelectedFlag)) { diff --git a/src/Menus.h b/src/Menus.h index 6f3d0b221..9c26e1695 100644 --- a/src/Menus.h +++ b/src/Menus.h @@ -39,17 +39,16 @@ void ModifyAllProjectToolbarMenus(); CommandFlag GetFocusedFrame(); +public: // If checkActive, do not do complete flags testing on an // inactive project as it is needlessly expensive. CommandFlag GetUpdateFlags(bool checkActive = false); -double NearestZeroCrossing(double t0); - -public: //Adds label and returns index of label in labeltrack. int DoAddLabel(const SelectedRegion& region, bool preserveFocus = false); private: +double NearestZeroCrossing(double t0); // Selecting a tool from the keyboard diff --git a/src/Project.cpp b/src/Project.cpp index a4f520bf5..1e4916936 100644 --- a/src/Project.cpp +++ b/src/Project.cpp @@ -1284,7 +1284,16 @@ void AudacityProject::UpdatePrefsVariables() #else gPrefs->Read(wxT("/GUI/Help"), &mHelpPref, wxT("Local") ); #endif - gPrefs->Read(wxT("/GUI/SelectAllOnNone"), &mSelectAllOnNone, true); + bool bSelectAllIfNone; + gPrefs->Read(wxT("/GUI/SelectAllOnNone"), &bSelectAllIfNone, false); + // 0 is grey out, 1 is Autoselect, 2 is Give warnings. +#ifdef EXPERIMENTAL_DA + // DA warns or greys out. + mWhatIfNoSelection = bSelectAllIfNone ? 2 : 0; +#else + // Audacity autoselects or warns. + mWhatIfNoSelection = bSelectAllIfNone ? 1 : 2; +#endif mStopIfWasPaused = true; // not configurable for now, but could be later. gPrefs->Read(wxT("/GUI/ShowSplashScreen"), &mShowSplashScreen, true); gPrefs->Read(wxT("/GUI/Solo"), &mSoloPref, wxT("Simple")); @@ -2272,6 +2281,19 @@ void AudacityProject::DoScroll() } } +bool AudacityProject::ReportIfActionNotAllowed + ( const wxString & Name, CommandFlag & flags, CommandFlag flagsRqd, CommandFlag mask ) +{ + bool bAllowed = TryToMakeActionAllowed( flags, flagsRqd, mask ); + if( bAllowed ) + return true; + CommandManager* cm = GetCommandManager(); + if (!cm) return false; + cm->TellUserWhyDisallowed( Name, flags & mask, flagsRqd & mask); + return false; +} + + /// Determines if flags for command are compatible with current state. /// If not, then try some recovery action to make it so. /// @return whether compatible or not after any actions taken. @@ -2300,12 +2322,14 @@ bool AudacityProject::TryToMakeActionAllowed return true; } + //We can only make the action allowed if we select audio when no selection. + // IF not set up to select all audio when none, THEN return with failure. + if( mWhatIfNoSelection != 1 ) + return false; + // Why is action still not allowed? // 0's wherever a required flag is missing (or is don't care) MissingFlags = (flags & ~flagsRqd) & mask; - // IF not set up to select all audio in that case, THEN return with failure. - if( !mSelectAllOnNone ) - return false; // IF selecting all audio won't do any good, THEN return with failure. if( !(flags & WaveTracksExistFlag) ) diff --git a/src/Project.h b/src/Project.h index e91af0eed..5d637582a 100644 --- a/src/Project.h +++ b/src/Project.h @@ -541,6 +541,8 @@ public: void OnAudioIONewBlockFiles(const AutoSaveFile & blockFileLog) override; // Command Handling + bool ReportIfActionNotAllowed + ( const wxString & Name, CommandFlag & flags, CommandFlag flagsRqd, CommandFlag mask ); bool TryToMakeActionAllowed ( CommandFlag & flags, CommandFlag flagsRqd, CommandFlag mask ); @@ -688,7 +690,8 @@ private: bool mShowId3Dialog{ true }; //lda bool mEmptyCanBeDirty; - bool mSelectAllOnNone; + // 0 is grey out, 1 is Autoselect, 2 is Give warnings. + int mWhatIfNoSelection; bool mStopIfWasPaused; bool mIsSyncLocked; diff --git a/src/commands/CommandManager.cpp b/src/commands/CommandManager.cpp index 68a02c4b1..d14f31ed9 100644 --- a/src/commands/CommandManager.cpp +++ b/src/commands/CommandManager.cpp @@ -93,6 +93,8 @@ CommandManager. It holds the callback for one command. #include "Keyboard.h" #include "../PluginManager.h" #include "../effects/EffectManager.h" +#include "../widgets/LinkingHtmlWindow.h" +#include "../widgets/ErrorDialog.h" // On wxGTK, there may be many many many plugins, but the menus don't automatically // allow for scrolling, so we build sub-menus. If the menu gets longer than @@ -1139,19 +1141,39 @@ void CommandManager::SetKeyFromIndex(int i, const wxString &key) entry->key = KeyStringNormalize(key); } -void CommandManager::TellUserWhyDisallowed( CommandFlag flagsGot, CommandMask flagsRequired ) +void CommandManager::TellUserWhyDisallowed( const wxString & Name, CommandFlag flagsGot, CommandMask flagsRequired ) { // 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. wxString 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'. + wxString title = _("Disallowed"); + wxString help_url =""; auto missingFlags = flagsRequired & (~flagsGot ); if( missingFlags & AudioIONotBusyFlag ) reason = _("You can only do this when playing and recording are\nstopped. (Pausing is not sufficient.)"); else if( missingFlags & StereoRequiredFlag ) reason = _("You must first select some stereo audio to perform this\naction. (You cannot use this with mono.)"); - else if( missingFlags & TimeSelectedFlag ) - reason = _("You must first select some audio to perform this action."); + // 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 )){ + 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.\n\n" +"Click the Help button to learn more about selection methods."), Name ); +#else + // 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, Ctrl + A to Select All) then try again.\n\n" +"Click the Help button to learn more about selection methods."), Name ); +#endif +#endif + help_url = "http://alphamanual.audacityteam.org/man/Selecting_Audio_-_the_basics"; + } else if( missingFlags & WaveTracksSelectedFlag) reason = _("You must first select some audio to perform this action.\n(Selecting other kinds of track won't work.)"); // If the only thing wrong was no tracks, we do nothing and don't report a problem @@ -1164,7 +1186,30 @@ void CommandManager::TellUserWhyDisallowed( CommandFlag flagsGot, CommandMask fl else if( missingFlags == TrackPanelHasFocus ) return; - wxMessageBox(reason, _("Disallowed"), wxICON_WARNING | wxOK ); + +#if 0 + // Does not have the warning icon... + ShowErrorDialog( + NULL, + title, + reason, + help_url, + false); +#endif + + // JKC: I tried building a custom error dialog with the warning icon, and a + // help button linking to our html (without closing). + // In the end I decided it was easier (more portable across different + // OS's) to use the stock one. + int result = ::wxMessageBox(reason, title, wxICON_WARNING | wxOK | + (help_url.IsEmpty() ? 0 : wxHELP) ); + // if they click help, we fetch that help, and pop the dialog (without a + // help button) up again. + if( result == wxHELP ){ + wxHtmlLinkInfo link( help_url ); + OpenInDefaultBrowser(link); + ::wxMessageBox(reason, title, wxICON_WARNING | wxOK ); + } } /// @@ -1285,14 +1330,14 @@ bool CommandManager::HandleCommandEntry(const CommandListEntry * entry, if( !proj ) return false; + wxString NiceName = entry->label; + NiceName.Replace("&", "");// remove & + NiceName.Replace(".","");// remove ... // NB: The call may have the side effect of changing flags. - bool allowed = proj->TryToMakeActionAllowed( flags, entry->flags, combinedMask ); + bool allowed = proj->ReportIfActionNotAllowed( + NiceName, flags, entry->flags, combinedMask ); if (!allowed) - { - TellUserWhyDisallowed( - flags & combinedMask, entry->flags & combinedMask); return false; - } } (*(entry->callback))(entry->index, evt); @@ -1314,7 +1359,7 @@ bool CommandManager::HandleMenuID(int id, CommandFlag flags, CommandMask mask) /// HandleTextualCommand() allows us a limitted version of script/batch /// behavior, since we can get from a string command name to the actual /// code to run. -bool CommandManager::HandleTextualCommand(wxString & Str, CommandFlag flags, CommandMask mask) +bool CommandManager::HandleTextualCommand(const wxString & Str, CommandFlag flags, CommandMask mask) { if( Str.IsEmpty() ) return false; diff --git a/src/commands/CommandManager.h b/src/commands/CommandManager.h index bd819c05c..c03dd90e4 100644 --- a/src/commands/CommandManager.h +++ b/src/commands/CommandManager.h @@ -225,7 +225,7 @@ class AUDACITY_DLL_API CommandManager final : public XMLTagHandler // Lyrics and MixerTrackCluster classes use it. bool FilterKeyEvent(AudacityProject *project, const wxKeyEvent & evt, bool permit = false); bool HandleMenuID(int id, CommandFlag flags, CommandMask mask); - bool HandleTextualCommand(wxString & Str, CommandFlag flags, CommandMask mask); + bool HandleTextualCommand(const wxString & Str, CommandFlag flags, CommandMask mask); // // Accessing @@ -259,6 +259,7 @@ class AUDACITY_DLL_API CommandManager final : public XMLTagHandler // void WriteXML(XMLWriter &xmlFile) const /* not override */; + void TellUserWhyDisallowed(const wxString & Name, CommandFlag flagsGot, CommandFlag flagsRequired); protected: @@ -288,7 +289,6 @@ protected: // bool HandleCommandEntry(const CommandListEntry * entry, CommandFlag flags, CommandMask mask, const wxEvent * evt = NULL); - void TellUserWhyDisallowed(CommandFlag flagsGot, CommandFlag flagsRequired); // // Modifying diff --git a/src/effects/Effect.cpp b/src/effects/Effect.cpp index a3f1190b7..00f52b2ab 100644 --- a/src/effects/Effect.cpp +++ b/src/effects/Effect.cpp @@ -3171,14 +3171,13 @@ void EffectUIHost::OnApply(wxCommandEvent & evt) if (!mIsBatch && mEffect->GetType() != EffectTypeGenerate && mProject->mViewInfo.selectedRegion.isPoint()) { auto flags = AlwaysEnabledFlag; - bool allowed = mProject->TryToMakeActionAllowed(flags, - WaveTracksSelectedFlag | TimeSelectedFlag, - WaveTracksSelectedFlag | TimeSelectedFlag); + bool allowed = mProject->ReportIfActionNotAllowed( + mEffect->GetName(), + flags, + WaveTracksSelectedFlag | TimeSelectedFlag, + WaveTracksSelectedFlag | TimeSelectedFlag); if (!allowed) - { - wxMessageBox(_("You must select audio in the project window.")); return; - } } if (!mClient->ValidateUI()) diff --git a/src/prefs/TracksBehaviorsPrefs.cpp b/src/prefs/TracksBehaviorsPrefs.cpp index 20a88d322..a522b7d0f 100644 --- a/src/prefs/TracksBehaviorsPrefs.cpp +++ b/src/prefs/TracksBehaviorsPrefs.cpp @@ -62,7 +62,7 @@ void TracksBehaviorsPrefs::PopulateOrExchange(ShuttleGui & S) { S.TieCheckBox(_("A&uto-select audio for editing"), wxT("/GUI/SelectAllOnNone"), - true); + false); /* i18n-hint: Cut-lines are lines that can expand to show the cut audio.*/ S.TieCheckBox(_("Enable cut &lines"), wxT("/GUI/EnableCutLines"), diff --git a/src/toolbars/EditToolBar.cpp b/src/toolbars/EditToolBar.cpp index 3ebd3d8b2..bfbef2e54 100644 --- a/src/toolbars/EditToolBar.cpp +++ b/src/toolbars/EditToolBar.cpp @@ -198,150 +198,94 @@ void EditToolBar::UpdatePrefs() void EditToolBar::RegenerateTooltips() { -#if wxUSE_TOOLTIPS - static const struct Entry { - int tool; - wxString commandName; - wxString untranslatedLabel; - } table[] = { - { ETBCutID, wxT("Cut"), XO("Cut") }, - { ETBCopyID, wxT("Copy"), XO("Copy") }, - { ETBPasteID, wxT("Paste"), XO("Paste") }, - { ETBTrimID, wxT("Trim"), XO("Trim Audio") }, - { ETBSilenceID, wxT("Silence"), XO("Silence Audio") }, - { ETBUndoID, wxT("Undo"), XO("Undo") }, - { ETBRedoID, wxT("Redo"), XO("Redo") }, - -#ifdef OPTION_SYNC_LOCK_BUTTON - { ETBSyncLockID, wxT("SyncLock"), XO("Sync-Lock Tracks") }, -#endif - - { ETBZoomInID, wxT("ZoomIn"), XO("Zoom In") }, - { ETBZoomOutID, wxT("ZoomOut"), XO("Zoom Out") }, - { ETBZoomSelID, wxT("ZoomSel"), XO("Fit Selection") }, - { ETBZoomFitID, wxT("FitInWindow"), XO("Fit Project") }, - -#if defined(EXPERIMENTAL_EFFECTS_RACK) - { ETBEffectsID, wxT(""), XO("Open Effects Rack") }, -#endif - }; - - std::vector commands; - for (const auto &entry : table) { - commands.clear(); - commands.push_back(wxGetTranslation(entry.untranslatedLabel)); - commands.push_back(entry.commandName); - ToolBar::SetButtonToolTip(*mButtons[entry.tool], commands); - } -#endif -} - -void EditToolBar::OnButton(wxCommandEvent &event) -{ - AudacityProject *p = GetActiveProject(); - if (!p) return; - - int id = event.GetId(); - // FIXME: Some "SelectAllIfNone()" do not work as expected - // due to bugs elsewhere (see: AudacityProject::UpdateMenus() ) - - // Be sure the pop-up happens even if there are exceptions - // except for buttons which toggle. - auto cleanup = finally( [&] { mButtons[id]->InteractionOver();} - ); - - switch (id) { - case ETBCutID: - p->SelectAllIfNone(); - p->OnCut(); - break; - case ETBCopyID: - p->SelectAllIfNone(); - p->OnCopy(); - break; - case ETBPasteID: - p->OnPaste(); - break; - case ETBTrimID: - p->SelectAllIfNone(); - p->OnTrim(); - break; - case ETBSilenceID: - p->SelectAllIfNone(); - p->OnSilence(); - break; - case ETBUndoID: - p->OnUndo(); - break; - case ETBRedoID: - p->OnRedo(); - break; -#ifdef OPTION_SYNC_LOCK_BUTTON - case ETBSyncLockID: - p->OnSyncLock(); - break; -#endif - case ETBZoomInID: - p->OnZoomIn(); - break; - case ETBZoomOutID: - p->OnZoomOut(); - break; - -#if 0 // Disabled for version 1.2.0 because we have many other zoomers. - case ETBZoomToggleID: - p->OnZoomToggle(); - break; -#endif - - case ETBZoomSelID: - p->OnZoomSel(); - break; - case ETBZoomFitID: - p->OnZoomFit(); - break; -#if defined(EXPERIMENTAL_EFFECTS_RACK) - case ETBEffectsID: - EffectManager::Get().ShowRack(); - break; -#endif - } + ForAllButtons( ETBActTooltips ); } void EditToolBar::EnableDisableButtons() { + ForAllButtons( ETBActEnableDisable ); +} + + +static const struct Entry { + int tool; + wxString commandName; + wxString untranslatedLabel; +} EditToolbarButtonList[] = { + { ETBCutID, wxT("Cut"), XO("Cut") }, + { ETBCopyID, wxT("Copy"), XO("Copy") }, + { ETBPasteID, wxT("Paste"), XO("Paste") }, + { ETBTrimID, wxT("Trim"), XO("Trim audio outside selection") }, + { ETBSilenceID, wxT("Silence"), XO("Silence audio selection") }, + { ETBUndoID, wxT("Undo"), XO("Undo") }, + { ETBRedoID, wxT("Redo"), XO("Redo") }, + +#ifdef OPTION_SYNC_LOCK_BUTTON + { ETBSyncLockID, wxT("SyncLock"), XO("Sync-Lock Tracks") }, +#endif + + { ETBZoomInID, wxT("ZoomIn"), XO("Zoom In") }, + { ETBZoomOutID, wxT("ZoomOut"), XO("Zoom Out") }, + { ETBZoomSelID, wxT("ZoomSel"), XO("Fit selection in window") }, + { ETBZoomFitID, wxT("FitInWindow"), XO("Fit project in window") }, + +#if defined(EXPERIMENTAL_EFFECTS_RACK) + { ETBEffectsID, wxT(""), XO("Open Effects Rack") }, +#endif +}; + + +void EditToolBar::ForAllButtons(int Action) +{ + AudacityProject *p; + CommandManager* cm; + + if( Action & ETBActEnableDisable ){ + p = GetActiveProject(); + if (!p) return; + cm = p->GetCommandManager(); + if (!cm) return; +#ifdef OPTION_SYNC_LOCK_BUTTON + bool bSyncLockTracks; + gPrefs->Read(wxT("/GUI/SyncLockTracks"), &bSyncLockTracks, false); + + if (bSyncLockTracks) + mButtons[ETBSyncLockID]->PushDown(); + else + mButtons[ETBSyncLockID]->PopUp(); +#endif + } + + + std::vector commands; + for (const auto &entry : EditToolbarButtonList) { +#if wxUSE_TOOLTIPS + if( Action & ETBActTooltips ){ + commands.clear(); + commands.push_back(wxGetTranslation(entry.untranslatedLabel)); + commands.push_back(entry.commandName); + ToolBar::SetButtonToolTip(*mButtons[entry.tool], commands); + } +#endif + if( Action & ETBActEnableDisable ){ + mButtons[entry.tool]->SetEnabled(cm->GetEnabled(entry.commandName)); + } + } +} + +void EditToolBar::OnButton(wxCommandEvent &event) +{ + int id = event.GetId(); + // Be sure the pop-up happens even if there are exceptions, except for buttons which toggle. + auto cleanup = finally( [&] { mButtons[id]->InteractionOver();}); + AudacityProject *p = GetActiveProject(); if (!p) return; CommandManager* cm = p->GetCommandManager(); if (!cm) return; - mButtons[ETBCutID]->SetEnabled(cm->GetEnabled("Cut")); - mButtons[ETBCopyID]->SetEnabled(cm->GetEnabled("Copy")); - mButtons[ETBTrimID]->SetEnabled(cm->GetEnabled("Trim")); - mButtons[ETBSilenceID]->SetEnabled(cm->GetEnabled("Silence")); - - mButtons[ETBUndoID]->SetEnabled(cm->GetEnabled("Undo")); - mButtons[ETBRedoID]->SetEnabled(cm->GetEnabled("Redo")); - - mButtons[ETBZoomInID]->SetEnabled(cm->GetEnabled("ZoomIn")); - mButtons[ETBZoomOutID]->SetEnabled(cm->GetEnabled("ZoomOut")); - - #if 0 // Disabled for version 1.2.0 since it doesn't work quite right... - mButtons[ETBZoomToggleID]->SetEnabled(true); - #endif - - mButtons[ETBZoomSelID]->SetEnabled(cm->GetEnabled("ZoomSel")); - mButtons[ETBZoomFitID]->SetEnabled(cm->GetEnabled("FitInWindow")); - - mButtons[ETBPasteID]->SetEnabled(cm->GetEnabled("Paste")); - -#ifdef OPTION_SYNC_LOCK_BUTTON - bool bSyncLockTracks; - gPrefs->Read(wxT("/GUI/SyncLockTracks"), &bSyncLockTracks, false); - - if (bSyncLockTracks) - mButtons[ETBSyncLockID]->PushDown(); - else - mButtons[ETBSyncLockID]->PopUp(); -#endif + auto flags = p->GetUpdateFlags(); + cm->HandleTextualCommand(EditToolbarButtonList[id].commandName, flags, NoFlagsSpecifed); } + + diff --git a/src/toolbars/EditToolBar.h b/src/toolbars/EditToolBar.h index 82d3257db..e6c8d64f5 100644 --- a/src/toolbars/EditToolBar.h +++ b/src/toolbars/EditToolBar.h @@ -63,6 +63,12 @@ enum { ETBNumButtons }; +// flags so 1,2,4,8 etc. +enum { + ETBActTooltips = 1, + ETBActEnableDisable = 2, +}; + class EditToolBar final : public ToolBar { public: @@ -89,6 +95,7 @@ class EditToolBar final : public ToolBar { void MakeButtons(); void RegenerateTooltips() override; + void ForAllButtons(int Action); AButton *mButtons[ETBNumButtons];