diff --git a/src/LabelTrack.cpp b/src/LabelTrack.cpp index f1b872841..c47065617 100644 --- a/src/LabelTrack.cpp +++ b/src/LabelTrack.cpp @@ -1012,7 +1012,7 @@ void LabelTrack::calculateFontHeight(wxDC & dc) const mFontHeight += CursorExtraHeight - (charLeading+charDescent); } -bool LabelTrack::IsTextSelected() +bool LabelTrack::IsTextSelected() const { if (mSelIndex == -1) return false; diff --git a/src/LabelTrack.h b/src/LabelTrack.h index e794cb075..bdd90d529 100644 --- a/src/LabelTrack.h +++ b/src/LabelTrack.h @@ -117,7 +117,7 @@ class AUDACITY_DLL_API LabelTrack final : public Track bool IsGoodLabelFirstKey(const wxKeyEvent & evt); bool IsGoodLabelEditKey(const wxKeyEvent & evt); - bool IsTextSelected(); + bool IsTextSelected() const; void CreateCustomGlyphs(); LabelTrack(const std::shared_ptr &projDirManager); @@ -228,6 +228,7 @@ class AUDACITY_DLL_API LabelTrack final : public Track int GetNumLabels() const; const LabelStruct *GetLabel(int index) const; + const LabelArray &GetLabels() const { return mLabels; } //This returns the index of the label we just added. int AddLabel(const SelectedRegion ®ion, const wxString &title = {}, diff --git a/src/Menus.cpp b/src/Menus.cpp index a97079400..767638328 100644 --- a/src/Menus.cpp +++ b/src/Menus.cpp @@ -30,6 +30,8 @@ #include "Experimental.h" +#include + #include "AdornedRulerPanel.h" #include "AudioIO.h" #include "LabelTrack.h" @@ -414,55 +416,286 @@ CommandFlag MenuManager::GetFocusedFrame(AudacityProject &project) } -ReservedCommandFlag::ReservedCommandFlag() +// Really means, some track is selected, that isn't a time track +const auto TracksSelectedPred = + [](const AudacityProject &project){ + auto range = TrackList::Get( project ).Selected() + - []( const Track *pTrack ){ + return track_cast( pTrack ); }; + return !range.empty(); + }; + +const auto AudioIOBusyPred = + [](const AudacityProject &project ){ + return AudioIOBase::Get()->IsAudioTokenActive( + ProjectAudioIO::Get( project ).GetAudioIOToken()); + }; + +const auto TimeSelectedPred = + [](const AudacityProject &project){ + // This is equivalent to check if there is a valid selection, + // so it's used for Zoom to Selection too + return !ViewInfo::Get( project ).selectedRegion.isPoint(); + }; + +namespace{ + using Predicates = std::vector< ReservedCommandFlag::Predicate >; + Predicates &RegisteredPredicates() + { + static Predicates thePredicates; + return thePredicates; + } +} + +ReservedCommandFlag::ReservedCommandFlag( const Predicate &predicate ) { static size_t sNextReservedFlag = 0; // This will throw std::out_of_range if the constant NCommandFlags is too // small set( sNextReservedFlag++ ); + RegisteredPredicates().emplace_back( predicate ); } const ReservedCommandFlag - AudioIONotBusyFlag, - TimeSelectedFlag, // This is equivalent to check if there is a valid selection, so it's used for Zoom to Selection too - TracksSelectedFlag, - TracksExistFlag, - LabelTracksExistFlag, - WaveTracksSelectedFlag, - ClipboardFlag, - TextClipFlag, // Same as Clipboard flag for now. - UnsavedChangesFlag, - HasLastEffectFlag, - UndoAvailableFlag, - RedoAvailableFlag, - ZoomInAvailableFlag, - ZoomOutAvailableFlag, - StereoRequiredFlag, //lda - TopDockHasFocus, //lll - TrackPanelHasFocus, //lll - BotDockHasFocus, //lll - LabelsSelectedFlag, - AudioIOBusyFlag, //lll - PlayRegionLockedFlag, //msmeyer - PlayRegionNotLockedFlag, //msmeyer - CutCopyAvailableFlag, - WaveTracksExistFlag, - NoteTracksExistFlag, //gsw - NoteTracksSelectedFlag, //gsw - HaveRecentFiles, - IsNotSyncLockedFlag, //awd - IsSyncLockedFlag, //awd - IsRealtimeNotActiveFlag, //lll - CaptureNotBusyFlag, - CanStopAudioStreamFlag, - RulerHasFocus, // prl - NotMinimizedFlag, // prl - PausedFlag, // jkc - NotPausedFlag, // jkc - HasWaveDataFlag, // jkc - PlayableTracksExistFlag, - AudioTracksSelectedFlag, - NoAutoSelect // jkc + AudioIONotBusyFlag{ + [](const AudacityProject &project ){ + return !AudioIOBusyPred( project ); + } + }, + TimeSelectedFlag{ + TimeSelectedPred + }, + TracksSelectedFlag{ + TracksSelectedPred + }, + TracksExistFlag{ + [](const AudacityProject &project){ + return !TrackList::Get( project ).Any().empty(); + } + }, + LabelTracksExistFlag{ + [](const AudacityProject &project){ + return !TrackList::Get( project ).Any().empty(); + } + }, + WaveTracksSelectedFlag{ + [](const AudacityProject &project){ + return !TrackList::Get( project ).Selected().empty(); + } + }, + UnsavedChangesFlag{ + [](const AudacityProject &project){ + auto &undoManager = UndoManager::Get( project ); + return + undoManager.UnsavedChanges() + || + !ProjectFileIO::Get( project ).IsProjectSaved() + ; + } + }, + HasLastEffectFlag{ + [](const AudacityProject &project){ + return !MenuManager::Get( project ).mLastEffect.empty(); + } + }, + UndoAvailableFlag{ + [](const AudacityProject &project){ + return ProjectHistory::Get( project ).UndoAvailable(); + } + }, + RedoAvailableFlag{ + [](const AudacityProject &project){ + return ProjectHistory::Get( project ).RedoAvailable(); + } + }, + ZoomInAvailableFlag{ + [](const AudacityProject &project){ + return + ViewInfo::Get( project ).ZoomInAvailable() + && + !TrackList::Get( project ).Any().empty() + ; + } + }, + ZoomOutAvailableFlag{ + [](const AudacityProject &project){ + return + ViewInfo::Get( project ).ZoomOutAvailable() + && + !TrackList::Get( project ).Any().empty() + ; + } + }, + StereoRequiredFlag{ + [](const AudacityProject &project){ + // True iff at least one stereo track is selected, i.e., at least + // one right channel is selected. + // TODO: more-than-two-channels + auto range = TrackList::Get( project ).Selected() + - &Track::IsLeader; + return !range.empty(); + } + }, //lda + TrackPanelHasFocus{ + [](const AudacityProject &project){ + for (auto w = wxWindow::FindFocus(); w; w = w->GetParent()) { + if (dynamic_cast(w)) + return true; + } + return false; + } + }, //lll + LabelsSelectedFlag{ + [](const AudacityProject &project){ + // At least one label track selected, having at least one label + // completely within the time selection. + const auto &selectedRegion = ViewInfo::Get( project ).selectedRegion; + const auto &test = [&]( const LabelTrack *pTrack ){ + const auto &labels = pTrack->GetLabels(); + return std::any_of( labels.begin(), labels.end(), + [&](const LabelStruct &label){ + return + label.getT0() >= selectedRegion.t0() + && + label.getT1() <= selectedRegion.t1() + ; + } + ); + }; + auto range = TrackList::Get( project ).Selected() + + test; + return !range.empty(); + } + }, + AudioIOBusyFlag{ + AudioIOBusyPred + }, //lll + PlayRegionLockedFlag{ + [](const AudacityProject &project){ + return ViewInfo::Get(project).playRegion.Locked(); + } + }, //msmeyer + PlayRegionNotLockedFlag{ + [](const AudacityProject &project){ + const auto &playRegion = ViewInfo::Get(project).playRegion; + return !playRegion.Locked() && !playRegion.Empty(); + } + }, //msmeyer + CutCopyAvailableFlag{ + [](const AudacityProject &project){ + auto range = TrackList::Get( project ).Any() + + &LabelTrack::IsTextSelected; + if ( !range.empty() ) + return true; + + if ( + !AudioIOBusyPred( project ) + && + TimeSelectedPred( project ) + && + TracksSelectedPred( project ) + ) + return true; + + return false; + } + }, + WaveTracksExistFlag{ + [](const AudacityProject &project){ + return !TrackList::Get( project ).Any().empty(); + } + }, + NoteTracksExistFlag{ + [](const AudacityProject &project){ + return !TrackList::Get( project ).Any().empty(); + } + }, //gsw + NoteTracksSelectedFlag{ + [](const AudacityProject &project){ + return !TrackList::Get( project ).Selected().empty(); + } + }, //gsw + IsNotSyncLockedFlag{ + [](const AudacityProject &project){ + return !ProjectSettings::Get( project ).IsSyncLocked(); + } + }, //awd + IsSyncLockedFlag{ + [](const AudacityProject &project){ + return ProjectSettings::Get( project ).IsSyncLocked(); + } + }, //awd + IsRealtimeNotActiveFlag{ + [](const AudacityProject &){ + return !EffectManager::Get().RealtimeIsActive(); + } + }, //lll + CaptureNotBusyFlag{ + [](const AudacityProject &){ + auto gAudioIO = AudioIO::Get(); + return !( + gAudioIO->IsBusy() && + gAudioIO->GetNumCaptureChannels() > 0 + ); + } + }, + CanStopAudioStreamFlag{ + [](const AudacityProject &project){ + return ControlToolBar::Get( project ).CanStopAudioStream(); + } + }, + NotMinimizedFlag{ + [](const AudacityProject &project){ + const wxWindow *focus = FindProjectFrame( &project ); + if (focus) { + while (focus && focus->GetParent()) + focus = focus->GetParent(); + } + return (focus && + !static_cast(focus)->IsIconized() + ); + } + }, // prl + PausedFlag{ + [](const AudacityProject&){ + return AudioIOBase::Get()->IsPaused(); + } + }, + HasWaveDataFlag{ + [](const AudacityProject &project){ + auto range = TrackList::Get( project ).Any() + + [](const WaveTrack *pTrack){ + return pTrack->GetEndTime() > pTrack->GetStartTime(); + }; + return !range.empty(); + } + }, // jkc + PlayableTracksExistFlag{ + [](const AudacityProject &project){ + auto &tracks = TrackList::Get( project ); + return +#ifdef EXPERIMENTAL_MIDI_OUT + !tracks.Any().empty() + || +#endif + !tracks.Any().empty() + ; + } + }, + AudioTracksSelectedFlag{ + [](const AudacityProject &project){ + auto &tracks = TrackList::Get( project ); + return + !tracks.Selected().empty() + // even if not EXPERIMENTAL_MIDI_OUT + || + tracks.Selected().empty() + ; + } + }, + NoAutoSelect{ + [](const AudacityProject &){ return false; } + } // jkc ; CommandFlag MenuManager::GetUpdateFlags( bool checkActive ) diff --git a/src/ProjectFileIO.cpp b/src/ProjectFileIO.cpp index a45f031c0..918ea8902 100644 --- a/src/ProjectFileIO.cpp +++ b/src/ProjectFileIO.cpp @@ -642,7 +642,7 @@ void ProjectFileIO::DeleteCurrentAutoSaveFile() } } -bool ProjectFileIO::IsProjectSaved() { +bool ProjectFileIO::IsProjectSaved() const { auto &project = mProject; auto &dirManager = DirManager::Get( project ); // This is true if a project was opened from an .aup diff --git a/src/ProjectFileIO.h b/src/ProjectFileIO.h index 3130c09e6..c13f9d49a 100644 --- a/src/ProjectFileIO.h +++ b/src/ProjectFileIO.h @@ -40,7 +40,7 @@ public: // recovery file void SetProjectTitle( int number = -1 ); - bool IsProjectSaved(); + bool IsProjectSaved() const; void Reset(); diff --git a/src/ProjectHistory.cpp b/src/ProjectHistory.cpp index 32356d29a..f5674dda3 100644 --- a/src/ProjectHistory.cpp +++ b/src/ProjectHistory.cpp @@ -59,7 +59,7 @@ void ProjectHistory::InitialState() undoManager.StateSaved(); } -bool ProjectHistory::UndoAvailable() +bool ProjectHistory::UndoAvailable() const { auto &project = mProject; auto &tracks = TrackList::Get( project ); @@ -68,7 +68,7 @@ bool ProjectHistory::UndoAvailable() !tracks.HasPendingTracks(); } -bool ProjectHistory::RedoAvailable() +bool ProjectHistory::RedoAvailable() const { auto &project = mProject; auto &tracks = TrackList::Get( project ); diff --git a/src/ProjectHistory.h b/src/ProjectHistory.h index d425854e4..6e86d626e 100644 --- a/src/ProjectHistory.h +++ b/src/ProjectHistory.h @@ -31,8 +31,8 @@ public: void InitialState(); void SetStateTo(unsigned int n); - bool UndoAvailable(); - bool RedoAvailable(); + bool UndoAvailable() const; + bool RedoAvailable() const; void PushState(const wxString &desc, const wxString &shortDesc); // use UndoPush::AUTOSAVE void PushState(const wxString &desc, const wxString &shortDesc, UndoPush flags); void RollbackState(); diff --git a/src/UndoManager.cpp b/src/UndoManager.cpp index fa6ab15fa..c552581a3 100644 --- a/src/UndoManager.cpp +++ b/src/UndoManager.cpp @@ -388,7 +388,7 @@ void UndoManager::Redo(const Consumer &consumer) mProject.QueueEvent( safenew wxCommandEvent{ EVT_UNDO_OR_REDO } ); } -bool UndoManager::UnsavedChanges() +bool UndoManager::UnsavedChanges() const { return (saved != current) || HasODChangesFlag(); } @@ -418,7 +418,7 @@ void UndoManager::SetODChangesFlag() mODChangesMutex.Unlock(); } -bool UndoManager::HasODChangesFlag() +bool UndoManager::HasODChangesFlag() const { bool ret; mODChangesMutex.Lock(); diff --git a/src/UndoManager.h b/src/UndoManager.h index fa383e3ce..a7b9b475b 100644 --- a/src/UndoManager.h +++ b/src/UndoManager.h @@ -152,7 +152,7 @@ class AUDACITY_DLL_API UndoManager bool UndoAvailable(); bool RedoAvailable(); - bool UnsavedChanges(); + bool UnsavedChanges() const; void StateSaved(); // Return value must first be calculated by CalculateSpaceUsage(): @@ -167,7 +167,7 @@ class AUDACITY_DLL_API UndoManager ///to mark as unsaved changes without changing the state/tracks. void SetODChangesFlag(); - bool HasODChangesFlag(); + bool HasODChangesFlag() const; void ResetODChangesFlag(); private: @@ -184,7 +184,7 @@ class AUDACITY_DLL_API UndoManager unsigned long long mClipboardSpaceUsage {}; bool mODChanges; - ODLock mODChangesMutex;//mODChanges is accessed from many threads. + mutable ODLock mODChangesMutex;//mODChanges is accessed from many threads. }; diff --git a/src/commands/CommandFlag.h b/src/commands/CommandFlag.h index f570abaa6..7260314a4 100644 --- a/src/commands/CommandFlag.h +++ b/src/commands/CommandFlag.h @@ -12,6 +12,9 @@ // Flags used in command handling. #include +#include + +class AudacityProject; // Increase the template parameter as needed to allow more flags constexpr size_t NCommandFlags = 64; @@ -30,10 +33,12 @@ constexpr CommandFlag NoFlagsSpecified{ ~0ULL }; // all ones // Construct one statically to register (and reserve) a bit position in the set +// an associate it with a test function class ReservedCommandFlag : public CommandFlag { public: - ReservedCommandFlag(); + using Predicate = std::function< bool( const AudacityProject& ) >; + ReservedCommandFlag( const Predicate & ); }; // Widely used command flags, but this list need not be exhaustive. It may be diff --git a/src/toolbars/ControlToolBar.cpp b/src/toolbars/ControlToolBar.cpp index 5c4541dff..adcd3ae9e 100644 --- a/src/toolbars/ControlToolBar.cpp +++ b/src/toolbars/ControlToolBar.cpp @@ -824,7 +824,7 @@ void ControlToolBar::OnStop(wxCommandEvent & WXUNUSED(evt)) } } -bool ControlToolBar::CanStopAudioStream() +bool ControlToolBar::CanStopAudioStream() const { auto gAudioIO = AudioIO::Get(); return (!gAudioIO->IsStreamActive() || diff --git a/src/toolbars/ControlToolBar.h b/src/toolbars/ControlToolBar.h index 6324bce82..d4d438490 100644 --- a/src/toolbars/ControlToolBar.h +++ b/src/toolbars/ControlToolBar.h @@ -99,7 +99,7 @@ class ControlToolBar final : public ToolBar { bool IsRecordDown() const; // A project is only allowed to stop an audio stream that it owns. - bool CanStopAudioStream (); + bool CanStopAudioStream () const; // Play currently selected region, or if nothing selected, // play from current cursor. diff --git a/src/toolbars/ToolManager.cpp b/src/toolbars/ToolManager.cpp index 35e2d79e9..696c9d0cf 100644 --- a/src/toolbars/ToolManager.cpp +++ b/src/toolbars/ToolManager.cpp @@ -1022,6 +1022,11 @@ ToolDock *ToolManager::GetTopDock() return mTopDock; } +const ToolDock *ToolManager::GetTopDock() const +{ + return mTopDock; +} + // // Return a pointer to the bottom dock // @@ -1030,6 +1035,11 @@ ToolDock *ToolManager::GetBotDock() return mBotDock; } +const ToolDock *ToolManager::GetBotDock() const +{ + return mBotDock; +} + // // Queues an EVT_TOOLBAR_UPDATED command event to notify any // interest parties of an updated toolbar or dock layout diff --git a/src/toolbars/ToolManager.h b/src/toolbars/ToolManager.h index 673b76caf..820512645 100644 --- a/src/toolbars/ToolManager.h +++ b/src/toolbars/ToolManager.h @@ -68,7 +68,9 @@ class ToolManager final ToolBar *GetToolBar( int type ) const; ToolDock *GetTopDock(); + const ToolDock *GetTopDock() const; ToolDock *GetBotDock(); + const ToolDock *GetBotDock() const; void Reset(); void Destroy();