Audacity 3.2.0
ClipMenus.cpp
Go to the documentation of this file.
1#include "../CommonCommandFlags.h"
2#include "ProjectHistory.h"
3#include "../ProjectSettings.h"
4#include "../TrackPanelAx.h"
5#include "../ProjectWindow.h"
6#include "UndoManager.h"
7#include "../WaveClip.h"
8#include "ViewInfo.h"
9#include "../WaveTrack.h"
10#include "../commands/CommandContext.h"
11#include "../commands/CommandManager.h"
12#include "../tracks/ui/TimeShiftHandle.h"
13
14// private helper classes and functions
15namespace {
16
17struct FoundTrack {
18 const WaveTrack* waveTrack{};
19 int trackNum{};
20 bool channel{};
21
22 wxString ComposeTrackName() const
23 {
24 /* i18n-hint: The %d is replaced by the number of the track.*/
25 auto shortName = wxString::Format(_("Track %d"), trackNum).Append(" " + waveTrack->GetName());
26 if (channel) {
27 // TODO: more-than-two-channels-message
28 if ( waveTrack->IsLeader() )
29 /* i18n-hint: given the name of a track, specify its left channel */
30 return XO("%s left").Translation().Format(shortName);
31 else
32 /* i18n-hint: given the name of a track, specify its right channel */
33 return XO("%s right").Translation().Format(shortName);
34 }
35 return shortName;
36 }
37};
38
40 bool found{};
41 double startTime{};
42 double endTime{};
43 wxString name{};
44 int index{};
45};
46
48 int nFound{}; // 0, 1, or 2
49 double time{};
50 int index1{};
51 wxString name1{};
52 bool clipStart1{};
53 int index2{};
54 wxString name2{};
55 bool clipStart2{};
56};
57
59( const WaveTrack *first, const WaveTrack *second )
60{
61 bool sameClips = false;
62
63 auto& left = first->GetClips();
64 auto& right = second->GetClips();
65
66 // PRL: should that have been? :
67 // auto left = first->SortedClipArray();
68 // auto right = second->SortedClipArray();
69
70 if (left.size() == right.size()) {
71 sameClips = true;
72 for (unsigned int i = 0; i < left.size(); i++) {
73 if (left[i]->GetPlayStartTime() != right[i]->GetPlayStartTime() ||
74 left[i]->GetPlayEndTime() != right[i]->GetPlayEndTime()) {
75 sameClips = false;
76 break;
77 }
78 }
79 }
80 return sameClips;
81}
82
84 const WaveTrack* wt)
85{
86 // This is quadratic in the number of channels
87 auto channels = TrackList::Channels(wt);
88 while (!channels.empty()) {
89 auto channel = *channels.first++;
90 for (auto other : channels) {
91 if (!TwoChannelsHaveSameBoundaries(channel, other))
92 return true;
93 }
94 }
95
96 return false;
97}
98
99// When two clips are immediately next to each other, the GetPlayEndTime() of the
100// first clip and the GetPlayStartTime() of the second clip may not be exactly equal
101// due to rounding errors. When searching for the next/prev start time from a
102// given time, the following function adjusts that given time if necessary to
103// take this into account. If the given time is the end time of the first of two
104// clips which are next to each other, then the given time is changed to the
105// start time of the second clip. This ensures that the correct next/prev start
106// time is found.
108 const std::vector<const WaveClip*> & clips, double time)
109{
110 auto q = std::find_if(clips.begin(), clips.end(),
111 [&] (const WaveClip* const& clip) {
112 return clip->GetPlayEndTime() == time; });
113 if (q != clips.end() && q + 1 != clips.end() &&
114 (*q)->SharesBoundaryWithNextClip(*(q+1))) {
115 time = (*(q+1))->GetPlayStartTime();
116 }
117
118 return time;
119}
120
121// When two clips are immediately next to each other, the GetPlayEndTime() of the
122// first clip and the GetPlayStartTime() of the second clip may not be exactly equal
123// due to rounding errors. When searching for the next/prev end time from a
124// given time, the following function adjusts that given time if necessary to
125// take this into account. If the given time is the start time of the second of
126// two clips which are next to each other, then the given time is changed to the
127// end time of the first clip. This ensures that the correct next/prev end time
128// is found.
130 const std::vector<const WaveClip*>& clips, double time)
131{
132 auto q = std::find_if(clips.begin(), clips.end(),
133 [&] (const WaveClip* const& clip) {
134 return clip->GetPlayStartTime() == time; });
135 if (q != clips.end() && q != clips.begin() &&
136 (*(q - 1))->SharesBoundaryWithNextClip(*q)) {
137 time = (*(q-1))->GetPlayEndTime();
138 }
139
140 return time;
141}
142
144(const WaveTrack* wt, double time)
145{
146 FoundClipBoundary result{};
147 result.waveTrack = wt;
148 const auto clips = wt->SortedClipArray();
149 double timeStart = AdjustForFindingStartTimes(clips, time);
150 double timeEnd = AdjustForFindingEndTimes(clips, time);
151
152 auto pStart = std::find_if(clips.begin(), clips.end(),
153 [&] (const WaveClip* const& clip) {
154 return clip->GetPlayStartTime() > timeStart; });
155 auto pEnd = std::find_if(clips.begin(), clips.end(),
156 [&] (const WaveClip* const& clip) {
157 return clip->GetPlayEndTime() > timeEnd; });
158
159 if (pStart != clips.end() && pEnd != clips.end()) {
160 if ((*pEnd)->SharesBoundaryWithNextClip(*pStart)) {
161 // boundary between two clips which are immediately next to each other.
162 result.nFound = 2;
163 result.time = (*pEnd)->GetPlayEndTime();
164 result.index1 = std::distance(clips.begin(), pEnd);
165 result.name1 = (*pEnd)->GetName();
166 result.clipStart1 = false;
167 result.index2 = std::distance(clips.begin(), pStart);
168 result.name2 = (*pStart)->GetName();
169 result.clipStart2 = true;
170 }
171 else if ((*pStart)->GetPlayStartTime() < (*pEnd)->GetPlayEndTime()) {
172 result.nFound = 1;
173 result.time = (*pStart)->GetPlayStartTime();
174 result.index1 = std::distance(clips.begin(), pStart);
175 result.name1 = (*pStart)->GetName();
176 result.clipStart1 = true;
177 }
178 else {
179 result.nFound = 1;
180 result.time = (*pEnd)->GetPlayEndTime();
181 result.index1 = std::distance(clips.begin(), pEnd);
182 result.name1 = (*pEnd)->GetName();
183 result.clipStart1 = false;
184 }
185 }
186 else if (pEnd != clips.end()) {
187 result.nFound = 1;
188 result.time = (*pEnd)->GetPlayEndTime();
189 result.index1 = std::distance(clips.begin(), pEnd);
190 result.name1 = (*pEnd)->GetName();
191 result.clipStart1 = false;
192 }
193
194 return result;
195}
196
198{
199 FoundClipBoundary result{};
200 result.waveTrack = wt;
201 const auto clips = wt->SortedClipArray();
202 double timeStart = AdjustForFindingStartTimes(clips, time);
203 double timeEnd = AdjustForFindingEndTimes(clips, time);
204
205 auto pStart = std::find_if(clips.rbegin(), clips.rend(),
206 [&] (const WaveClip* const& clip) {
207 return clip->GetPlayStartTime() < timeStart; });
208 auto pEnd = std::find_if(clips.rbegin(), clips.rend(),
209 [&] (const WaveClip* const& clip) {
210 return clip->GetPlayEndTime() < timeEnd; });
211
212 if (pStart != clips.rend() && pEnd != clips.rend()) {
213 if ((*pEnd)->SharesBoundaryWithNextClip(*pStart)) {
214 // boundary between two clips which are immediately next to each other.
215 result.nFound = 2;
216 result.time = (*pStart)->GetPlayStartTime();
217 result.index1 =
218 static_cast<int>(clips.size()) - 1 -
219 std::distance(clips.rbegin(), pStart);
220 result.name1 = (*pStart)->GetName();
221 result.clipStart1 = true;
222 result.index2 =
223 static_cast<int>(clips.size()) - 1 -
224 std::distance(clips.rbegin(), pEnd);
225 result.name2 = (*pEnd)->GetName();
226 result.clipStart2 = false;
227 }
228 else if ((*pStart)->GetPlayStartTime() > (*pEnd)->GetPlayEndTime()) {
229 result.nFound = 1;
230 result.time = (*pStart)->GetPlayStartTime();
231 result.index1 =
232 static_cast<int>(clips.size()) - 1 -
233 std::distance(clips.rbegin(), pStart);
234 result.name1 = (*pStart)->GetName();
235 result.clipStart1 = true;
236 }
237 else {
238 result.nFound = 1;
239 result.time = (*pEnd)->GetPlayEndTime();
240 result.index1 =
241 static_cast<int>(clips.size()) - 1 -
242 std::distance(clips.rbegin(), pEnd);
243 result.name1 = (*pEnd)->GetName();
244 result.clipStart1 = false;
245 }
246 }
247 else if (pStart != clips.rend()) {
248 result.nFound = 1;
249 result.time = (*pStart)->GetPlayStartTime();
250 result.index1 =
251 static_cast<int>(clips.size()) - 1 -
252 std::distance(clips.rbegin(), pStart);
253 result.name1 = (*pStart)->GetName();
254 result.clipStart1 = true;
255 }
256
257 return result;
258}
259
261(AudacityProject &project,
262 double time, bool next, std::vector<FoundClipBoundary>& finalResults)
263{
264 auto &tracks = TrackList::Get( project );
265 finalResults.clear();
266
267 bool anyWaveTracksSelected{ tracks.Selected< const WaveTrack >() };
268
269
270 // first search the tracks individually
271
272 std::vector<FoundClipBoundary> results;
273
274 int nTracksSearched = 0;
275 auto leaders = tracks.Leaders();
276 auto rangeLeaders = leaders.Filter<const WaveTrack>();
277 if (anyWaveTracksSelected)
278 rangeLeaders = rangeLeaders + &Track::GetSelected;
279 for (auto waveTrack : rangeLeaders) {
280 bool stereoAndDiff = ChannelsHaveDifferentClipBoundaries(waveTrack);
281
282 auto rangeChan = stereoAndDiff
283 ? TrackList::Channels( waveTrack )
284 : TrackList::SingletonRange(waveTrack);
285
286 for (auto wt : rangeChan) {
287 auto result = next ? FindNextClipBoundary(wt, time) :
288 FindPrevClipBoundary(wt, time);
289 if (result.nFound > 0) {
290 result.trackNum =
291 1 + std::distance( leaders.begin(), leaders.find( waveTrack ) );
292 result.channel = stereoAndDiff;
293 results.push_back(result);
294 }
295 }
296
297 nTracksSearched++;
298 }
299
300
301 if (results.size() > 0) {
302 // If any clip boundaries were found
303 // find the clip boundary or boundaries with the min/max time
304 auto compare = [] (const FoundClipBoundary& a, const FoundClipBoundary&b)
305 { return a.time < b.time; };
306
307 auto p = next ? min_element(results.begin(), results.end(), compare ) :
308 max_element(results.begin(), results.end(), compare);
309
310 for ( auto &r : results )
311 if ( r.time == (*p).time )
312 finalResults.push_back( r );
313 }
314
315 return nTracksSearched; // can be used for screen reader messages if required
316}
317
318// for clip boundary commands, create a message for screen readers
320 const std::vector<FoundClipBoundary>& results)
321{
322 TranslatableString message;
323 for (auto& result : results) {
324
325 auto longName = result.ComposeTrackName();
326
328 auto nClips = result.waveTrack->GetNumClips();
329 if (result.nFound < 2) {
330 str = XP(
331 /* i18n-hint:
332 First %s is replaced with the noun "start" or "end"
333 identifying one end of a clip,
334 second string is the name of that clip,
335 first number gives the position of that clip in a sequence
336 of clips,
337 last number counts all clips,
338 and the last string is the name of the track containing the
339 clips.
340 */
341 "%s %s, %d of %d clip %s",
342 "%s %s, %d of %d clips %s",
343 3
344 )(
345 result.clipStart1 ? XO("start") : XO("end"),
346 result.name1,
347 result.index1 + 1,
348 nClips,
349 longName
350 );
351 }
352 else {
353 str = XP(
354 /* i18n-hint:
355 First and third %s are each replaced with the noun "start"
356 or with "end", identifying and end of a clip,
357 second and fourth strings are the names of those clips,
358 first and second numbers give the position of those clips in
359 a sequence of clips,
360 last number counts all clips,
361 and the last string is the name of the track containing the
362 clips.
363 */
364 "%s %s and %s %s, %d and %d of %d clip %s",
365 "%s %s and %s %s, %d and %d of %d clips %s",
366 6
367 )(
368 result.clipStart1 ? XO("start") : XO("end"),
369 result.name1,
370 result.clipStart2 ? XO("start") : XO("end"),
371 result.name2,
372 result.index1 + 1,
373 result.index2 + 1,
374 nClips,
375 longName
376 );
377 }
378
379 if (message.empty())
380 message = str;
381 else
382 message = XO("%s, %s").Format( message, str );
383 }
384
385 return message;
386}
387
388void DoSelectClipBoundary(AudacityProject &project, bool next)
389{
390 auto &selectedRegion = ViewInfo::Get( project ).selectedRegion;
391 auto &trackFocus = TrackFocus::Get( project );
392
393 std::vector<FoundClipBoundary> results;
394 FindClipBoundaries(project, next ? selectedRegion.t1() :
395 selectedRegion.t0(), next, results);
396
397 if (results.size() > 0) {
398 // note that if there is more than one result, each has the same time
399 // value.
400 if (next)
401 selectedRegion.setT1(results[0].time);
402 else
403 selectedRegion.setT0(results[0].time);
404
405 ProjectHistory::Get( project ).ModifyState(false);
406
407 auto message = ClipBoundaryMessage(results);
408 trackFocus.MessageForScreenReader(message);
409 }
410}
411
413(AudacityProject &project, const WaveTrack* wt, double t0, double t1)
414{
415 (void)project;//Compiler food.
416
417 FoundClip result{};
418 result.waveTrack = wt;
419 const auto clips = wt->SortedClipArray();
420
421 t0 = AdjustForFindingStartTimes(clips, t0);
422
423 {
424 auto p = std::find_if(clips.begin(), clips.end(),
425 [&] (const WaveClip* const& clip) {
426 return clip->GetPlayStartTime() == t0; });
427 if (p != clips.end() && (*p)->GetPlayEndTime() > t1) {
428 result.found = true;
429 result.startTime = (*p)->GetPlayStartTime();
430 result.endTime = (*p)->GetPlayEndTime();
431 result.name = (*p)->GetName();
432 result.index = std::distance(clips.begin(), p);
433 return result;
434 }
435 }
436
437 {
438 auto p = std::find_if(clips.begin(), clips.end(),
439 [&] (const WaveClip* const& clip) {
440 return clip->GetPlayStartTime() > t0; });
441 if (p != clips.end()) {
442 result.found = true;
443 result.startTime = (*p)->GetPlayStartTime();
444 result.endTime = (*p)->GetPlayEndTime();
445 result.name = (*p)->GetName();
446 result.index = std::distance(clips.begin(), p);
447 return result;
448 }
449 }
450
451 return result;
452}
453
455(AudacityProject &project, const WaveTrack* wt, double t0, double t1)
456{
457 (void)project;//Compiler food.
458
459 FoundClip result{};
460 result.waveTrack = wt;
461 const auto clips = wt->SortedClipArray();
462
463 t0 = AdjustForFindingStartTimes(clips, t0);
464
465 {
466 auto p = std::find_if(clips.begin(), clips.end(),
467 [&] (const WaveClip* const& clip) {
468 return clip->GetPlayStartTime() == t0; });
469 if (p != clips.end() && (*p)->GetPlayEndTime() < t1) {
470 result.found = true;
471 result.startTime = (*p)->GetPlayStartTime();
472 result.endTime = (*p)->GetPlayEndTime();
473 result.name = (*p)->GetName();
474 result.index = std::distance(clips.begin(), p);
475 return result;
476 }
477 }
478
479 {
480 auto p = std::find_if(clips.rbegin(), clips.rend(),
481 [&] (const WaveClip* const& clip) {
482 return clip->GetPlayStartTime() < t0; });
483 if (p != clips.rend()) {
484 result.found = true;
485 result.startTime = (*p)->GetPlayStartTime();
486 result.endTime = (*p)->GetPlayEndTime();
487 result.name = (*p)->GetName();
488 result.index =
489 static_cast<int>(clips.size()) - 1 -
490 std::distance(clips.rbegin(), p);
491 return result;
492 }
493 }
494
495 return result;
496}
497
499(AudacityProject &project,
500 double t0, double t1, bool next, std::vector<FoundClip>& finalResults)
501{
502 auto &tracks = TrackList::Get( project );
503 finalResults.clear();
504
505 bool anyWaveTracksSelected{ tracks.Selected< const WaveTrack >() };
506
507 // first search the tracks individually
508
509 std::vector<FoundClip> results;
510
511 int nTracksSearched = 0;
512 auto leaders = tracks.Leaders();
513 auto rangeLeaders = leaders.Filter<const WaveTrack>();
514 if (anyWaveTracksSelected)
515 rangeLeaders = rangeLeaders + &Track::GetSelected;
516 for (auto waveTrack : rangeLeaders) {
517 bool stereoAndDiff = ChannelsHaveDifferentClipBoundaries(waveTrack);
518
519 auto rangeChans = stereoAndDiff
520 ? TrackList::Channels( waveTrack )
521 : TrackList::SingletonRange( waveTrack );
522
523 for ( auto wt : rangeChans ) {
524 auto result = next ? FindNextClip(project, wt, t0, t1) :
525 FindPrevClip(project, wt, t0, t1);
526 if (result.found) {
527 result.trackNum =
528 1 + std::distance( leaders.begin(), leaders.find( waveTrack ) );
529 result.channel = stereoAndDiff;
530 results.push_back(result);
531 }
532 }
533
534 nTracksSearched++;
535 }
536
537
538 if (results.size() > 0) {
539 // if any clips were found,
540 // find the clip or clips with the min/max start time
541 auto compareStart = [] (const FoundClip& a, const FoundClip& b)
542 { return a.startTime < b.startTime; };
543
544 auto pStart = next
545 ? std::min_element(results.begin(), results.end(), compareStart)
546 : std::max_element(results.begin(), results.end(), compareStart);
547
548 std::vector<FoundClip> resultsStartTime;
549 for ( auto &r : results )
550 if ( r.startTime == (*pStart).startTime )
551 resultsStartTime.push_back( r );
552
553 if (resultsStartTime.size() > 1) {
554 // more than one clip with same start time so
555 // find the clip or clips with the min/max end time
556 auto compareEnd = [] (const FoundClip& a, const FoundClip& b)
557 { return a.endTime < b.endTime; };
558
559 auto pEnd = next ? std::min_element(resultsStartTime.begin(),
560 resultsStartTime.end(), compareEnd) :
561 std::max_element(resultsStartTime.begin(),
562 resultsStartTime.end(), compareEnd);
563
564 for ( auto &r : resultsStartTime )
565 if ( r.endTime == (*pEnd).endTime )
566 finalResults.push_back( r );
567 }
568 else {
569 finalResults = resultsStartTime;
570 }
571 }
572
573 return nTracksSearched; // can be used for screen reader messages if required
574}
575
576void DoSelectClip(AudacityProject &project, bool next)
577{
578 auto &selectedRegion = ViewInfo::Get( project ).selectedRegion;
579 auto &trackFocus = TrackFocus::Get( project );
580 auto &window = ProjectWindow::Get( project );
581
582 std::vector<FoundClip> results;
583 FindClips(project, selectedRegion.t0(),
584 selectedRegion.t1(), next, results);
585
586 if (results.size() > 0) {
587 // note that if there is more than one result, each has the same start
588 // and end time
589 double t0 = results[0].startTime;
590 double t1 = results[0].endTime;
591 selectedRegion.setTimes(t0, t1);
592 ProjectHistory::Get( project ).ModifyState(false);
593 window.ScrollIntoView(selectedRegion.t0());
594
595 // create and send message to screen reader
596 TranslatableString message;
597 for (auto& result : results) {
598 auto longName = result.ComposeTrackName();
599 auto nClips = result.waveTrack->GetNumClips();
600 auto str = XP(
601 /* i18n-hint:
602 first string is the name of a clip,
603 first number gives the position of that clip
604 in a sequence of clips,
605 last number counts all clips,
606 last string names a track */
607 "%s, %d of %d clip %s",
608 "%s, %d of %d clips %s",
609 2
610 )(
611 result.name,
612 result.index + 1,
613 nClips,
614 longName
615 );
616
617 if (message.empty())
618 message = str;
619 else
620 message = XO("%s, %s").Format( message, str );
621 }
622 trackFocus.MessageForScreenReader(message);
623 }
624}
625
627(AudacityProject &project, bool next)
628{
629 auto &selectedRegion = ViewInfo::Get( project ).selectedRegion;
630 auto &trackFocus = TrackFocus::Get( project );
631 auto &window = ProjectWindow::Get( project );
632
633 std::vector<FoundClipBoundary> results;
634 FindClipBoundaries(project, next ? selectedRegion.t1() :
635 selectedRegion.t0(), next, results);
636
637 if (results.size() > 0) {
638 // note that if there is more than one result, each has the same time
639 // value.
640 double time = results[0].time;
641 selectedRegion.setTimes(time, time);
642 ProjectHistory::Get( project ).ModifyState(false);
643 window.ScrollIntoView(selectedRegion.t0());
644
645 auto message = ClipBoundaryMessage(results);
646 trackFocus.MessageForScreenReader(message);
647 }
648}
649
650// This function returns the amount moved. Possibly 0.0.
651double DoClipMove( AudacityProject &project, Track *track,
652 TrackList &trackList, bool syncLocked, bool right )
653{
654 auto &viewInfo = ViewInfo::Get(project);
655 auto &selectedRegion = viewInfo.selectedRegion;
656
657 if (track) {
658 ClipMoveState state;
659
660 auto t0 = selectedRegion.t0();
661
662 std::unique_ptr<TrackShifter> uShifter;
663
664 // Find the first channel that has a clip at time t0
665 auto hitTestResult = TrackShifter::HitTestResult::Track;
666 for (auto channel : TrackList::Channels(track) ) {
667 uShifter = MakeTrackShifter::Call( *channel, project );
668 if ( (hitTestResult = uShifter->HitTest( t0, viewInfo )) ==
670 uShifter.reset();
671 else
672 break;
673 }
674
675 if (!uShifter)
676 return 0.0;
677 auto pShifter = uShifter.get();
678 auto desiredT0 = viewInfo.OffsetTimeByPixels( t0, ( right ? 1 : -1 ) );
679 auto desiredSlideAmount = pShifter->HintOffsetLarger( desiredT0 - t0 );
680
681 state.Init( project, pShifter->GetTrack(), hitTestResult, std::move( uShifter ),
682 t0, viewInfo, trackList, syncLocked );
683
684 auto hSlideAmount = state.DoSlideHorizontal( desiredSlideAmount );
685
686 double newT0 = t0 + hSlideAmount;
687 if (hitTestResult != TrackShifter::HitTestResult::Track) {
688 // If necessary, correct for rounding errors. For example,
689 // for a wavetrack, ensure that t0 is still in the clip
690 // which it was within before the move.
691 // (pShifter is still undestroyed in the ClipMoveState.)
692 newT0 = pShifter->AdjustT0(newT0);
693 }
694
695 double diff = selectedRegion.duration();
696 selectedRegion.setTimes(newT0, newT0 + diff);
697
698 return hSlideAmount;
699 };
700 return 0.0;
701}
702
704(AudacityProject &project, bool right, bool keyUp )
705{
706 auto &undoManager = UndoManager::Get( project );
707 auto &window = ProjectWindow::Get( project );
708
709 if (keyUp) {
710 undoManager.StopConsolidating();
711 return;
712 }
713
714 auto &trackFocus = TrackFocus::Get( project );
715 auto &viewInfo = ViewInfo::Get( project );
716 auto &selectedRegion = viewInfo.selectedRegion;
717 const auto &settings = ProjectSettings::Get( project );
718 auto &tracks = TrackList::Get( project );
719 auto isSyncLocked = settings.IsSyncLocked();
720
721 auto amount = DoClipMove( project, trackFocus.Get(),
722 tracks, isSyncLocked, right );
723
724 window.ScrollIntoView(selectedRegion.t0());
725
726 if (amount != 0.0) {
727 auto message = right? XO("Time shifted clips to the right") :
728 XO("Time shifted clips to the left");
729
730 // The following use of the UndoPush flags is so that both a single
731 // keypress (keydown, then keyup), and holding down a key
732 // (multiple keydowns followed by a keyup) result in a single
733 // entry in Audacity's history dialog.
734 ProjectHistory::Get( project )
735 .PushState(message, XO("Time-Shift"), UndoPush::CONSOLIDATE);
736 }
737
738 if ( amount == 0.0 )
739 trackFocus.MessageForScreenReader( XO("clip not moved"));
740}
741
742}
743
745namespace ClipActions {
746
747// exported helper functions
748// none
749
750// Menu handler functions
751
753
755(const CommandContext &context)
756{
757 auto &project = context.project;
758 DoSelectClipBoundary(project, false);
759}
760
762(const CommandContext &context)
763{
764 auto &project = context.project;
765 DoSelectClipBoundary(project, true);
766}
767
769{
770 auto &project = context.project;
771 DoSelectClip(project, false);
772}
773
775{
776 auto &project = context.project;
777 DoSelectClip(project, true);
778}
779
781{
782 AudacityProject &project = context.project;
783
784 DoCursorClipBoundary(project, false);
785}
786
788{
789 AudacityProject &project = context.project;
790
791 DoCursorClipBoundary(project, true);
792}
793
794// PRL: Clip moving functions -- more than just selection adjustment. Do they
795// really belong in these navigation menus?
796void OnClipLeft(const CommandContext &context)
797{
798 auto &project = context.project;
799 auto evt = context.pEvt;
800 if (evt)
801 DoClipLeftOrRight( project, false, evt->GetEventType() == wxEVT_KEY_UP );
802 else { // called from menu, so simulate keydown and keyup
803 DoClipLeftOrRight( project, false, false );
804 DoClipLeftOrRight( project, false, true );
805 }
806}
807
808void OnClipRight(const CommandContext &context)
809{
810 auto &project = context.project;
811 auto evt = context.pEvt;
812 if (evt)
813 DoClipLeftOrRight( project, true, evt->GetEventType() == wxEVT_KEY_UP );
814 else { // called from menu, so simulate keydown and keyup
815 DoClipLeftOrRight( project, true, false );
816 DoClipLeftOrRight( project, true, true );
817 }
818}
819
820}; // struct Handler
821
822} // namespace
823
825 // Handler is not stateful. Doesn't need a factory registered with
826 // AudacityProject.
827 static ClipActions::Handler instance;
828 return instance;
829};
830
831// Menu definitions
832
833#define FN(X) (& ClipActions::Handler :: X)
834
835namespace {
836using namespace MenuTable;
837
838// Register menu items
839
841{
843
844 static BaseItemSharedPtr menu {
846 Menu( wxT("Clip"), XXO("Audi&o Clips"),
847 Command( wxT("SelPrevClipBoundaryToCursor"),
848 XXO("Pre&vious Clip Boundary to Cursor"),
849 FN(OnSelectPrevClipBoundaryToCursor),
851 Command( wxT("SelCursorToNextClipBoundary"),
852 XXO("Cursor to Ne&xt Clip Boundary"),
853 FN(OnSelectCursorToNextClipBoundary),
855 Command( wxT("SelPrevClip"), XXO("Previo&us Clip"),
856 FN(OnSelectPrevClip), WaveTracksExistFlag(),
857 Options{ wxT("Alt+,"), XO("Select Previous Clip") } ),
858 Command( wxT("SelNextClip"), XXO("N&ext Clip"), FN(OnSelectNextClip),
860 Options{ wxT("Alt+."), XO("Select Next Clip") } )
861 ) ) };
862 return menu;
863}
864
866 wxT("Select/Basic"),
868};
869
871{
873
874 static BaseItemSharedPtr items{
876 Items( wxT("Clip"),
877 Command( wxT("CursPrevClipBoundary"), XXO("Pre&vious Clip Boundary"),
878 FN(OnCursorPrevClipBoundary),
880 Options{}.LongName( XO("Cursor to Prev Clip Boundary") ) ),
881 Command( wxT("CursNextClipBoundary"), XXO("Ne&xt Clip Boundary"),
882 FN(OnCursorNextClipBoundary),
884 Options{}.LongName( XO("Cursor to Next Clip Boundary") ) )
885 ) ) };
886 return items;
887}
888
890 { wxT("Transport/Basic/Cursor"),
891 { OrderingHint::Before, wxT("CursProjectStart") } },
893};
894
896{
898 static BaseItemSharedPtr items{
900 Items( wxT("TimeShift"),
901 Command( wxT("ClipLeft"), XXO("Time Shift &Left"), FN(OnClipLeft),
902 TracksExistFlag() | TrackPanelHasFocus(), Options{}.WantKeyUp() ),
903 Command( wxT("ClipRight"), XXO("Time Shift &Right"), FN(OnClipRight),
904 TracksExistFlag() | TrackPanelHasFocus(), Options{}.WantKeyUp() )
905 ) ) };
906 return items;
907}
908
910 { wxT("Optional/Extra/Part1/Edit"), { OrderingHint::End, {} } },
912};
913
914}
915
916#undef FN
wxT("CloseDown"))
static CommandHandlerObject & findCommandHandler(AudacityProject &)
Definition: ClipMenus.cpp:824
#define FN(X)
Definition: ClipMenus.cpp:833
wxEvtHandler CommandHandlerObject
const ReservedCommandFlag & TracksExistFlag()
const ReservedCommandFlag & WaveTracksExistFlag()
const ReservedCommandFlag & TrackPanelHasFocus()
#define str(a)
const TranslatableString name
Definition: Distortion.cpp:82
#define XXO(s)
Definition: Internat.h:44
#define XO(s)
Definition: Internat.h:31
#define XP(sing, plur, n)
Definition: Internat.h:96
#define _(s)
Definition: Internat.h:75
static Settings & settings()
Definition: TrackInfo.cpp:87
static Return Call(This &obj, Arguments &&...arguments)
Invoke the method – but only after static initialization time.
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:89
Subclass & Get(const RegisteredFactory &key)
Get reference to an attachment, creating on demand if not present, down-cast it to Subclass.
Definition: ClientData.h:309
CommandContext provides additional information to an 'Apply()' command. It provides the project,...
const wxEvent * pEvt
AudacityProject & project
void PushState(const TranslatableString &desc, const TranslatableString &shortDesc)
void ModifyState(bool bWantsAutoSave)
static ProjectHistory & Get(AudacityProject &project)
static ProjectSettings & Get(AudacityProject &project)
static ProjectWindow & Get(AudacityProject &project)
Track * Get()
Abstract base class for an object holding data associated with points on a time axis.
Definition: Track.h:225
bool GetSelected() const
Definition: Track.h:469
A flat linked list of tracks supporting Add, Remove, Clear, and Contains, serialization of the list o...
Definition: Track.h:1338
static auto SingletonRange(TrackType *pTrack) -> TrackIterRange< TrackType >
Definition: Track.h:1505
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:486
static auto Channels(TrackType *pTrack) -> TrackIterRange< TrackType >
Definition: Track.h:1541
@ Track
Shift selected track and sister channels only, as a whole.
@ Miss
Don't shift anything.
Holds a msgid for the translation catalog; may also bind format arguments.
static UndoManager & Get(AudacityProject &project)
Definition: UndoManager.cpp:67
NotifyingSelectedRegion selectedRegion
Definition: ViewInfo.h:216
static ViewInfo & Get(AudacityProject &project)
Definition: ViewInfo.cpp:235
This allows multiple clips to be a part of one WaveTrack.
Definition: WaveClip.h:101
A Track that contains audio waveform data.
Definition: WaveTrack.h:57
WaveClipPointers SortedClipArray()
Definition: WaveTrack.cpp:2733
WaveClipHolders & GetClips()
Definition: WaveTrack.h:329
Namespace for functions for Clip menu.
Definition: ClipMenus.cpp:745
std::unique_ptr< MenuItem > Menu(const Identifier &internalName, const TranslatableString &title, Args &&... args)
std::unique_ptr< MenuItems > Items(const Identifier &internalName, Args &&... args)
std::unique_ptr< CommandItem > Command(const CommandID &name, const TranslatableString &label_in, void(Handler::*pmf)(const CommandContext &), CommandFlag flags, const CommandManager::Options &options={}, CommandHandlerFinder finder=FinderScope::DefaultFinder())
std::shared_ptr< BaseItem > BaseItemSharedPtr
Definition: Registry.h:72
double AdjustForFindingStartTimes(const std::vector< const WaveClip * > &clips, double time)
Definition: ClipMenus.cpp:107
BaseItemSharedPtr ExtraTimeShiftItems()
Definition: ClipMenus.cpp:895
int FindClipBoundaries(AudacityProject &project, double time, bool next, std::vector< FoundClipBoundary > &finalResults)
Definition: ClipMenus.cpp:261
BaseItemSharedPtr ClipSelectMenu()
Definition: ClipMenus.cpp:840
void DoCursorClipBoundary(AudacityProject &project, bool next)
Definition: ClipMenus.cpp:627
FoundClipBoundary FindNextClipBoundary(const WaveTrack *wt, double time)
Definition: ClipMenus.cpp:144
int FindClips(AudacityProject &project, double t0, double t1, bool next, std::vector< FoundClip > &finalResults)
Definition: ClipMenus.cpp:499
FoundClip FindNextClip(AudacityProject &project, const WaveTrack *wt, double t0, double t1)
Definition: ClipMenus.cpp:413
BaseItemSharedPtr ClipCursorItems()
Definition: ClipMenus.cpp:870
FoundClipBoundary FindPrevClipBoundary(const WaveTrack *wt, double time)
Definition: ClipMenus.cpp:197
TranslatableString ClipBoundaryMessage(const std::vector< FoundClipBoundary > &results)
Definition: ClipMenus.cpp:319
void DoSelectClipBoundary(AudacityProject &project, bool next)
Definition: ClipMenus.cpp:388
double AdjustForFindingEndTimes(const std::vector< const WaveClip * > &clips, double time)
Definition: ClipMenus.cpp:129
bool TwoChannelsHaveSameBoundaries(const WaveTrack *first, const WaveTrack *second)
Definition: ClipMenus.cpp:59
void DoClipLeftOrRight(AudacityProject &project, bool right, bool keyUp)
Definition: ClipMenus.cpp:704
void DoSelectClip(AudacityProject &project, bool next)
Definition: ClipMenus.cpp:576
double DoClipMove(AudacityProject &project, Track *track, TrackList &trackList, bool syncLocked, bool right)
Definition: ClipMenus.cpp:651
bool ChannelsHaveDifferentClipBoundaries(const WaveTrack *wt)
Definition: ClipMenus.cpp:83
FoundClip FindPrevClip(AudacityProject &project, const WaveTrack *wt, double t0, double t1)
Definition: ClipMenus.cpp:455
std::vector< CommandFlagOptions > & Options()
Definition: Menus.cpp:535
void OnClipRight(const CommandContext &context)
Definition: ClipMenus.cpp:808
void OnClipLeft(const CommandContext &context)
Definition: ClipMenus.cpp:796
void OnCursorNextClipBoundary(const CommandContext &context)
Definition: ClipMenus.cpp:787
void OnCursorPrevClipBoundary(const CommandContext &context)
Definition: ClipMenus.cpp:780
void OnSelectPrevClipBoundaryToCursor(const CommandContext &context)
Definition: ClipMenus.cpp:755
void OnSelectPrevClip(const CommandContext &context)
Definition: ClipMenus.cpp:768
void OnSelectNextClip(const CommandContext &context)
Definition: ClipMenus.cpp:774
void OnSelectCursorToNextClipBoundary(const CommandContext &context)
Definition: ClipMenus.cpp:762
double DoSlideHorizontal(double desiredSlideAmount)
Do sliding of tracks and intervals, maybe adjusting the offset.
void Init(AudacityProject &project, Track &capturedTrack, TrackShifter::HitTestResult hitTestResult, std::unique_ptr< TrackShifter > pHit, double clickTime, const ViewInfo &viewInfo, TrackList &trackList, bool syncLocked)
Will associate a TrackShifter with each track in the list.