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