From 95cca91eb16763a452bd536a75f43b7e22dc67e6 Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Sat, 27 Oct 2018 19:00:21 -0400 Subject: [PATCH 1/6] Move AudacityProject::Clear --- src/Project.cpp | 20 -------------------- src/Project.h | 2 -- src/menus/EditMenus.cpp | 21 ++++++++++++++++++++- 3 files changed, 20 insertions(+), 23 deletions(-) diff --git a/src/Project.cpp b/src/Project.cpp index 236f7c8d9..bbeb92877 100644 --- a/src/Project.cpp +++ b/src/Project.cpp @@ -4739,26 +4739,6 @@ void AudacityProject::ClearClipboard() } } -void AudacityProject::Clear() -{ - for (auto n : GetTracks()->Any()) { - if (n->GetSelected() || n->IsSyncLockSelected()) { - n->Clear(mViewInfo.selectedRegion.t0(), mViewInfo.selectedRegion.t1()); - } - } - - double seconds = mViewInfo.selectedRegion.duration(); - - mViewInfo.selectedRegion.collapseToT0(); - - PushState(wxString::Format(_("Deleted %.2f seconds at t=%.2f"), - seconds, - mViewInfo.selectedRegion.t0()), - _("Delete")); - - RedrawProject(); -} - // Utility function called by other zoom methods void AudacityProject::Zoom(double level) { diff --git a/src/Project.h b/src/Project.h index 9efd87d5d..4c3e6c72e 100644 --- a/src/Project.h +++ b/src/Project.h @@ -318,8 +318,6 @@ private: bool DoSave(bool fromSaveAs, bool bWantSaveCopy, bool bLossless = false); public: - void Clear();// clears a selection - const wxString &GetFileName() { return mFileName; } bool GetDirty() { return mDirty; } void SetProjectTitle( int number =-1); diff --git a/src/menus/EditMenus.cpp b/src/menus/EditMenus.cpp index a69f228a6..f463de620 100644 --- a/src/menus/EditMenus.cpp +++ b/src/menus/EditMenus.cpp @@ -364,9 +364,28 @@ void OnCut(const CommandContext &context) void OnDelete(const CommandContext &context) { auto &project = context.project; - project.Clear(); + auto &tracks = *project.GetTracks(); + auto &selectedRegion = project.GetViewInfo().selectedRegion; + + for (auto n : tracks.Any()) { + if (n->GetSelected() || n->IsSyncLockSelected()) { + n->Clear(selectedRegion.t0(), selectedRegion.t1()); + } + } + + double seconds = selectedRegion.duration(); + + selectedRegion.collapseToT0(); + + project.PushState(wxString::Format(_("Deleted %.2f seconds at t=%.2f"), + seconds, + selectedRegion.t0()), + _("Delete")); + + project.RedrawProject(); } + void OnCopy(const CommandContext &context) { auto &project = context.project; From b2ea8ab6e97ce3f6b0a6e0554f13b6ab07283521 Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Sat, 27 Oct 2018 19:21:54 -0400 Subject: [PATCH 2/6] Move AudacityProject::EditByLabel and EditClipboardByLabel --- src/Project.cpp | 149 ------------------------------ src/Project.h | 10 -- src/WaveTrack.h | 2 +- src/menus/LabelMenus.cpp | 193 ++++++++++++++++++++++++++++++++++++--- 4 files changed, 183 insertions(+), 171 deletions(-) diff --git a/src/Project.cpp b/src/Project.cpp index bbeb92877..90de2b4c9 100644 --- a/src/Project.cpp +++ b/src/Project.cpp @@ -4989,155 +4989,6 @@ void AudacityProject::OnTimer(wxTimerEvent& WXUNUSED(event)) RestartTimer(); } -//get regions selected by selected labels -//removes unnecessary regions, overlapping regions are merged -//regions memory need to be deleted by the caller -void AudacityProject::GetRegionsByLabel( Regions ®ions ) -{ - //determine labeled regions - for (auto lt : GetTracks()->Selected< LabelTrack >()) { - for (int i = 0; i < lt->GetNumLabels(); i++) - { - const LabelStruct *ls = lt->GetLabel(i); - if (ls->selectedRegion.t0() >= mViewInfo.selectedRegion.t0() && - ls->selectedRegion.t1() <= mViewInfo.selectedRegion.t1()) - regions.push_back(Region(ls->getT0(), ls->getT1())); - } - } - - //anything to do ? - if( regions.size() == 0 ) - return; - - //sort and remove unnecessary regions - std::sort(regions.begin(), regions.end()); - unsigned int selected = 1; - while( selected < regions.size() ) - { - const Region &cur = regions.at( selected ); - Region &last = regions.at( selected - 1 ); - if( cur.start < last.end ) - { - if( cur.end > last.end ) - last.end = cur.end; - regions.erase( regions.begin() + selected ); - } - else - selected++; - } -} - -//Executes the edit function on all selected wave tracks with -//regions specified by selected labels -//If No tracks selected, function is applied on all tracks -//If the function replaces the selection with audio of a different length, -// bSyncLockedTracks should be set true to perform the same action on sync-lock selected -// tracks. -void AudacityProject::EditByLabel( EditFunction action, - bool bSyncLockedTracks ) -{ - Regions regions; - - GetRegionsByLabel( regions ); - if( regions.size() == 0 ) - return; - - // if at least one wave track is selected - // apply only on the selected track - const bool allTracks = (GetTracks()->Selected< WaveTrack >()).empty(); - - //Apply action on wavetracks starting from - //labeled regions in the end. This is to correctly perform - //actions like 'Delete' which collapse the track area. - for (auto wt : GetTracks()->Any()) - { - if (allTracks || wt->GetSelected() || (bSyncLockedTracks && wt->IsSyncLockSelected())) - { - for (int i = (int)regions.size() - 1; i >= 0; i--) { - const Region ®ion = regions.at(i); - (wt->*action)(region.start, region.end); - } - } - } -} - -//Executes the edit function on all selected wave tracks with -//regions specified by selected labels -//If No tracks selected, function is applied on all tracks -//Functions copy the edited regions to clipboard, possibly in multiple tracks -//This probably should not be called if *action() changes the timeline, because -// the copy needs to happen by track, and the timeline change by group. -void AudacityProject::EditClipboardByLabel( EditDestFunction action ) -{ - Regions regions; - - GetRegionsByLabel( regions ); - if( regions.size() == 0 ) - return; - - // if at least one wave track is selected - // apply only on the selected track - const bool allTracks = (GetTracks()->Selected< WaveTrack >()).empty(); - - ClearClipboard(); - - auto pNewClipboard = TrackList::Create(); - auto &newClipboard = *pNewClipboard; - - //Apply action on wavetracks starting from - //labeled regions in the end. This is to correctly perform - //actions like 'Cut' which collapse the track area. - - for(auto wt : - GetTracks()->Any() - + (allTracks ? &Track::Any : &Track::IsSelected) - ) { - // This track accumulates the needed clips, right to left: - Track::Holder merged; - for( int i = (int)regions.size() - 1; i >= 0; i-- ) - { - const Region ®ion = regions.at(i); - auto dest = ( wt->*action )( region.start, region.end ); - if( dest ) - { - Track::FinishCopy( wt, dest.get() ); - if( !merged ) - merged = std::move(dest); - else - { - // Paste to the beginning; unless this is the first region, - // offset the track to account for time between the regions - if (i < (int)regions.size() - 1) - merged->Offset( - regions.at(i + 1).start - region.end); - - // dest may have a placeholder clip at the end that is - // removed when pasting, which is okay because we proceed - // right to left. Any placeholder already in merged is kept. - // Only the rightmost placeholder is important in the final - // result. - merged->Paste( 0.0 , dest.get() ); - } - } - else // nothing copied but there is a 'region', so the 'region' must be a 'point label' so offset - if (i < (int)regions.size() - 1) - if (merged) - merged->Offset( - regions.at(i + 1).start - region.end); - } - if( merged ) - newClipboard.Add( std::move(merged) ); - } - - // Survived possibility of exceptions. Commit changes to the clipboard now. - newClipboard.Swap(*msClipboard); - wxTheApp->AddPendingEvent( wxCommandEvent{ EVT_CLIPBOARD_CHANGE } ); - - msClipT0 = regions.front().start; - msClipT1 = regions.back().end; -} - - // TrackPanel callback method void AudacityProject::TP_DisplayStatusMessage(const wxString &msg) { diff --git a/src/Project.h b/src/Project.h index 4c3e6c72e..63d957260 100644 --- a/src/Project.h +++ b/src/Project.h @@ -93,8 +93,6 @@ class MixerBoardFrame; struct AudioIOStartStreamOptions; struct UndoState; -class Regions; - class LWSlider; class UndoManager; enum class UndoPush : unsigned char; @@ -424,12 +422,6 @@ public: void SkipEnd(bool shift); - typedef void (WaveTrack::* EditFunction)(double, double); - typedef std::unique_ptr (WaveTrack::* EditDestFunction)(double, double); - - void EditByLabel(EditFunction action, bool bSyncLockedTracks); - void EditClipboardByLabel(EditDestFunction action ); - bool IsSyncLocked(); void SetSyncLock(bool flag); @@ -601,8 +593,6 @@ public: void PopState(const UndoState &state); - void GetRegionsByLabel( Regions ®ions ); - void AutoSave(); void DeleteCurrentAutoSaveFile(); diff --git a/src/WaveTrack.h b/src/WaveTrack.h index e9ecad0ec..c58c0531c 100644 --- a/src/WaveTrack.h +++ b/src/WaveTrack.h @@ -53,7 +53,7 @@ struct Region } }; -class Regions : public std::vector < Region > {}; +using Regions = std::vector < Region >; class Envelope; diff --git a/src/menus/LabelMenus.cpp b/src/menus/LabelMenus.cpp index 306b2a6e7..09c414ff2 100644 --- a/src/menus/LabelMenus.cpp +++ b/src/menus/LabelMenus.cpp @@ -1,3 +1,4 @@ +#include "../AudacityApp.h" // for EVT_CLIPBOARD_CHANGE #include "../AudioIO.h" #include "../LabelTrack.h" #include "../Menus.h" @@ -80,6 +81,165 @@ int DoAddLabel( return index; } +//get regions selected by selected labels +//removes unnecessary regions, overlapping regions are merged +void GetRegionsByLabel( + const TrackList &tracks, const SelectedRegion &selectedRegion, + Regions ®ions ) +{ + //determine labeled regions + for (auto lt : tracks.Selected< const LabelTrack >()) { + for (int i = 0; i < lt->GetNumLabels(); i++) + { + const LabelStruct *ls = lt->GetLabel(i); + if (ls->selectedRegion.t0() >= selectedRegion.t0() && + ls->selectedRegion.t1() <= selectedRegion.t1()) + regions.push_back(Region(ls->getT0(), ls->getT1())); + } + } + + //anything to do ? + if( regions.size() == 0 ) + return; + + //sort and remove unnecessary regions + std::sort(regions.begin(), regions.end()); + unsigned int selected = 1; + while( selected < regions.size() ) + { + const Region &cur = regions.at( selected ); + Region &last = regions.at( selected - 1 ); + if( cur.start < last.end ) + { + if( cur.end > last.end ) + last.end = cur.end; + regions.erase( regions.begin() + selected ); + } + else + ++selected; + } +} + +using EditFunction = void (WaveTrack::*)(double, double); + +//Executes the edit function on all selected wave tracks with +//regions specified by selected labels +//If No tracks selected, function is applied on all tracks +//If the function replaces the selection with audio of a different length, +// bSyncLockedTracks should be set true to perform the same action on sync-lock +// selected tracks. +void EditByLabel( + TrackList &tracks, const SelectedRegion &selectedRegion, + EditFunction action, bool bSyncLockedTracks ) +{ + Regions regions; + + GetRegionsByLabel( tracks, selectedRegion, regions ); + if( regions.size() == 0 ) + return; + + // if at least one wave track is selected + // apply only on the selected track + const bool allTracks = (tracks.Selected< WaveTrack >()).empty(); + + //Apply action on wavetracks starting from + //labeled regions in the end. This is to correctly perform + //actions like 'Delete' which collapse the track area. + for (auto wt : tracks.Any()) + { + if (allTracks || wt->GetSelected() || + (bSyncLockedTracks && wt->IsSyncLockSelected())) + { + for (int i = (int)regions.size() - 1; i >= 0; i--) { + const Region ®ion = regions.at(i); + (wt->*action)(region.start, region.end); + } + } + } +} + +using EditDestFunction = std::unique_ptr (WaveTrack::*)(double, double); + +//Executes the edit function on all selected wave tracks with +//regions specified by selected labels +//If No tracks selected, function is applied on all tracks +//Functions copy the edited regions to clipboard, possibly in multiple tracks +//This probably should not be called if *action() changes the timeline, because +// the copy needs to happen by track, and the timeline change by group. +void EditClipboardByLabel( + TrackList &tracks, const SelectedRegion &selectedRegion, + EditDestFunction action ) +{ + Regions regions; + + GetRegionsByLabel( tracks, selectedRegion, regions ); + if( regions.size() == 0 ) + return; + + // if at least one wave track is selected + // apply only on the selected track + const bool allTracks = (tracks.Selected< WaveTrack >()).empty(); + + AudacityProject::ClearClipboard(); + + auto pNewClipboard = TrackList::Create(); + auto &newClipboard = *pNewClipboard; + + //Apply action on wavetracks starting from + //labeled regions in the end. This is to correctly perform + //actions like 'Cut' which collapse the track area. + + for(auto wt : + tracks.Any() + + (allTracks ? &Track::Any : &Track::IsSelected) + ) { + // This track accumulates the needed clips, right to left: + Track::Holder merged; + for( int i = (int)regions.size() - 1; i >= 0; i-- ) + { + const Region ®ion = regions.at(i); + auto dest = ( wt->*action )( region.start, region.end ); + if( dest ) + { + Track::FinishCopy( wt, dest.get() ); + if( !merged ) + merged = std::move(dest); + else + { + // Paste to the beginning; unless this is the first region, + // offset the track to account for time between the regions + if (i < (int)regions.size() - 1) + merged->Offset( + regions.at(i + 1).start - region.end); + + // dest may have a placeholder clip at the end that is + // removed when pasting, which is okay because we proceed + // right to left. Any placeholder already in merged is kept. + // Only the rightmost placeholder is important in the final + // result. + merged->Paste( 0.0 , dest.get() ); + } + } + else + // nothing copied but there is a 'region', so the 'region' must + // be a 'point label' so offset + if (i < (int)regions.size() - 1) + if (merged) + merged->Offset( + regions.at(i + 1).start - region.end); + } + if( merged ) + newClipboard.Add( std::move(merged) ); + } + + // Survived possibility of exceptions. Commit changes to the clipboard now. + newClipboard.Swap(*AudacityProject::msClipboard); + wxTheApp->AddPendingEvent( wxCommandEvent{ EVT_CLIPBOARD_CHANGE } ); + + AudacityProject::msClipT0 = regions.front().start; + AudacityProject::msClipT1 = regions.back().end; +} + } namespace LabelEditActions { @@ -196,6 +356,7 @@ void OnToggleTypeToCreateLabel(const CommandContext &WXUNUSED(context) ) void OnCutLabels(const CommandContext &context) { auto &project = context.project; + auto &tracks = *project.GetTracks(); auto &selectedRegion = project.GetViewInfo().selectedRegion; if( selectedRegion.isPoint() ) @@ -203,12 +364,13 @@ void OnCutLabels(const CommandContext &context) // Because of grouping the copy may need to operate on different tracks than // the clear, so we do these actions separately. - project.EditClipboardByLabel( &WaveTrack::CopyNonconst ); + EditClipboardByLabel( tracks, selectedRegion, &WaveTrack::CopyNonconst ); if( gPrefs->Read( wxT( "/GUI/EnableCutLines" ), ( long )0 ) ) - project.EditByLabel( &WaveTrack::ClearAndAddCutLine, true ); + EditByLabel( + tracks, selectedRegion, &WaveTrack::ClearAndAddCutLine, true ); else - project.EditByLabel( &WaveTrack::Clear, true ); + EditByLabel( tracks, selectedRegion, &WaveTrack::Clear, true ); AudacityProject::msClipProject = &project; @@ -227,12 +389,13 @@ void OnCutLabels(const CommandContext &context) void OnDeleteLabels(const CommandContext &context) { auto &project = context.project; + auto &tracks = *project.GetTracks(); auto &selectedRegion = project.GetViewInfo().selectedRegion; if( selectedRegion.isPoint() ) return; - project.EditByLabel( &WaveTrack::Clear, true ); + EditByLabel( tracks, selectedRegion, &WaveTrack::Clear, true ); selectedRegion.collapseToT0(); @@ -248,12 +411,13 @@ void OnDeleteLabels(const CommandContext &context) void OnSplitCutLabels(const CommandContext &context) { auto &project = context.project; + auto &tracks = *project.GetTracks(); auto &selectedRegion = project.GetViewInfo().selectedRegion; if( selectedRegion.isPoint() ) return; - project.EditClipboardByLabel( &WaveTrack::SplitCut ); + EditClipboardByLabel( tracks, selectedRegion, &WaveTrack::SplitCut ); AudacityProject::msClipProject = &project; @@ -270,12 +434,13 @@ void OnSplitCutLabels(const CommandContext &context) void OnSplitDeleteLabels(const CommandContext &context) { auto &project = context.project; + auto &tracks = *project.GetTracks(); auto &selectedRegion = project.GetViewInfo().selectedRegion; if( selectedRegion.isPoint() ) return; - project.EditByLabel( &WaveTrack::SplitDelete, false ); + EditByLabel( tracks, selectedRegion, &WaveTrack::SplitDelete, false ); project.PushState( /* i18n-hint: (verb) Audacity has just done a special kind of DELETE on @@ -292,12 +457,13 @@ void OnSilenceLabels(const CommandContext &context) { auto &project = context.project; auto trackPanel = project.GetTrackPanel(); + auto &tracks = *project.GetTracks(); auto &selectedRegion = project.GetViewInfo().selectedRegion; if( selectedRegion.isPoint() ) return; - project.EditByLabel( &WaveTrack::Silence, false ); + EditByLabel( tracks, selectedRegion, &WaveTrack::Silence, false ); project.PushState( /* i18n-hint: (verb)*/ @@ -312,12 +478,13 @@ void OnCopyLabels(const CommandContext &context) { auto &project = context.project; auto trackPanel = project.GetTrackPanel(); + auto &tracks = *project.GetTracks(); auto &selectedRegion = project.GetViewInfo().selectedRegion; if( selectedRegion.isPoint() ) return; - project.EditClipboardByLabel( &WaveTrack::CopyNonconst ); + EditClipboardByLabel( tracks, selectedRegion, &WaveTrack::CopyNonconst ); AudacityProject::msClipProject = &project; @@ -331,8 +498,10 @@ void OnCopyLabels(const CommandContext &context) void OnSplitLabels(const CommandContext &context) { auto &project = context.project; + auto &tracks = *project.GetTracks(); + auto &selectedRegion = project.GetViewInfo().selectedRegion; - project.EditByLabel( &WaveTrack::Split, false ); + EditByLabel( tracks, selectedRegion, &WaveTrack::Split, false ); project.PushState( /* i18n-hint: (verb) past tense. Audacity has just split the labeled @@ -347,12 +516,13 @@ void OnSplitLabels(const CommandContext &context) void OnJoinLabels(const CommandContext &context) { auto &project = context.project; + auto &tracks = *project.GetTracks(); auto &selectedRegion = project.GetViewInfo().selectedRegion; if( selectedRegion.isPoint() ) return; - project.EditByLabel( &WaveTrack::Join, false ); + EditByLabel( tracks, selectedRegion, &WaveTrack::Join, false ); project.PushState( /* i18n-hint: (verb) Audacity has just joined the labeled audio (points or @@ -367,12 +537,13 @@ void OnJoinLabels(const CommandContext &context) void OnDisjoinLabels(const CommandContext &context) { auto &project = context.project; + auto &tracks = *project.GetTracks(); auto &selectedRegion = project.GetViewInfo().selectedRegion; if( selectedRegion.isPoint() ) return; - project.EditByLabel( &WaveTrack::Disjoin, false ); + EditByLabel( tracks, selectedRegion, &WaveTrack::Disjoin, false ); project.PushState( /* i18n-hint: (verb) Audacity has just detached the labeled audio regions. From edd23e263465c8145dea4933d533135e2a5061b4 Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Sat, 27 Oct 2018 19:47:48 -0400 Subject: [PATCH 3/6] Move AudacityProject::GetZoomOf* --- src/Menus.h | 1 + src/Project.cpp | 103 +--------------------------------- src/Project.h | 5 -- src/ViewInfo.cpp | 6 +- src/ViewInfo.h | 8 ++- src/menus/ViewMenus.cpp | 121 ++++++++++++++++++++++++++++++++++++++-- 6 files changed, 127 insertions(+), 117 deletions(-) diff --git a/src/Menus.h b/src/Menus.h index 6b4c592ea..9782fac77 100644 --- a/src/Menus.h +++ b/src/Menus.h @@ -118,6 +118,7 @@ void DoSelectSomething( AudacityProject &project ); } namespace ViewActions { +double GetZoomOfToFit( const AudacityProject &project ); void DoZoomFit( AudacityProject &project ); void DoZoomFitV( AudacityProject &project ); } diff --git a/src/Project.cpp b/src/Project.cpp index 90de2b4c9..bb9a74fb9 100644 --- a/src/Project.cpp +++ b/src/Project.cpp @@ -2951,7 +2951,7 @@ AudacityProject *AudacityProject::OpenProject( pProject->OpenFile( fileNameArg, addtohistory ); pNewProject = nullptr; if( pProject && pProject->mIsRecovered ) - pProject->Zoom( pProject->GetZoomOfToFit() ); + pProject->Zoom( ViewActions::GetZoomOfToFit( *pProject ) ); return pProject; } @@ -5679,107 +5679,6 @@ int AudacityProject::GetEstimatedRecordingMinsLeftOnDisk(long lCaptureChannels) } -double AudacityProject::GetZoomOfToFit(){ - const double end = mTracks->GetEndTime(); - const double start = mViewInfo.bScrollBeyondZero - ? std::min(mTracks->GetStartTime(), 0.0) - : 0; - const double len = end - start; - - if (len <= 0.0) - return mViewInfo.GetZoom(); - - int w; - mTrackPanel->GetTracksUsableArea(&w, NULL); - w -= 10; - return w/len; -} - -double AudacityProject::GetZoomOfSelection(){ - const double lowerBound = - std::max(mViewInfo.selectedRegion.t0(), ScrollingLowerBoundTime()); - const double denom = - mViewInfo.selectedRegion.t1() - lowerBound; - if (denom <= 0.0) - return mViewInfo.GetZoom(); - - // LL: The "-1" is just a hack to get around an issue where zooming to - // selection doesn't actually get the entire selected region within the - // visible area. This causes a problem with scrolling at end of playback - // where the selected region may be scrolled off the left of the screen. - // I know this isn't right, but until the real rounding or 1-off issue is - // found, this will have to work. - // PRL: Did I fix this? I am not sure, so I leave the hack in place. - // Fixes might have resulted from commits - // 1b8f44d0537d987c59653b11ed75a842b48896ea and - // e7c7bb84a966c3b3cc4b3a9717d5f247f25e7296 - int width; - mTrackPanel->GetTracksUsableArea(&width, NULL); - return (width - 1) / denom; -} - -double AudacityProject::GetZoomOfPreset( int preset ){ - - // Sets a limit on how far we will zoom out as a factor over zoom to fit. - const double maxZoomOutFactor = 4.0; - // Sets how many pixels we allow for one uint, such as seconds. - const double pixelsPerUnit = 5.0; - - double result = 1.0; - double zoomToFit = GetZoomOfToFit(); - switch( preset ){ - default: - case WaveTrack::kZoomDefault: - result = ZoomInfo::GetDefaultZoom(); - break; - case WaveTrack::kZoomToFit: - result = zoomToFit; - break; - case WaveTrack::kZoomToSelection: - result = GetZoomOfSelection(); - break; - case WaveTrack::kZoomMinutes: - result = pixelsPerUnit * 1.0/60; - break; - case WaveTrack::kZoomSeconds: - result = pixelsPerUnit * 1.0; - break; - case WaveTrack::kZoom5ths: - result = pixelsPerUnit * 5.0; - break; - case WaveTrack::kZoom10ths: - result = pixelsPerUnit * 10.0; - break; - case WaveTrack::kZoom20ths: - result = pixelsPerUnit * 20.0; - break; - case WaveTrack::kZoom50ths: - result = pixelsPerUnit * 50.0; - break; - case WaveTrack::kZoom100ths: - result = pixelsPerUnit * 100.0; - break; - case WaveTrack::kZoom500ths: - result = pixelsPerUnit * 500.0; - break; - case WaveTrack::kZoomMilliSeconds: - result = pixelsPerUnit * 1000.0; - break; - case WaveTrack::kZoomSamples: - result = 44100.0; - break; - case WaveTrack::kZoom4To1: - result = 44100.0 * 4; - break; - case WaveTrack::kMaxZoom: - result = ZoomInfo::GetMaxZoom(); - break; - }; - if( result < (zoomToFit/maxZoomOutFactor) ) - result = zoomToFit / maxZoomOutFactor; - return result; -} - AudacityProject::PlaybackScroller::PlaybackScroller(AudacityProject *project) : mProject(project) { diff --git a/src/Project.h b/src/Project.h index 63d957260..d62ab16ed 100644 --- a/src/Project.h +++ b/src/Project.h @@ -597,11 +597,6 @@ public: void DeleteCurrentAutoSaveFile(); public: - double GetZoomOfToFit(); - double GetZoomOfSelection(); - - double GetZoomOfPreset(int preset ); - bool IsSoloSimple() const { return mSoloPref == wxT("Simple"); } bool IsSoloNone() const { return mSoloPref == wxT("None"); } diff --git a/src/ViewInfo.cpp b/src/ViewInfo.cpp index 68745c697..98d1f0cce 100644 --- a/src/ViewInfo.cpp +++ b/src/ViewInfo.cpp @@ -87,9 +87,9 @@ bool ZoomInfo::ZoomOutAvailable() const return zoom > gMinZoom; } -double ZoomInfo::GetZoom( ){ return zoom;}; -double ZoomInfo::GetMaxZoom( ){ return gMaxZoom;}; -double ZoomInfo::GetMinZoom( ){ return gMinZoom;}; +double ZoomInfo::GetZoom( ) const { return zoom;}; +double ZoomInfo::GetMaxZoom( ) { return gMaxZoom;}; +double ZoomInfo::GetMinZoom( ) { return gMinZoom;}; void ZoomInfo::SetZoom(double pixelsPerSecond) { diff --git a/src/ViewInfo.h b/src/ViewInfo.h index d9d1d020c..49f40f412 100644 --- a/src/ViewInfo.h +++ b/src/ViewInfo.h @@ -85,12 +85,14 @@ public: static double GetDefaultZoom() { return 44100.0 / 512.0; } - // There is NO GetZoom()! - // Use TimeToPosition and PositionToTime and OffsetTimeByPixels! // Limits zoom to certain bounds void SetZoom(double pixelsPerSecond); - double GetZoom(); + + // This function should not be used to convert positions to times and back + // Use TimeToPosition and PositionToTime and OffsetTimeByPixels instead + double GetZoom() const; + static double GetMaxZoom( ); static double GetMinZoom( ); diff --git a/src/menus/ViewMenus.cpp b/src/menus/ViewMenus.cpp index b2bce37f9..8b7ffe1e5 100644 --- a/src/menus/ViewMenus.cpp +++ b/src/menus/ViewMenus.cpp @@ -14,12 +14,125 @@ // private helper classes and functions namespace { + +double GetZoomOfSelection( const AudacityProject &project ) +{ + const auto &viewInfo = project.GetViewInfo(); + const auto &trackPanel = *project.GetTrackPanel(); + + const double lowerBound = + std::max(viewInfo.selectedRegion.t0(), + project.ScrollingLowerBoundTime()); + const double denom = + viewInfo.selectedRegion.t1() - lowerBound; + if (denom <= 0.0) + return viewInfo.GetZoom(); + + // LL: The "-1" is just a hack to get around an issue where zooming to + // selection doesn't actually get the entire selected region within the + // visible area. This causes a problem with scrolling at end of playback + // where the selected region may be scrolled off the left of the screen. + // I know this isn't right, but until the real rounding or 1-off issue is + // found, this will have to work. + // PRL: Did I fix this? I am not sure, so I leave the hack in place. + // Fixes might have resulted from commits + // 1b8f44d0537d987c59653b11ed75a842b48896ea and + // e7c7bb84a966c3b3cc4b3a9717d5f247f25e7296 + int width; + trackPanel.GetTracksUsableArea(&width, NULL); + return (width - 1) / denom; +} + +double GetZoomOfPreset( const AudacityProject &project, int preset ) +{ + + // Sets a limit on how far we will zoom out as a factor over zoom to fit. + const double maxZoomOutFactor = 4.0; + // Sets how many pixels we allow for one uint, such as seconds. + const double pixelsPerUnit = 5.0; + + double result = 1.0; + double zoomToFit = ViewActions::GetZoomOfToFit( project ); + switch( preset ){ + default: + case WaveTrack::kZoomDefault: + result = ZoomInfo::GetDefaultZoom(); + break; + case WaveTrack::kZoomToFit: + result = zoomToFit; + break; + case WaveTrack::kZoomToSelection: + result = GetZoomOfSelection( project ); + break; + case WaveTrack::kZoomMinutes: + result = pixelsPerUnit * 1.0/60; + break; + case WaveTrack::kZoomSeconds: + result = pixelsPerUnit * 1.0; + break; + case WaveTrack::kZoom5ths: + result = pixelsPerUnit * 5.0; + break; + case WaveTrack::kZoom10ths: + result = pixelsPerUnit * 10.0; + break; + case WaveTrack::kZoom20ths: + result = pixelsPerUnit * 20.0; + break; + case WaveTrack::kZoom50ths: + result = pixelsPerUnit * 50.0; + break; + case WaveTrack::kZoom100ths: + result = pixelsPerUnit * 100.0; + break; + case WaveTrack::kZoom500ths: + result = pixelsPerUnit * 500.0; + break; + case WaveTrack::kZoomMilliSeconds: + result = pixelsPerUnit * 1000.0; + break; + case WaveTrack::kZoomSamples: + result = 44100.0; + break; + case WaveTrack::kZoom4To1: + result = 44100.0 * 4; + break; + case WaveTrack::kMaxZoom: + result = ZoomInfo::GetMaxZoom(); + break; + }; + if( result < (zoomToFit/maxZoomOutFactor) ) + result = zoomToFit / maxZoomOutFactor; + return result; +} + } namespace ViewActions { // exported helper functions +double GetZoomOfToFit( const AudacityProject &project ) +{ + const auto &tracks = *project.GetTracks(); + const auto &viewInfo = project.GetViewInfo(); + const auto &trackPanel = *project.GetTrackPanel(); + + const double end = tracks.GetEndTime(); + const double start = viewInfo.bScrollBeyondZero + ? std::min( tracks.GetStartTime(), 0.0) + : 0; + const double len = end - start; + + if (len <= 0.0) + return viewInfo.GetZoom(); + + int w; + trackPanel.GetTracksUsableArea(&w, NULL); + w -= 10; + return w/len; +} + void DoZoomFit(AudacityProject &project) { auto &viewInfo = project.GetViewInfo(); @@ -29,7 +142,7 @@ void DoZoomFit(AudacityProject &project) ? std::min(tracks->GetStartTime(), 0.0) : 0; - project.Zoom( project.GetZoomOfToFit() ); + project.Zoom( GetZoomOfToFit( project ) ); project.TP_ScrollWindow(start); } @@ -91,7 +204,7 @@ void OnZoomSel(const CommandContext &context) auto &project = context.project; auto &selectedRegion = project.GetViewInfo().selectedRegion; - project.Zoom( project.GetZoomOfSelection() ); + project.Zoom( GetZoomOfSelection( project ) ); project.TP_ScrollWindow(selectedRegion.t0()); } @@ -105,8 +218,8 @@ void OnZoomToggle(const CommandContext &context) // const double origWidth = GetScreenEndTime() - origLeft; // Choose the zoom that is most different to the current zoom. - double Zoom1 = project.GetZoomOfPreset( TracksPrefs::Zoom1Choice() ); - double Zoom2 = project.GetZoomOfPreset( TracksPrefs::Zoom2Choice() ); + double Zoom1 = GetZoomOfPreset( project, TracksPrefs::Zoom1Choice() ); + double Zoom2 = GetZoomOfPreset( project, TracksPrefs::Zoom2Choice() ); double Z = viewInfo.GetZoom();// Current Zoom. double ChosenZoom = fabs(log(Zoom1 / Z)) > fabs(log( Z / Zoom2)) ? Zoom1:Zoom2; From 83f504e30f3d53e57b7a889438105415179a7acd Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Sat, 27 Oct 2018 19:55:01 -0400 Subject: [PATCH 4/6] Move AudacityProject::SetTrackPan and SetTrackGain --- src/Project.cpp | 26 -------------------------- src/Project.h | 2 -- src/menus/TrackMenus.cpp | 38 ++++++++++++++++++++++++++++++++------ 3 files changed, 32 insertions(+), 34 deletions(-) diff --git a/src/Project.cpp b/src/Project.cpp index bb9a74fb9..84238f8f6 100644 --- a/src/Project.cpp +++ b/src/Project.cpp @@ -5328,32 +5328,6 @@ void AudacityProject::DoTrackSolo(Track *t, bool exclusive) mTrackPanel->Refresh(false); } -void AudacityProject::SetTrackGain(WaveTrack * wt, LWSlider * slider) -{ - wxASSERT(wt); - float newValue = slider->Get(); - - for (auto channel : TrackList::Channels(wt)) - channel->SetGain(newValue); - - PushState(_("Adjusted gain"), _("Gain"), UndoPush::CONSOLIDATE); - - GetTrackPanel()->RefreshTrack(wt); -} - -void AudacityProject::SetTrackPan(WaveTrack * wt, LWSlider * slider) -{ - wxASSERT(wt); - float newValue = slider->Get(); - - for (auto channel : TrackList::Channels(wt)) - channel->SetPan(newValue); - - PushState(_("Adjusted Pan"), _("Pan"), UndoPush::CONSOLIDATE); - - GetTrackPanel()->RefreshTrack(wt); -} - /// Removes the specified track. Called from HandleClosing. void AudacityProject::RemoveTrack(Track * toRemove) { diff --git a/src/Project.h b/src/Project.h index d62ab16ed..d86f79f67 100644 --- a/src/Project.h +++ b/src/Project.h @@ -427,8 +427,6 @@ public: void DoTrackMute(Track *pTrack, bool exclusive); void DoTrackSolo(Track *pTrack, bool exclusive); - void SetTrackGain(WaveTrack * track, LWSlider * slider); - void SetTrackPan(WaveTrack * track, LWSlider * slider); void RemoveTrack(Track * toRemove); diff --git a/src/menus/TrackMenus.cpp b/src/menus/TrackMenus.cpp index 0795c233d..025e1e0ee 100644 --- a/src/menus/TrackMenus.cpp +++ b/src/menus/TrackMenus.cpp @@ -503,6 +503,32 @@ void DoSortTracks( AudacityProject &project, int flags ) pTracks->Permute(arr); } +void SetTrackGain(AudacityProject &project, WaveTrack * wt, LWSlider * slider) +{ + wxASSERT(wt); + float newValue = slider->Get(); + + for (auto channel : TrackList::Channels(wt)) + channel->SetGain(newValue); + + project.PushState(_("Adjusted gain"), _("Gain"), UndoPush::CONSOLIDATE); + + project.GetTrackPanel()->RefreshTrack(wt); +} + +void SetTrackPan(AudacityProject &project, WaveTrack * wt, LWSlider * slider) +{ + wxASSERT(wt); + float newValue = slider->Get(); + + for (auto channel : TrackList::Channels(wt)) + channel->SetPan(newValue); + + project.PushState(_("Adjusted Pan"), _("Pan"), UndoPush::CONSOLIDATE); + + project.GetTrackPanel()->RefreshTrack(wt); +} + } namespace TrackActions { @@ -1084,7 +1110,7 @@ void OnTrackPan(const CommandContext &context) if (track) track->TypeSwitch( [&](WaveTrack *wt) { LWSlider *slider = trackPanel->PanSlider(wt); if (slider->ShowDialog()) - project.SetTrackPan(wt, slider); + SetTrackPan(project, wt, slider); }); } @@ -1097,7 +1123,7 @@ void OnTrackPanLeft(const CommandContext &context) if (track) track->TypeSwitch( [&](WaveTrack *wt) { LWSlider *slider = trackPanel->PanSlider(wt); slider->Decrease(1); - project.SetTrackPan(wt, slider); + SetTrackPan(project, wt, slider); }); } @@ -1110,7 +1136,7 @@ void OnTrackPanRight(const CommandContext &context) if (track) track->TypeSwitch( [&](WaveTrack *wt) { LWSlider *slider = trackPanel->PanSlider(wt); slider->Increase(1); - project.SetTrackPan(wt, slider); + SetTrackPan(project, wt, slider); }); } @@ -1124,7 +1150,7 @@ void OnTrackGain(const CommandContext &context) if (track) track->TypeSwitch( [&](WaveTrack *wt) { LWSlider *slider = trackPanel->GainSlider(wt); if (slider->ShowDialog()) - project.SetTrackGain(wt, slider); + SetTrackGain(project, wt, slider); }); } @@ -1137,7 +1163,7 @@ void OnTrackGainInc(const CommandContext &context) if (track) track->TypeSwitch( [&](WaveTrack *wt) { LWSlider *slider = trackPanel->GainSlider(wt); slider->Increase(1); - project.SetTrackGain(wt, slider); + SetTrackGain(project, wt, slider); }); } @@ -1150,7 +1176,7 @@ void OnTrackGainDec(const CommandContext &context) if (track) track->TypeSwitch( [&](WaveTrack *wt) { LWSlider *slider = trackPanel->GainSlider(wt); slider->Decrease(1); - project.SetTrackGain(wt, slider); + SetTrackGain(project, wt, slider); }); } From 51c35417167c7f7821e32391dba1e9eae3c4bfcc Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Sat, 27 Oct 2018 20:16:39 -0400 Subject: [PATCH 5/6] Move AudacityProject::RemoveTrack --- src/Menus.h | 1 + src/Project.cpp | 37 ------------------------- src/Project.h | 2 -- src/menus/TrackMenus.cpp | 41 +++++++++++++++++++++++++++- src/tracks/ui/TrackButtonHandles.cpp | 3 +- 5 files changed, 43 insertions(+), 41 deletions(-) diff --git a/src/Menus.h b/src/Menus.h index 9782fac77..5d7ffcbb4 100644 --- a/src/Menus.h +++ b/src/Menus.h @@ -140,6 +140,7 @@ namespace TrackActions { }; /// Move a track up, down, to top or to bottom. void DoMoveTrack( AudacityProject &project, Track* target, MoveChoice choice ); +void DoRemoveTrack( AudacityProject &project, Track * toRemove ); void DoRemoveTracks( AudacityProject & ); } diff --git a/src/Project.cpp b/src/Project.cpp index 84238f8f6..0431c90ab 100644 --- a/src/Project.cpp +++ b/src/Project.cpp @@ -5328,43 +5328,6 @@ void AudacityProject::DoTrackSolo(Track *t, bool exclusive) mTrackPanel->Refresh(false); } -/// Removes the specified track. Called from HandleClosing. -void AudacityProject::RemoveTrack(Track * toRemove) -{ - // If it was focused, then NEW focus is the next or, if - // unavailable, the previous track. (The NEW focus is set - // after the track has been removed.) - bool toRemoveWasFocused = mTrackPanel->GetFocusedTrack() == toRemove; - Track* newFocus{}; - if (toRemoveWasFocused) { - auto iterNext = mTracks->FindLeader(toRemove), iterPrev = iterNext; - newFocus = *++iterNext; - if (!newFocus) { - newFocus = *--iterPrev; - } - } - - wxString name = toRemove->GetName(); - - auto channels = TrackList::Channels(toRemove); - // Be careful to post-increment over positions that get erased! - auto &iter = channels.first; - while (iter != channels.end()) - mTracks->Remove( * iter++ ); - - if (toRemoveWasFocused) - mTrackPanel->SetFocusedTrack(newFocus); - - PushState( - wxString::Format(_("Removed track '%s.'"), - name), - _("Track Remove")); - - TP_RedrawScrollbars(); - HandleResize(); - GetTrackPanel()->Refresh(false); -} - void AudacityProject::HandleTrackMute(Track *t, const bool exclusive) { // Whatever t is, replace with lead channel diff --git a/src/Project.h b/src/Project.h index d86f79f67..5a984418f 100644 --- a/src/Project.h +++ b/src/Project.h @@ -428,8 +428,6 @@ public: void DoTrackMute(Track *pTrack, bool exclusive); void DoTrackSolo(Track *pTrack, bool exclusive); - void RemoveTrack(Track * toRemove); - // "exclusive" mute means mute the chosen track and unmute all others. void HandleTrackMute(Track *t, const bool exclusive); diff --git a/src/menus/TrackMenus.cpp b/src/menus/TrackMenus.cpp index 025e1e0ee..f1fe5df94 100644 --- a/src/menus/TrackMenus.cpp +++ b/src/menus/TrackMenus.cpp @@ -575,6 +575,45 @@ void DoRemoveTracks( AudacityProject &project ) trackPanel->Refresh(false); } +void DoRemoveTrack(AudacityProject &project, Track * toRemove) +{ + auto &tracks = *project.GetTracks(); + auto &trackPanel = *project.GetTrackPanel(); + + // If it was focused, then NEW focus is the next or, if + // unavailable, the previous track. (The NEW focus is set + // after the track has been removed.) + bool toRemoveWasFocused = trackPanel.GetFocusedTrack() == toRemove; + Track* newFocus{}; + if (toRemoveWasFocused) { + auto iterNext = tracks.FindLeader(toRemove), iterPrev = iterNext; + newFocus = *++iterNext; + if (!newFocus) { + newFocus = *--iterPrev; + } + } + + wxString name = toRemove->GetName(); + + auto channels = TrackList::Channels(toRemove); + // Be careful to post-increment over positions that get erased! + auto &iter = channels.first; + while (iter != channels.end()) + tracks.Remove( * iter++ ); + + if (toRemoveWasFocused) + trackPanel.SetFocusedTrack(newFocus); + + project.PushState( + wxString::Format(_("Removed track '%s.'"), + name), + _("Track Remove")); + + project.FixScrollbars(); + project.HandleResize(); + trackPanel.Refresh(false); +} + void DoMoveTrack (AudacityProject &project, Track* target, MoveChoice choice) { @@ -1229,7 +1268,7 @@ void OnTrackClose(const CommandContext &context) return; } - project.RemoveTrack(t); + DoRemoveTrack(project, t); trackPanel->UpdateViewIfNoTracks(); trackPanel->Refresh(false); diff --git a/src/tracks/ui/TrackButtonHandles.cpp b/src/tracks/ui/TrackButtonHandles.cpp index ff2709615..d578b627d 100644 --- a/src/tracks/ui/TrackButtonHandles.cpp +++ b/src/tracks/ui/TrackButtonHandles.cpp @@ -12,6 +12,7 @@ Paul Licameli split from TrackPanel.cpp #include "TrackButtonHandles.h" #include "../../HitTestResult.h" +#include "../../Menus.h" #include "../../Project.h" #include "../../RefreshCode.h" #include "../../Track.h" @@ -95,7 +96,7 @@ UIHandle::Result CloseButtonHandle::CommitChanges pProject->StopIfPaused(); if (!pProject->IsAudioActive()) { // This pushes an undo item: - pProject->RemoveTrack(pTrack.get()); + TrackActions::DoRemoveTrack(*pProject, pTrack.get()); // Redraw all tracks when any one of them closes // (Could we invent a return code that draws only those at or below // the affected track?) From 50f85794759bc583d4ed57430071b75b3485322a Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Sat, 27 Oct 2018 20:32:58 -0400 Subject: [PATCH 6/6] Move AudacityProject::DoTrackMute and DoTrackSolo... ... also fixed the bug that muting and soloing from the mixer board did not update accessibility. --- src/Menus.h | 5 + src/MixerBoard.cpp | 7 +- src/Project.cpp | 107 ------------------ src/Project.h | 10 -- src/menus/TrackMenus.cpp | 105 ++++++++++++++++- .../ui/PlayableTrackButtonHandles.cpp | 5 +- 6 files changed, 116 insertions(+), 123 deletions(-) diff --git a/src/Menus.h b/src/Menus.h index 5d7ffcbb4..bb6a64943 100644 --- a/src/Menus.h +++ b/src/Menus.h @@ -140,6 +140,11 @@ namespace TrackActions { }; /// Move a track up, down, to top or to bottom. void DoMoveTrack( AudacityProject &project, Track* target, MoveChoice choice ); +// "exclusive" mute means mute the chosen track and unmute all others. +void DoTrackMute( AudacityProject &project, Track *pTrack, bool exclusive ); +// Type of solo (standard or simple) follows the set preference, unless +// exclusive == true, which causes the opposite behavior. +void DoTrackSolo( AudacityProject &project, Track *pTrack, bool exclusive ); void DoRemoveTrack( AudacityProject &project, Track * toRemove ); void DoRemoveTracks( AudacityProject & ); } diff --git a/src/MixerBoard.cpp b/src/MixerBoard.cpp index 4f993a47c..6eecfd1c4 100644 --- a/src/MixerBoard.cpp +++ b/src/MixerBoard.cpp @@ -10,6 +10,7 @@ **********************************************************************/ #include "Audacity.h" +#include "Menus.h" #include "MixerBoard.h" #include @@ -744,7 +745,8 @@ void MixerTrackCluster::OnSlider_Pan(wxCommandEvent& WXUNUSED(event)) void MixerTrackCluster::OnButton_Mute(wxCommandEvent& WXUNUSED(event)) { - mProject->HandleTrackMute(mTrack.get(), mToggleButton_Mute->WasShiftDown()); + TrackActions::DoTrackMute( + *mProject, mTrack.get(), mToggleButton_Mute->WasShiftDown()); mToggleButton_Mute->SetAlternateIdx(mTrack->GetSolo() ? 1 : 0); // Update the TrackPanel correspondingly. @@ -759,7 +761,8 @@ void MixerTrackCluster::OnButton_Mute(wxCommandEvent& WXUNUSED(event)) void MixerTrackCluster::OnButton_Solo(wxCommandEvent& WXUNUSED(event)) { - mProject->HandleTrackSolo(mTrack.get(), mToggleButton_Solo->WasShiftDown()); + TrackActions::DoTrackSolo( + *mProject, mTrack.get(), mToggleButton_Solo->WasShiftDown()); bool bIsSolo = mTrack->GetSolo(); mToggleButton_Mute->SetAlternateIdx(bIsSolo ? 1 : 0); diff --git a/src/Project.cpp b/src/Project.cpp index 0431c90ab..9220257e7 100644 --- a/src/Project.cpp +++ b/src/Project.cpp @@ -5312,113 +5312,6 @@ void AudacityProject::SetSyncLock(bool flag) } } -void AudacityProject::DoTrackMute(Track *t, bool exclusive) -{ - HandleTrackMute(t, exclusive); - - mTrackPanel->UpdateAccessibility(); - mTrackPanel->Refresh(false); -} - -void AudacityProject::DoTrackSolo(Track *t, bool exclusive) -{ - HandleTrackSolo(t, exclusive); - - mTrackPanel->UpdateAccessibility(); - mTrackPanel->Refresh(false); -} - -void AudacityProject::HandleTrackMute(Track *t, const bool exclusive) -{ - // Whatever t is, replace with lead channel - t = *GetTracks()->FindLeader(t); - - // "exclusive" mute means mute the chosen track and unmute all others. - if (exclusive) { - for (auto leader : GetTracks()->Leaders()) { - const auto group = TrackList::Channels(leader); - bool chosen = (t == leader); - for (auto channel : group) - channel->SetMute( chosen ), - channel->SetSolo( false ); - } - } - else { - // Normal click toggles this track. - auto pt = dynamic_cast( t ); - if (!pt) - return; - - bool wasMute = pt->GetMute(); - for (auto channel : TrackList::Channels(pt)) - channel->SetMute( !wasMute ); - - if (IsSoloSimple() || IsSoloNone()) - { - // We also set a solo indicator if we have just one track / stereo pair playing. - // in a group of more than one playable tracks. - // otherwise clear solo on everything. - - auto range = GetTracks()->Leaders(); - auto nPlayableTracks = range.size(); - auto nPlaying = (range - &PlayableTrack::GetMute).size(); - - for (auto track : GetTracks()->Any()) - // will set both of a stereo pair - track->SetSolo( (nPlaying==1) && (nPlayableTracks > 1 ) && !track->GetMute() ); - } - } - ModifyState(true); -} - -// Type of solo (standard or simple) follows the set preference, unless -// alternate == true, which causes the opposite behavior. -void AudacityProject::HandleTrackSolo(Track *t, const bool alternate) -{ - // Whatever t is, replace with lead channel - t = *GetTracks()->FindLeader(t); - - const auto pt = dynamic_cast( t ); - if (!pt) - return; - bool bWasSolo = pt->GetSolo(); - - bool bSoloMultiple = !IsSoloSimple() ^ alternate; - - // Standard and Simple solo have opposite defaults: - // Standard - Behaves as individual buttons, shift=radio buttons - // Simple - Behaves as radio buttons, shift=individual - // In addition, Simple solo will mute/unmute tracks - // when in standard radio button mode. - if ( bSoloMultiple ) - { - for (auto channel : TrackList::Channels(pt)) - channel->SetSolo( !bWasSolo ); - } - else - { - // Normal click solo this track only, mute everything else. - // OR unmute and unsolo everything. - for (auto leader : GetTracks()->Leaders()) { - const auto group = TrackList::Channels(leader); - bool chosen = (t == leader); - for (auto channel : group) { - if (chosen) { - channel->SetSolo( !bWasSolo ); - if( IsSoloSimple() ) - channel->SetMute( false ); - } - else { - channel->SetSolo( false ); - if( IsSoloSimple() ) - channel->SetMute( !bWasSolo ); - } - } - } - } - ModifyState(true); -} - // Keyboard capture // static diff --git a/src/Project.h b/src/Project.h index 5a984418f..e7ce5af64 100644 --- a/src/Project.h +++ b/src/Project.h @@ -425,16 +425,6 @@ public: bool IsSyncLocked(); void SetSyncLock(bool flag); - void DoTrackMute(Track *pTrack, bool exclusive); - void DoTrackSolo(Track *pTrack, bool exclusive); - - // "exclusive" mute means mute the chosen track and unmute all others. - void HandleTrackMute(Track *t, const bool exclusive); - - // Type of solo (standard or simple) follows the set preference, unless - // alternate == true, which causes the opposite behavior. - void HandleTrackSolo(Track *t, const bool alternate); - // Snap To void SetSnapTo(int snap); diff --git a/src/menus/TrackMenus.cpp b/src/menus/TrackMenus.cpp index f1fe5df94..699b49eac 100644 --- a/src/menus/TrackMenus.cpp +++ b/src/menus/TrackMenus.cpp @@ -575,6 +575,107 @@ void DoRemoveTracks( AudacityProject &project ) trackPanel->Refresh(false); } +void DoTrackMute(AudacityProject &project, Track *t, bool exclusive) +{ + auto &tracks = *project.GetTracks(); + auto &trackPanel = *project.GetTrackPanel(); + + // Whatever t is, replace with lead channel + t = *tracks.FindLeader(t); + + // "exclusive" mute means mute the chosen track and unmute all others. + if (exclusive) { + for (auto leader : tracks.Leaders()) { + const auto group = TrackList::Channels(leader); + bool chosen = (t == leader); + for (auto channel : group) + channel->SetMute( chosen ), + channel->SetSolo( false ); + } + } + else { + // Normal click toggles this track. + auto pt = dynamic_cast( t ); + if (!pt) + return; + + bool wasMute = pt->GetMute(); + for (auto channel : TrackList::Channels(pt)) + channel->SetMute( !wasMute ); + + if (project.IsSoloSimple() || project.IsSoloNone()) + { + // We also set a solo indicator if we have just one track / stereo pair playing. + // in a group of more than one playable tracks. + // otherwise clear solo on everything. + + auto range = tracks.Leaders(); + auto nPlayableTracks = range.size(); + auto nPlaying = (range - &PlayableTrack::GetMute).size(); + + for (auto track : tracks.Any()) + // will set both of a stereo pair + track->SetSolo( (nPlaying==1) && (nPlayableTracks > 1 ) && !track->GetMute() ); + } + } + project.ModifyState(true); + + trackPanel.UpdateAccessibility(); + trackPanel.Refresh(false); +} + +void DoTrackSolo(AudacityProject &project, Track *t, bool exclusive) +{ + auto &tracks = *project.GetTracks(); + auto &trackPanel = *project.GetTrackPanel(); + + // Whatever t is, replace with lead channel + t = *tracks.FindLeader(t); + + const auto pt = dynamic_cast( t ); + if (!pt) + return; + bool bWasSolo = pt->GetSolo(); + + bool bSoloMultiple = !project.IsSoloSimple() ^ exclusive; + + // Standard and Simple solo have opposite defaults: + // Standard - Behaves as individual buttons, shift=radio buttons + // Simple - Behaves as radio buttons, shift=individual + // In addition, Simple solo will mute/unmute tracks + // when in standard radio button mode. + if ( bSoloMultiple ) + { + for (auto channel : TrackList::Channels(pt)) + channel->SetSolo( !bWasSolo ); + } + else + { + // Normal click solo this track only, mute everything else. + // OR unmute and unsolo everything. + for (auto leader : tracks.Leaders()) { + const auto group = TrackList::Channels(leader); + bool chosen = (t == leader); + for (auto channel : group) { + if (chosen) { + channel->SetSolo( !bWasSolo ); + if( project.IsSoloSimple() ) + channel->SetMute( false ); + } + else { + channel->SetSolo( false ); + if( project.IsSoloSimple() ) + channel->SetMute( !bWasSolo ); + } + } + } + } + project.ModifyState(true); + + trackPanel.UpdateAccessibility(); + trackPanel.Refresh(false); +} + void DoRemoveTrack(AudacityProject &project, Track * toRemove) { auto &tracks = *project.GetTracks(); @@ -1234,7 +1335,7 @@ void OnTrackMute(const CommandContext &context) const auto track = trackPanel->GetFocusedTrack(); if (track) track->TypeSwitch( [&](PlayableTrack *t) { - project.DoTrackMute(t, false); + DoTrackMute(project, t, false); }); } @@ -1245,7 +1346,7 @@ void OnTrackSolo(const CommandContext &context) const auto track = trackPanel->GetFocusedTrack(); if (track) track->TypeSwitch( [&](PlayableTrack *t) { - project.DoTrackSolo(t, false); + DoTrackSolo(project, t, false); }); } diff --git a/src/tracks/playabletrack/ui/PlayableTrackButtonHandles.cpp b/src/tracks/playabletrack/ui/PlayableTrackButtonHandles.cpp index 136fe9209..97ab167c5 100644 --- a/src/tracks/playabletrack/ui/PlayableTrackButtonHandles.cpp +++ b/src/tracks/playabletrack/ui/PlayableTrackButtonHandles.cpp @@ -13,6 +13,7 @@ Paul Licameli split from TrackPanel.cpp #include "../../../commands/CommandManager.h" #include "../../../HitTestResult.h" +#include "../../../Menus.h" #include "../../../Project.h" #include "../../../RefreshCode.h" #include "../../../Track.h" @@ -33,7 +34,7 @@ UIHandle::Result MuteButtonHandle::CommitChanges { auto pTrack = mpTrack.lock(); if ( dynamic_cast< PlayableTrack* >( pTrack.get() ) ) - pProject->DoTrackMute(pTrack.get(), event.ShiftDown()); + TrackActions::DoTrackMute(*pProject, pTrack.get(), event.ShiftDown()); return RefreshCode::RefreshNone; } @@ -89,7 +90,7 @@ UIHandle::Result SoloButtonHandle::CommitChanges { auto pTrack = mpTrack.lock(); if ( dynamic_cast< PlayableTrack* >( pTrack.get() ) ) - pProject->DoTrackSolo(pTrack.get(), event.ShiftDown()); + TrackActions::DoTrackSolo(*pProject, pTrack.get(), event.ShiftDown()); return RefreshCode::RefreshNone; }