1 /**********************************************************************
2 
3  Audacity: A Digital Audio Editor
4 
5  TrackUtilities.cpp
6 
7  Paul Licameli split from TrackMenus.cpp
8 
9  **********************************************************************/
10 
11 #include "TrackUtilities.h"
12 
13 #include "ProjectHistory.h"
14 #include "ProjectSettings.h"
15 #include "ProjectWindow.h"
16 #include "Track.h"
17 #include "TrackPanelAx.h"
18 #include "TrackPanel.h"
19 
20 namespace TrackUtilities {
21 
DoRemoveTracks(AudacityProject & project)22 void DoRemoveTracks( AudacityProject &project )
23 {
24    auto &tracks = TrackList::Get( project );
25    auto &trackPanel = TrackPanel::Get( project );
26 
27    std::vector<Track*> toRemove;
28    for (auto track : tracks.Selected())
29       toRemove.push_back(track);
30 
31    // Capture the track preceding the first removed track
32    Track *f{};
33    if (!toRemove.empty()) {
34       auto found = tracks.Find(toRemove[0]);
35       f = *--found;
36    }
37 
38    for (auto track : toRemove)
39       tracks.Remove(track);
40 
41    if (!f)
42       // try to use the last track
43       f = *tracks.Any().rbegin();
44    if (f) {
45       // Try to use the first track after the removal
46       auto found = tracks.FindLeader(f);
47       auto t = *++found;
48       if (t)
49          f = t;
50    }
51 
52    // If we actually have something left, then set focus and make sure it's seen
53    if (f) {
54       TrackFocus::Get(project).Set(f);
55       f->EnsureVisible();
56    }
57 
58    ProjectHistory::Get( project )
59       .PushState(XO("Removed audio track(s)"), XO("Remove Track"));
60 
61    trackPanel.UpdateViewIfNoTracks();
62 }
63 
DoTrackMute(AudacityProject & project,Track * t,bool exclusive)64 void DoTrackMute(AudacityProject &project, Track *t, bool exclusive)
65 {
66    const auto &settings = ProjectSettings::Get( project );
67    auto &tracks = TrackList::Get( project );
68 
69    // Whatever t is, replace with lead channel
70    t = *tracks.FindLeader(t);
71 
72    // "exclusive" mute means mute the chosen track and unmute all others.
73    if (exclusive) {
74       for (auto leader : tracks.Leaders<PlayableTrack>()) {
75          const auto group = TrackList::Channels(leader);
76          bool chosen = (t == leader);
77          for (auto channel : group)
78             channel->SetMute( chosen ),
79             channel->SetSolo( false );
80       }
81    }
82    else {
83       // Normal click toggles this track.
84       auto pt = dynamic_cast<PlayableTrack *>( t );
85       if (!pt)
86          return;
87 
88       bool wasMute = pt->GetMute();
89       for (auto channel : TrackList::Channels(pt))
90          channel->SetMute( !wasMute );
91 
92       if (settings.IsSoloSimple() || settings.IsSoloNone())
93       {
94          // We also set a solo indicator if we have just one track / stereo pair playing.
95          // in a group of more than one playable tracks.
96          // otherwise clear solo on everything.
97 
98          auto range = tracks.Leaders<PlayableTrack>();
99          auto nPlayableTracks = range.size();
100          auto nPlaying = (range - &PlayableTrack::GetMute).size();
101 
102          for (auto track : tracks.Any<PlayableTrack>())
103             // will set both of a stereo pair
104             track->SetSolo( (nPlaying==1) && (nPlayableTracks > 1 ) && !track->GetMute() );
105       }
106    }
107    ProjectHistory::Get( project ).ModifyState(true);
108 
109    TrackFocus::Get( project ).UpdateAccessibility();
110 }
111 
DoTrackSolo(AudacityProject & project,Track * t,bool exclusive)112 void DoTrackSolo(AudacityProject &project, Track *t, bool exclusive)
113 {
114    const auto &settings = ProjectSettings::Get( project );
115    auto &tracks = TrackList::Get( project );
116 
117    // Whatever t is, replace with lead channel
118    t = *tracks.FindLeader(t);
119 
120    const auto pt = dynamic_cast<PlayableTrack *>( t );
121    if (!pt)
122       return;
123    bool bWasSolo = pt->GetSolo();
124 
125    bool bSoloMultiple = !settings.IsSoloSimple() ^ exclusive;
126 
127    // Standard and Simple solo have opposite defaults:
128    //   Standard - Behaves as individual buttons, shift=radio buttons
129    //   Simple   - Behaves as radio buttons, shift=individual
130    // In addition, Simple solo will mute/unmute tracks
131    // when in standard radio button mode.
132    if ( bSoloMultiple )
133    {
134       for (auto channel : TrackList::Channels(pt))
135          channel->SetSolo( !bWasSolo );
136    }
137    else
138    {
139       // Normal click solo this track only, mute everything else.
140       // OR unmute and unsolo everything.
141       for (auto leader : tracks.Leaders<PlayableTrack>()) {
142          const auto group = TrackList::Channels(leader);
143          bool chosen = (t == leader);
144          for (auto channel : group) {
145             if (chosen) {
146                channel->SetSolo( !bWasSolo );
147                if( settings.IsSoloSimple() )
148                   channel->SetMute( false );
149             }
150             else {
151                channel->SetSolo( false );
152                if( settings.IsSoloSimple() )
153                   channel->SetMute( !bWasSolo );
154             }
155          }
156       }
157    }
158    ProjectHistory::Get( project ).ModifyState(true);
159 
160    TrackFocus::Get( project ).UpdateAccessibility();
161 }
162 
DoRemoveTrack(AudacityProject & project,Track * toRemove)163 void DoRemoveTrack(AudacityProject &project, Track * toRemove)
164 {
165    auto &tracks = TrackList::Get( project );
166    auto &trackFocus = TrackFocus::Get( project );
167    auto &window = ProjectWindow::Get( project );
168 
169    // If it was focused, then NEW focus is the next or, if
170    // unavailable, the previous track. (The NEW focus is set
171    // after the track has been removed.)
172    bool toRemoveWasFocused = trackFocus.Get() == toRemove;
173    Track* newFocus{};
174    if (toRemoveWasFocused) {
175       auto iterNext = tracks.FindLeader(toRemove), iterPrev = iterNext;
176       newFocus = *++iterNext;
177       if (!newFocus) {
178          newFocus = *--iterPrev;
179       }
180    }
181 
182    wxString name = toRemove->GetName();
183 
184    auto channels = TrackList::Channels(toRemove);
185    // Be careful to post-increment over positions that get erased!
186    auto &iter = channels.first;
187    while (iter != channels.end())
188       tracks.Remove( * iter++ );
189 
190    if (toRemoveWasFocused)
191       trackFocus.Set( newFocus );
192 
193    ProjectHistory::Get( project ).PushState(
194       XO("Removed track '%s.'").Format( name ),
195       XO("Track Remove"));
196 }
197 
DoMoveTrack(AudacityProject & project,Track * target,MoveChoice choice)198 void DoMoveTrack
199 (AudacityProject &project, Track* target, MoveChoice choice)
200 {
201    auto &tracks = TrackList::Get( project );
202 
203    TranslatableString longDesc, shortDesc;
204 
205    switch (choice)
206    {
207    case OnMoveTopID:
208       /* i18n-hint: Past tense of 'to move', as in 'moved audio track up'.*/
209       longDesc = XO("Moved '%s' to Top");
210       shortDesc = XO("Move Track to Top");
211 
212       // TODO: write TrackList::Rotate to do this in one step and avoid emitting
213       // an event for each swap
214       while (tracks.CanMoveUp(target))
215          tracks.Move(target, true);
216 
217       break;
218    case OnMoveBottomID:
219       /* i18n-hint: Past tense of 'to move', as in 'moved audio track up'.*/
220       longDesc = XO("Moved '%s' to Bottom");
221       shortDesc = XO("Move Track to Bottom");
222 
223       // TODO: write TrackList::Rotate to do this in one step and avoid emitting
224       // an event for each swap
225       while (tracks.CanMoveDown(target))
226          tracks.Move(target, false);
227 
228       break;
229    default:
230       bool bUp = (OnMoveUpID == choice);
231 
232       tracks.Move(target, bUp);
233       longDesc =
234          /* i18n-hint: Past tense of 'to move', as in 'moved audio track up'.*/
235          bUp? XO("Moved '%s' Up")
236          : XO("Moved '%s' Down");
237       shortDesc =
238          /* i18n-hint: Past tense of 'to move', as in 'moved audio track up'.*/
239          bUp? XO("Move Track Up")
240          : XO("Move Track Down");
241 
242    }
243 
244    longDesc.Format(target->GetName());
245 
246    ProjectHistory::Get( project ).PushState(longDesc, shortDesc);
247 }
248 
249 }
250