Audacity 3.2.0
SelectMenus.cpp
Go to the documentation of this file.
1#include "../AdornedRulerPanel.h"
2#include "AudioIO.h"
3#include "BasicUI.h"
4#include "../CommonCommandFlags.h"
5#include "Prefs.h"
6#include "Project.h"
7#include "ProjectAudioIO.h"
8#include "../ProjectAudioManager.h"
9#include "ProjectHistory.h"
10#include "ProjectRate.h"
11#include "ProjectSnap.h"
12#include "../ProjectWindows.h"
13#include "../SelectUtilities.h"
14#include "SyncLock.h"
15#include "../TrackPanel.h"
16#include "Viewport.h"
17#include "WaveClip.h"
18#include "WaveTrack.h"
19#include "../LabelTrack.h"
20#include "CommandContext.h"
21#include "MenuRegistry.h"
22#include "../toolbars/ControlToolBar.h"
23#include "../tracks/ui/SelectHandle.h"
24#include "../tracks/labeltrack/ui/LabelTrackView.h"
25#include "../tracks/playabletrack/wavetrack/ui/WaveChannelView.h"
26
27// private helper classes and functions
28namespace {
29
30constexpr auto GetWindowSize(double projectRate)
31{
32 return size_t(std::max(1.0, projectRate / 100));
33}
34
36{
37 auto rate = ProjectRate::Get(project).GetRate();
38 auto &tracks = TrackList::Get( project );
39
40 // Window is 1/100th of a second.
41 auto windowSize = GetWindowSize(rate);
42 Floats dist{ windowSize, true };
43
44 int nTracks = 0;
45 for (auto one : tracks.Selected<const WaveTrack>()) {
46 const auto nChannels = one->NChannels();
47 auto oneWindowSize = size_t(std::max(1.0, one->GetRate() / 100));
48 Floats buffer1{ oneWindowSize };
49 Floats buffer2{ oneWindowSize };
50 float *const buffers[]{ buffer1.get(), buffer2.get() };
51 auto s = one->TimeToLongSamples(t0);
52
53 // fillTwo to ensure that missing values are treated as 2, and hence do
54 // not get used as zero crossings.
55 one->GetFloats(0, nChannels, buffers,
56 s - (int)oneWindowSize/2, oneWindowSize, false, FillFormat::fillTwo);
57
58
59 // Looking for actual crossings. Update dist
60 for (size_t iChannel = 0; iChannel < nChannels; ++iChannel) {
61 const auto oneDist = buffers[iChannel];
62 double prev = 2.0;
63 for (size_t i = 0; i < oneWindowSize; ++i) {
64 float fDist = fabs(oneDist[i]); // score is absolute value
65 if (prev * oneDist[i] > 0) // both same sign? No good.
66 fDist = fDist + 0.4; // No good if same sign.
67 else if (prev > 0.0)
68 fDist = fDist + 0.1; // medium penalty for downward crossing.
69 prev = oneDist[i];
70 oneDist[i] = fDist;
71 }
72
73 // TODO: The mixed rate zero crossing code is broken,
74 // if oneWindowSize > windowSize we'll miss out some
75 // samples - so they will still be zero, so we'll use them.
76 for (size_t i = 0; i < windowSize; i++) {
77 size_t j;
78 if (windowSize != oneWindowSize)
79 j = i * (oneWindowSize - 1) / (windowSize - 1);
80 else
81 j = i;
82
83 dist[i] += oneDist[j];
84 // Apply a small penalty for distance from the original endpoint
85 // We'll always prefer an upward
86 dist[i] +=
87 0.1 * (abs(int(i) - int(windowSize / 2))) / float(windowSize / 2);
88 }
89 }
90 nTracks++;
91 }
92
93 // Find minimum
94 int argmin = 0;
95 float min = 3.0;
96 for (size_t i = 0; i < windowSize; ++i) {
97 if (dist[i] < min) {
98 argmin = i;
99 min = dist[i];
100 }
101 }
102
103 // If we're worse than 0.2 on average, on one track, then no good.
104 if ((nTracks == 1) && (min > (0.2 * nTracks)))
105 return t0;
106 // If we're worse than 0.6 on average, on multi-track, then no good.
107 if ((nTracks > 1) && (min > (0.6 * nTracks)))
108 return t0;
109
110 return t0 + (argmin - (int)windowSize / 2) / rate;
111}
112
113// If this returns true, then there was a key up, and nothing more to do,
114// after this function has completed.
115// (at most this function just does a ModifyState for the keyup)
116bool OnlyHandleKeyUp( const CommandContext &context )
117{
118 auto &project = context.project;
119 auto evt = context.pEvt;
120 bool bKeyUp = (evt) && evt->GetEventType() == wxEVT_KEY_UP;
121
123 return bKeyUp;
124 if( !bKeyUp )
125 return false;
126
128 return true;
129}
130
133 DIRECTION_RIGHT = +1
135
141
146
148{
149 wxLongLong mLastSelectionAdjustment { ::wxGetUTCTimeMillis() };
150 double mSeekShort{ 0.0 };
151 double mSeekLong{ 0.0 };
152};
153
154void SeekWhenAudioActive(double seekStep, wxLongLong &lastSelectionAdjustment)
155{
156 auto gAudioIO = AudioIO::Get();
157#ifdef EXPERIMENTAL_IMPROVED_SEEKING
158 if (gAudioIO->GetLastPlaybackTime() < lastSelectionAdjustment) {
159 // Allow time for the last seek to output a buffer before
160 // discarding samples again
161 // Do not advance mLastSelectionAdjustment
162 return;
163 }
164#endif
165 lastSelectionAdjustment = ::wxGetUTCTimeMillis();
166
167 gAudioIO->SeekStream(seekStep);
168}
169
170// Handles moving a selection edge with the keyboard in snap-to-time mode;
171// returns the moved value.
172// Will move at least minPix pixels -- set minPix positive to move forward,
173// negative to move backward.
174// Helper for moving by keyboard with snap-to-grid enabled
176(AudacityProject &project, double t, int minPix)
177{
178 auto& projectSnap = ProjectSnap::Get(project);
179 auto &viewInfo = ViewInfo::Get( project );
180
181 auto result = projectSnap.SingleStep(t, minPix >= 0).time;
182
183 if (
184 std::abs(viewInfo.TimeToPosition(result) - viewInfo.TimeToPosition(t)) >=
185 abs(minPix))
186 return result;
187
188 // Otherwise, move minPix pixels, then snap to the time.
189 result = viewInfo.OffsetTimeByPixels(t, minPix);
190 return projectSnap.SnapTime(result).time;
191}
192
195 double t, double offset, TimeUnit timeUnit, SnapMode snapMode)
196{
197 auto &viewInfo = ViewInfo::Get( project );
198
199 if (timeUnit == TIME_UNIT_SECONDS)
200 return t + offset; // snapping is currently ignored for non-pixel moves
201
202 if (snapMode == SnapMode::SNAP_OFF)
203 return viewInfo.OffsetTimeByPixels(t, (int)offset);
204
205 return GridMove(project, t, (int)offset);
206}
207
208// Moving a cursor, and collapsed selection.
210(AudacityProject &project, double seekStep, TimeUnit timeUnit)
211{
212 auto &viewInfo = ViewInfo::Get(project);
213 auto &trackPanel = TrackPanel::Get(project);
216 const auto &settings = ProjectSnap::Get(project);
217 auto &viewport = Viewport::Get(project);
218
219 // If TIME_UNIT_SECONDS, snap-to will be off.
220 auto snapMode = settings.GetSnapMode();
221
222 // Move the cursor
223 // Already in cursor mode?
224 if (viewInfo.selectedRegion.isPoint())
225 {
226 double newT = OffsetTime(
227 project, viewInfo.selectedRegion.t0(), seekStep, timeUnit, snapMode);
228 // constrain.
229 newT = std::max(0.0, newT);
230 // Move
231 viewInfo.selectedRegion.setT0(
232 newT,
233 false); // do not swap selection boundaries
234 viewInfo.selectedRegion.collapseToT0();
235
236 // Move the visual cursor, avoiding an unnecessary complete redraw
237 trackPanel.DrawOverlays(false);
238 ruler.DrawOverlays(false);
239 }
240 else
241 {
242 // Transition to cursor mode.
243 constexpr auto maySwapBoundaries = false;
244 if (seekStep < 0)
245 {
246 if (snapMode != SnapMode::SNAP_OFF)
247 viewInfo.selectedRegion.setT0(
248 settings.SnapTime(viewInfo.selectedRegion.t0()).time,
249 maySwapBoundaries);
250 viewInfo.selectedRegion.collapseToT0();
251 }
252 else
253 {
254 if (snapMode != SnapMode::SNAP_OFF)
255 viewInfo.selectedRegion.setT1(
256 settings.SnapTime(viewInfo.selectedRegion.t1()).time,
257 maySwapBoundaries);
258 viewInfo.selectedRegion.collapseToT1();
259 }
260 trackPanel.Refresh(false);
261 }
262
263 // Make sure NEW position is in view
264 viewport.ScrollIntoView(viewInfo.selectedRegion.t1());
265 return;
266}
267
269(AudacityProject &project, double seekStep, TimeUnit timeUnit,
270SelectionOperation operation)
271{
272 auto &viewInfo = ViewInfo::Get(project);
274 const auto &settings = ProjectSnap::Get(project);
275 auto &viewport = Viewport::Get(project);
276
277 if (operation == CURSOR_MOVE)
278 {
279 MoveWhenAudioInactive( project, seekStep, timeUnit);
280 return;
281 }
282
283 auto snapMode = settings.GetSnapMode();
284 const double t0 = viewInfo.selectedRegion.t0();
285 const double t1 = viewInfo.selectedRegion.t1();
286 const double end = std::max(
287 tracks.GetEndTime(), viewInfo.GetScreenEndTime());
288
289 // Is it t0 or t1 moving?
290 bool bMoveT0 = (operation == SELECTION_CONTRACT && seekStep > 0) ||
291 (operation == SELECTION_EXTEND && seekStep < 0);
292 // newT is where we want to move to
293 double newT = OffsetTime( project,
294 bMoveT0 ? t0 : t1, seekStep, timeUnit, snapMode);
295 // constrain to be in the track/screen limits.
296 newT = std::max( 0.0, newT );
297 newT = std::min( newT, end);
298 // optionally constrain to be a contraction, i.e. so t0/t1 do not cross over
299 if( operation == SELECTION_CONTRACT )
300 newT = bMoveT0 ? std::min( t1, newT ) : std::max( t0, newT );
301
302 // Actually move
303 if( bMoveT0 )
304 viewInfo.selectedRegion.setT0( newT );
305 else
306 viewInfo.selectedRegion.setT1( newT );
307
308 // Ensure it is visible
309 viewport.ScrollIntoView(newT);
310}
311
312// Handle small cursor and play head movements
314(AudacityProject &project, double direction, SelectionOperation operation,
315 SeekInfo &info)
316{
317 // PRL: What I found and preserved, strange though it be:
318 // During playback: jump depends on preferences and is independent of the
319 // zoom and does not vary if the key is held
320 // Else: jump depends on the zoom and gets bigger if the key is held
321
322 if( ProjectAudioIO::Get( project ).IsAudioActive() )
323 {
324 if( operation == CURSOR_MOVE )
325 SeekWhenAudioActive( info.mSeekShort * direction,
327 else if( operation == SELECTION_EXTEND )
328 SeekWhenAudioActive( info.mSeekLong * direction,
330 // Note: no action for CURSOR_CONTRACT
331 return;
332 }
333
334 // If the last adjustment was very recent, we are
335 // holding the key down and should move faster.
336 const wxLongLong curtime = ::wxGetUTCTimeMillis();
337 enum { MIN_INTERVAL = 50 };
338 const bool fast =
339 (curtime - info.mLastSelectionAdjustment < MIN_INTERVAL);
340
341 info.mLastSelectionAdjustment = curtime;
342
343 // How much faster should the cursor move if shift is down?
344 enum { LARGER_MULTIPLIER = 4 };
345 const double seekStep = (fast ? LARGER_MULTIPLIER : 1.0) * direction;
346
347 SeekWhenAudioInactive( project, seekStep, TIME_UNIT_PIXELS, operation);
348}
349
350// Move the cursor forward or backward, while paused or while playing.
352 AudacityProject &project, double seekStep,
353 wxLongLong &lastSelectionAdjustment)
354{
355 if (ProjectAudioIO::Get( project ).IsAudioActive()) {
356 SeekWhenAudioActive(seekStep, lastSelectionAdjustment);
357 }
358 else
359 {
360 lastSelectionAdjustment = ::wxGetUTCTimeMillis();
362 }
363
365}
366
368{
369 auto &viewInfo = ViewInfo::Get(project);
371 auto &viewport = Viewport::Get(project);
372
373 // step is negative, then is moving left. step positive, moving right.
374 // Move the left/right selection boundary, to expand the selection
375
376 // If the last adjustment was very recent, we are
377 // holding the key down and should move faster.
378 wxLongLong curtime = ::wxGetUTCTimeMillis();
379 int pixels = step;
380 if (curtime - info.mLastSelectionAdjustment < 50)
381 pixels *= 4;
382 info.mLastSelectionAdjustment = curtime;
383
384 // we used to have a parameter boundaryContract to say if expanding or
385 // contracting. it is no longer needed.
386 bool bMoveT0 = (step < 0 );// ^ boundaryContract ;
387
389 auto gAudioIO = AudioIO::Get();
390 double indicator = gAudioIO->GetStreamTime();
391 if( bMoveT0 )
392 viewInfo.selectedRegion.setT0(indicator, false);
393 else
394 viewInfo.selectedRegion.setT1(indicator);
395
397 return;
398 }
399
400 const double t0 = viewInfo.selectedRegion.t0();
401 const double t1 = viewInfo.selectedRegion.t1();
402 const double end = std::max(
403 tracks.GetEndTime(), viewInfo.GetScreenEndTime());
404
405 double newT = viewInfo.OffsetTimeByPixels( bMoveT0 ? t0 : t1, pixels);
406 // constrain to be in the track/screen limits.
407 newT = std::max( 0.0, newT );
408 newT = std::min( newT, end);
409 // optionally constrain to be a contraction, i.e. so t0/t1 do not cross over
410 //if( boundaryContract )
411 // newT = bMoveT0 ? std::min( t1, newT ) : std::max( t0, newT );
412
413 // Actually move
414 if( bMoveT0 )
415 viewInfo.selectedRegion.setT0( newT );
416 else
417 viewInfo.selectedRegion.setT1( newT );
418
419 // Ensure it is visible
420 viewport.ScrollIntoView(newT);
421
423}
424
425}
426
427namespace SelectActions {
428
429
430// Menu handler functions
431
433 : CommandHandlerObject // MUST be the first base class!
436{
437
438void OnSelectAll(const CommandContext &context)
439{
440 auto& trackPanel = TrackPanel::Get(context.project);
441 auto& tracks = TrackList::Get(context.project);
442
443 for (auto lt : tracks.Selected<LabelTrack>()) {
444 auto& view = LabelTrackView::Get(*lt);
445 if (view.SelectAllText(context.project)) {
446 trackPanel.Refresh(false);
447 return;
448 }
449 }
450
451 //Presumably, there might be not more than one track
452 //that expects text input
453 for (auto wt : tracks.Any<WaveTrack>()) {
454 auto& view = WaveChannelView::Get(*wt);
455 if (view.SelectAllText(context.project)) {
456 trackPanel.Refresh(false);
457 return;
458 }
459 }
460
462}
463
464void OnSelectNone(const CommandContext &context)
465{
466 auto &project = context.project;
467 auto &selectedRegion = ViewInfo::Get( project ).selectedRegion;
468
469 selectedRegion.collapseToT0();
472}
473
475{
476 auto &project = context.project;
478}
479
481{
482 auto &project = context.project;
483 auto &tracks = TrackList::Get( project );
484
485 bool selected = false;
486 for (auto t : tracks.Any() + &Track::SupportsBasicEditing
488 t->SetSelected(true);
489 selected = true;
490 }
491
492 if (selected)
494}
495
497{
499 true, true, XO("Set Left Selection Boundary"));
500}
501
503{
505 false, true, XO("Set Right Selection Boundary"));
506}
507
509{
510 auto &project = context.project;
512 auto &selectedRegion = ViewInfo::Get(project).selectedRegion;
513
514 double kWayOverToRight = std::numeric_limits<double>::max();
515
516 auto range = tracks.Selected();
517 if (!range)
518 return;
519
520 double minOffset = range.min(&Track::GetStartTime);
521
522 if( minOffset >=
523 (kWayOverToRight * (1 - std::numeric_limits<double>::epsilon()) ))
524 return;
525
526 selectedRegion.setT0(minOffset);
528}
529
531{
532 auto &project = context.project;
534 auto &selectedRegion = ViewInfo::Get(project).selectedRegion;
535
536 double kWayOverToLeft = std::numeric_limits<double>::lowest();
537
538 auto range = tracks.Selected();
539 if (!range)
540 return;
541
542 double maxEndOffset = range.max(&Track::GetEndTime);
543
544 if( maxEndOffset <=
545 (kWayOverToLeft * (1 - std::numeric_limits<double>::epsilon()) ))
546 return;
547
548 selectedRegion.setT1(maxEndOffset);
550}
551
553{
554 auto &project = context.project;
555 auto &viewInfo = ViewInfo::Get(project);
557
558 auto range = tracks.Selected();
559 double maxEndOffset = range.max(&Track::GetEndTime);
560 double minOffset = range.min(&Track::GetStartTime);
561
562 if( maxEndOffset < minOffset)
563 return;
564
565 viewInfo.selectedRegion.setTimes(minOffset, maxEndOffset);
567}
568
569// Handler state:
571
572void OnSelectionSave(const CommandContext &context)
573{
574 auto &project = context.project;
575 auto &selectedRegion = ViewInfo::Get( project ).selectedRegion;
576
577 mRegionSave = selectedRegion;
578}
579
581{
582 auto &project = context.project;
583 auto &selectedRegion = ViewInfo::Get( project ).selectedRegion;
584 auto &viewport = Viewport::Get(project);
585
586 if ((mRegionSave.t0() == 0.0) &&
587 (mRegionSave.t1() == 0.0))
588 return;
589
590 selectedRegion = mRegionSave;
591 viewport.ScrollIntoView(selectedRegion.t0());
592
594}
595
596// Handler state:
599
601{
602 auto &project = context.project;
603 auto &selectedRegion = ViewInfo::Get( project ).selectedRegion;
604 auto isAudioActive = ProjectAudioIO::Get( project ).IsAudioActive();
605
607 auto gAudioIO = AudioIO::Get();
608 double cursorPositionCurrent = isAudioActive
609 ? gAudioIO->GetStreamTime()
610 : selectedRegion.t0();
611 selectedRegion.setTimes(
612 std::min(cursorPositionCurrent, mCursorPositionStored),
613 std::max(cursorPositionCurrent, mCursorPositionStored));
614
616 }
617}
618
620{
621 auto &project = context.project;
622 auto &selectedRegion = ViewInfo::Get( project ).selectedRegion;
623 auto isAudioActive = ProjectAudioIO::Get( project ).IsAudioActive();
624
625 auto gAudioIO = AudioIO::Get();
627 isAudioActive ? gAudioIO->GetStreamTime() : selectedRegion.t0();
629}
630
631void OnZeroCrossing(const CommandContext &context)
632{
633 auto &project = context.project;
634 auto &selectedRegion = ViewInfo::Get( project ).selectedRegion;
635 const auto& tracks = TrackList::Get(project);
636
637 // Selecting precise sample indices across tracks that may have clips with
638 // various stretch ratios in itself is not possible. Even in single-track
639 // mode, we cannot know what the final waveform will look like until
640 // stretching is applied, making this operation futile. Hence we disallow
641 // it if any stretched clip is involved.
642 const auto projectRate = ProjectRate(project).GetRate();
643 const auto searchWindowDuration = GetWindowSize(projectRate) / projectRate;
644 const auto wouldSearchStretchedClip =
645 [searchWindowDuration](const WaveTrack& track, double t) {
646 const auto clips = track.GetClipsIntersecting(
647 t - searchWindowDuration / 2, t + searchWindowDuration / 2);
648 return std::any_of(
649 clips.begin(), clips.end(),
650 [](const std::shared_ptr<const WaveClip>& clip) {
651 return !clip->StretchRatioEquals(1);
652 });
653 };
654 const auto selected = tracks.Selected<const WaveTrack>();
655 if (std::any_of(
656 selected.begin(), selected.end(), [&](const WaveTrack* track) {
657 return wouldSearchStretchedClip(*track, selectedRegion.t0()) ||
658 wouldSearchStretchedClip(*track, selectedRegion.t1());
659 }))
660 {
661 using namespace BasicUI;
663 XO("Zero-crossing search regions intersect stretched clip(s)."),
664 MessageBoxOptions {}.Caption(XO("Error")).IconStyle(Icon::Error));
665 return;
666 }
667
668 const double t0 = NearestZeroCrossing(project, selectedRegion.t0());
669 if (selectedRegion.isPoint())
670 selectedRegion.setTimes(t0, t0);
671 else {
672 const double t1 = NearestZeroCrossing(project, selectedRegion.t1());
673 // Empty selection is generally not much use, so do not make it if empty.
674 if( fabs( t1 - t0 ) * ProjectRate::Get(project).GetRate() > 1.5 )
675 selectedRegion.setTimes(t0, t1);
676 }
677
679}
680
681void OnSnapToOff(const CommandContext &context)
682{
683 auto &project = context.project;
685}
686
687void OnSnapToNearest(const CommandContext &context)
688{
689 auto &project = context.project;
691}
692
693void OnSnapToPrior(const CommandContext &context)
694{
695 auto &project = context.project;
697}
698
699void OnSelToStart(const CommandContext &context)
700{
701 auto &project = context.project;
702 auto &viewport = Viewport::Get(project);
703 viewport.ScrollToStart(true);
705}
706
707void OnSelToEnd(const CommandContext &context)
708{
709 auto &project = context.project;
710 auto &viewport = Viewport::Get(project);
711 viewport.ScrollToEnd(true);
713}
714
715// Handler state:
716SeekInfo mSeekInfo;
717
718void OnSelExtendLeft(const CommandContext &context)
719{
720 if( !OnlyHandleKeyUp( context ) )
722 mSeekInfo );
723}
724
726{
727 if( !OnlyHandleKeyUp( context ) )
729 mSeekInfo );
730}
731
733{
735}
736
738{
740}
741
743{
744 if( !OnlyHandleKeyUp( context ) )
746 mSeekInfo );
747}
748
750{
751 if( !OnlyHandleKeyUp( context ) )
753 mSeekInfo );
754}
755
757{
758 auto &project = context.project;
759 auto &selectedRegion = ViewInfo::Get( project ).selectedRegion;
760 auto &viewport = Viewport::Get(project);
761
762 selectedRegion.collapseToT0();
764 viewport.ScrollIntoView(selectedRegion.t0());
765}
766
767void OnCursorSelEnd(const CommandContext &context)
768{
769 auto &project = context.project;
770 auto &selectedRegion = ViewInfo::Get( project ).selectedRegion;
771 auto &viewport = Viewport::Get(project);
772
773 selectedRegion.collapseToT1();
775 viewport.ScrollIntoView(selectedRegion.t1());
776}
777
779{
780 auto &project = context.project;
782 auto &selectedRegion = ViewInfo::Get(project).selectedRegion;
783 auto &viewport = Viewport::Get(project);
784
785 double kWayOverToRight = std::numeric_limits<double>::max();
786
787 auto trackRange = tracks.Selected() + &Track::SupportsBasicEditing;
788 if (trackRange.empty())
789 // This should have been prevented by command manager
790 return;
791
792 // Range is surely nonempty now
793 auto minOffset = std::max(0.0, trackRange.min(&Track::GetStartTime));
794
795 if( minOffset >=
796 (kWayOverToRight * (1 - std::numeric_limits<double>::epsilon()) ))
797 return;
798
799 selectedRegion.setTimes(minOffset, minOffset);
801 viewport.ScrollIntoView(selectedRegion.t0());
802}
803
805{
806 auto &project = context.project;
808 auto &selectedRegion = ViewInfo::Get(project).selectedRegion;
809 auto &viewport = Viewport::Get(project);
810
811 double kWayOverToLeft = std::numeric_limits<double>::lowest();
812
813 auto trackRange = tracks.Selected() + &Track::SupportsBasicEditing;
814 if (trackRange.empty())
815 // This should have been prevented by command manager
816 return;
817
818 // Range is surely nonempty now
819 auto maxEndOffset = trackRange.max(&Track::GetEndTime);
820
821 if( maxEndOffset <
822 (kWayOverToLeft * (1 - std::numeric_limits<double>::epsilon()) ))
823 return;
824
825 selectedRegion.setTimes(maxEndOffset, maxEndOffset);
827 viewport.ScrollIntoView(selectedRegion.t1());
828}
829
830void OnSkipStart(const CommandContext &context)
831{
832 auto &project = context.project;
833 wxCommandEvent evt;
834
835 auto &controlToolBar = ControlToolBar::Get( project );
836 controlToolBar.OnRewind(evt);
838}
839
840void OnSkipEnd(const CommandContext &context)
841{
842 auto &project = context.project;
843 wxCommandEvent evt;
844
845 auto &controlToolBar = ControlToolBar::Get( project );
846 controlToolBar.OnFF(evt);
848}
849
850void OnCursorLeft(const CommandContext &context)
851{
852 if( !OnlyHandleKeyUp( context ) )
854 mSeekInfo );
855}
856
857void OnCursorRight(const CommandContext &context)
858{
859 if( !OnlyHandleKeyUp( context ) )
861 mSeekInfo );
862}
863
865{
866 DoCursorMove( context.project,
867 -mSeekInfo.mSeekShort, mSeekInfo.mLastSelectionAdjustment );
868}
869
871{
872 DoCursorMove( context.project,
873 mSeekInfo.mSeekShort, mSeekInfo.mLastSelectionAdjustment );
874}
875
877{
878 DoCursorMove( context.project,
879 -mSeekInfo.mSeekLong, mSeekInfo.mLastSelectionAdjustment );
880}
881
883{
884 DoCursorMove( context.project,
885 mSeekInfo.mSeekLong, mSeekInfo.mLastSelectionAdjustment );
886}
887
888void OnSeekLeftShort(const CommandContext &context)
889{
890 auto &project = context.project;
892}
893
895{
896 auto &project = context.project;
898}
899
900void OnSeekLeftLong(const CommandContext &context)
901{
902 auto &project = context.project;
904}
905
906void OnSeekRightLong(const CommandContext &context)
907{
908 auto &project = context.project;
910}
911
912#if 1
913// Legacy functions, not used as of version 2.3.0
914void OnSelectAllTime(const CommandContext &context)
915{
916 auto &project = context.project;
918}
919#endif
920
921void UpdatePrefs() override
922{
923 gPrefs->Read(wxT("/AudioIO/SeekShortPeriod"), &mSeekInfo.mSeekShort, 1.0);
924 gPrefs->Read(wxT("/AudioIO/SeekLongPeriod"), &mSeekInfo.mSeekLong, 15.0);
925}
927{
928 UpdatePrefs();
929}
930Handler( const Handler & ) = delete;
931Handler &operator=( const Handler & ) = delete;
932
933}; // struct Handler
934
935} // namespace
936
937// Handler is stateful. Needs a factory registered with
938// AudacityProject.
940 [](AudacityProject&) {
941 return std::make_unique< SelectActions::Handler >(); } };
942
944 return project.AttachedObjects::Get< SelectActions::Handler >( key );
945};
946
947// Menu definitions
948
949#define FN(X) (& SelectActions::Handler :: X)
950
951namespace {
952using namespace MenuRegistry;
954{
955 static auto menu = std::shared_ptr{
957 /* i18n-hint: (verb) It's an item on a menu. */
958 Menu( wxT("Select"), XXO("&Select"),
959 Section( "Basic",
960 Command( wxT("SelectAll"), XXO("&All"), FN(OnSelectAll),
962 Options{ wxT("Ctrl+A"), XO("Select All") } ),
963 Command( wxT("SelectNone"), XXO("&None"), FN(OnSelectNone),
965 Options{ wxT("Ctrl+Shift+A"), XO("Select None") } ),
966
968
969 Menu( wxT("Tracks"), XXO("&Tracks"),
970 Command( wxT("SelAllTracks"), XXO("In All &Tracks"),
971 FN(OnSelectAllTracks),
973 wxT("Ctrl+Shift+K") ),
974 Command( wxT("SelSyncLockTracks"), XXO("In All &Sync-Locked Tracks"),
975 FN(OnSelectSyncLockSel),
977 Options{ wxT("Ctrl+Shift+Y"), XO("Select Sync-Locked") } )
978 ),
979
981
982 Menu( wxT("Region"), XXO("R&egion"),
983 Section( "",
984 Command( wxT("SetLeftSelection"), XXO("&Left at Playback Position"),
985 FN(OnSetLeftSelection), TracksExistFlag(),
986 Options{ wxT("["), XO("Set Selection Left at Play Position") } ),
987 Command( wxT("SetRightSelection"), XXO("&Right at Playback Position"),
988 FN(OnSetRightSelection), TracksExistFlag(),
989 Options{ wxT("]"), XO("Set Selection Right at Play Position") } ),
990 Command( wxT("SelTrackStartToCursor"), XXO("Track &Start to Cursor"),
991 FN(OnSelectStartCursor), AlwaysEnabledFlag,
992 Options{ wxT("Shift+J"), XO("Select Track Start to Cursor") } ),
993 Command( wxT("SelCursorToTrackEnd"), XXO("Cursor to Track &End"),
994 FN(OnSelectCursorEnd), AlwaysEnabledFlag,
995 Options{ wxT("Shift+K"), XO("Select Cursor to Track End") } ),
996 Command( wxT("SelTrackStartToEnd"), XXO("Track Start to En&d"),
997 FN(OnSelectTrackStartToEnd), AlwaysEnabledFlag,
998 Options{}.LongName( XO("Select Track Start to End") ) )
999 ),
1000
1001 Section( "",
1002 // GA: Audacity had 'Store Re&gion' here previously. There is no
1003 // one-step way to restore the 'Saved Cursor Position' in Select Menu,
1004 // so arguably using the word 'Selection' to do duty for both saving
1005 // the region or the cursor is better. But it does not belong in a
1006 // 'Region' submenu.
1007 Command( wxT("SelSave"), XXO("S&tore Selection"), FN(OnSelectionSave),
1009 // Audacity had 'Retrieve Regio&n' here previously.
1010 Command( wxT("SelRestore"), XXO("Retrieve Selectio&n"),
1011 FN(OnSelectionRestore), TracksExistFlag() )
1012 )
1013 )
1014
1016
1017 ),
1018
1019 Section( "",
1020 Command( wxT("SelCursorStoredCursor"),
1021 XXO("Cursor to Stored &Cursor Position"),
1022 FN(OnSelectCursorStoredCursor), TracksExistFlag(),
1023 Options{}.LongName( XO("Select Cursor to Stored") ) ),
1024
1025 Command( wxT("StoreCursorPosition"), XXO("Store Cursor Pos&ition"),
1026 FN(OnCursorPositionStore),
1028 // Save cursor position is used in some selections.
1029 // Maybe there should be a restore for it?
1030 ),
1031
1032 Section( "",
1033 Command( wxT("ZeroCross"), XXO("At &Zero Crossings"),
1034 FN(OnZeroCrossing), EditableTracksSelectedFlag(),
1035 Options{ wxT("Z"), XO("Select Zero Crossing") } )
1036 )
1037 ) ) };
1038 return menu;
1039}
1040
1042
1044{
1045 static auto menu = std::shared_ptr{
1047 Menu( wxT("Select"), XXO("&Selection"),
1048 Command( wxT("SnapToOff"), XXO("Snap-To &Off"), FN(OnSnapToOff),
1050 Command( wxT("SnapToNearest"), XXO("Snap-To &Nearest"),
1051 FN(OnSnapToNearest), AlwaysEnabledFlag ),
1052 Command( wxT("SnapToPrior"), XXO("Snap-To &Prior"), FN(OnSnapToPrior),
1054 Command( wxT("SelStart"), XXO("Selection to &Start"), FN(OnSelToStart),
1055 AlwaysEnabledFlag, wxT("Shift+Home") ),
1056 Command( wxT("SelEnd"), XXO("Selection to En&d"), FN(OnSelToEnd),
1057 AlwaysEnabledFlag, wxT("Shift+End") ),
1058 Command( wxT("SelExtLeft"), XXO("Selection Extend &Left"),
1059 FN(OnSelExtendLeft),
1061 Options{ wxT("Shift+Left") }.WantKeyUp().AllowDup() ),
1062 Command( wxT("SelExtRight"), XXO("Selection Extend &Right"),
1063 FN(OnSelExtendRight),
1065 Options{ wxT("Shift+Right") }.WantKeyUp().AllowDup() ),
1066 Command( wxT("SelSetExtLeft"), XXO("Set (or Extend) Le&ft Selection"),
1067 FN(OnSelSetExtendLeft),
1069 Command( wxT("SelSetExtRight"), XXO("Set (or Extend) Rig&ht Selection"),
1070 FN(OnSelSetExtendRight),
1072 Command( wxT("SelCntrLeft"), XXO("Selection Contract L&eft"),
1073 FN(OnSelContractLeft),
1075 Options{ wxT("Ctrl+Shift+Right") }.WantKeyUp() ),
1076 Command( wxT("SelCntrRight"), XXO("Selection Contract R&ight"),
1077 FN(OnSelContractRight),
1079 Options{ wxT("Ctrl+Shift+Left") }.WantKeyUp() )
1080 ) ) };
1081 return menu;
1082}
1083
1085 wxT("Optional/Extra/Part1")
1086};
1087}
1088
1089namespace {
1091{
1092 static const auto CanStopFlags = AudioIONotBusyFlag() | CanStopAudioStreamFlag();
1093
1094 // JKC: ANSWER-ME: How is 'cursor to' different to 'Skip To' and how is it
1095 // useful?
1096 // GA: 'Skip to' moves the viewpoint to center of the track and preserves the
1097 // selection. 'Cursor to' does neither. 'Center at' might describe it better
1098 // than 'Skip'.
1099 static auto menu = std::shared_ptr{
1101 Menu( wxT("Cursor"), XXO("&Cursor to"),
1102 Command( wxT("CursSelStart"), XXO("Selection Star&t"),
1103 FN(OnCursorSelStart),
1105 Options{}.LongName( XO("Cursor to Selection Start") ) ),
1106 Command( wxT("CursSelEnd"), XXO("Selection En&d"),
1107 FN(OnCursorSelEnd),
1109 Options{}.LongName( XO("Cursor to Selection End") ) ),
1110
1111 Command( wxT("CursTrackStart"), XXO("Track &Start"),
1112 FN(OnCursorTrackStart),
1114 Options{ wxT("J"), XO("Cursor to Track Start") } ),
1115 Command( wxT("CursTrackEnd"), XXO("Track &End"),
1116 FN(OnCursorTrackEnd),
1118 Options{ wxT("K"), XO("Cursor to Track End") } ),
1119
1120 Command( wxT("CursProjectStart"), XXO("&Project Start"),
1121 FN(OnSkipStart),
1123 Options{ wxT("Home"), XO("Cursor to Project Start") } ),
1124 Command( wxT("CursProjectEnd"), XXO("Project E&nd"), FN(OnSkipEnd),
1126 Options{ wxT("End"), XO("Cursor to Project End") } )
1127 ) ) };
1128 return menu;
1129}
1130
1132 wxT("Transport/Basic")
1133};
1134
1136{
1137 static auto menu = std::shared_ptr{
1139 Menu( wxT("Cursor"), XXO("&Cursor"),
1140 Command( wxT("CursorLeft"), XXO("Cursor &Left"), FN(OnCursorLeft),
1142 Options{ wxT("Left") }.WantKeyUp().AllowDup() ),
1143 Command( wxT("CursorRight"), XXO("Cursor &Right"), FN(OnCursorRight),
1145 Options{ wxT("Right") }.WantKeyUp().AllowDup() ),
1146 Command( wxT("CursorShortJumpLeft"), XXO("Cursor Sh&ort Jump Left"),
1147 FN(OnCursorShortJumpLeft),
1149 Command( wxT("CursorShortJumpRight"), XXO("Cursor Shor&t Jump Right"),
1150 FN(OnCursorShortJumpRight),
1152 Command( wxT("CursorLongJumpLeft"), XXO("Cursor Long J&ump Left"),
1153 FN(OnCursorLongJumpLeft),
1154 TracksExistFlag() | TrackPanelHasFocus(), wxT("Shift+,") ),
1155 Command( wxT("CursorLongJumpRight"), XXO("Cursor Long Ju&mp Right"),
1156 FN(OnCursorLongJumpRight),
1157 TracksExistFlag() | TrackPanelHasFocus(), wxT("Shift+.") )
1158 ) ) };
1159 return menu;
1160}
1161
1163 wxT("Optional/Extra/Part2")
1164};
1165
1167{
1168 static auto menu = std::shared_ptr{
1170 Menu( wxT("Seek"), XXO("See&k"),
1171 Command( wxT("SeekLeftShort"), XXO("Short Seek &Left During Playback"),
1172 FN(OnSeekLeftShort), AudioIOBusyFlag(),
1173 Options{ wxT("Left") }.AllowDup() ),
1174 Command( wxT("SeekRightShort"),
1175 XXO("Short Seek &Right During Playback"), FN(OnSeekRightShort),
1177 Options{ wxT("Right") }.AllowDup() ),
1178 Command( wxT("SeekLeftLong"), XXO("Long Seek Le&ft During Playback"),
1179 FN(OnSeekLeftLong), AudioIOBusyFlag(),
1180 Options{ wxT("Shift+Left") }.AllowDup() ),
1181 Command( wxT("SeekRightLong"), XXO("Long Seek Rig&ht During Playback"),
1182 FN(OnSeekRightLong), AudioIOBusyFlag(),
1183 Options{ wxT("Shift+Right") }.AllowDup() )
1184 ) ) };
1185 return menu;
1186}
1187
1189 wxT("Optional/Extra/Part1")
1190};
1191
1192}
1193
1194#undef FN
wxT("CloseDown"))
Toolkit-neutral facade for basic user interface services.
AttachedItem sAttachment1
AttachedItem sAttachment2
constexpr CommandFlag AlwaysEnabledFlag
Definition: CommandFlag.h:34
wxEvtHandler CommandHandlerObject
const ReservedCommandFlag & AudioIOBusyFlag()
const ReservedCommandFlag & AudioIONotBusyFlag()
const ReservedCommandFlag & IsSyncLockedFlag()
const ReservedCommandFlag & TimeSelectedFlag()
const ReservedCommandFlag & EditableTracksSelectedFlag()
const ReservedCommandFlag & TracksExistFlag()
const ReservedCommandFlag & WaveTracksExistFlag()
const ReservedCommandFlag & WaveTracksSelectedFlag()
const ReservedCommandFlag & TrackPanelHasFocus()
int min(int a, int b)
XO("Cut/Copy/Paste")
XXO("&Cut/Copy/Paste Toolbar")
audacity::BasicSettings * gPrefs
Definition: Prefs.cpp:68
const ReservedCommandFlag & CanStopAudioStreamFlag()
an object holding per-project preferred sample rate
static const AudacityProject::AttachedObjects::RegisteredFactory key
#define FN(X)
static CommandHandlerObject & findCommandHandler(AudacityProject &project)
SnapMode
Definition: SnapUtils.h:21
const auto tracks
const auto project
static Settings & settings()
Definition: TrackInfo.cpp:69
static AdornedRulerPanel & Get(AudacityProject &project)
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:90
static AudioIO * Get()
Definition: AudioIO.cpp:126
double GetEndTime() const
Get the maximum of End() values of intervals, or 0 when none.
Definition: Channel.cpp:135
double GetStartTime() const
Get the minimum of Start() values of intervals, or 0 when none.
Definition: Channel.cpp:124
Client code makes static instance from a factory of attachments; passes it to Get or Find as a retrie...
Definition: ClientData.h:274
CommandContext provides additional information to an 'Apply()' command. It provides the project,...
const wxEvent * pEvt
AudacityProject & project
static ControlToolBar & Get(AudacityProject &project)
A LabelTrack is a Track that holds labels (LabelStruct).
Definition: LabelTrack.h:87
static LabelTrackView & Get(LabelTrack &)
A listener notified of changes in preferences.
Definition: Prefs.h:561
bool IsAudioActive() const
static ProjectAudioIO & Get(AudacityProject &project)
void ModifyState(bool bWantsAutoSave)
static ProjectHistory & Get(AudacityProject &project)
Holds project sample rate.
Definition: ProjectRate.h:24
static ProjectRate & Get(AudacityProject &project)
Definition: ProjectRate.cpp:28
double GetRate() const
Definition: ProjectRate.cpp:53
void SetSnapMode(SnapMode mode)
Definition: ProjectSnap.cpp:41
static ProjectSnap & Get(AudacityProject &project)
Definition: ProjectSnap.cpp:27
Generates classes whose instances register items at construction.
Definition: Registry.h:388
Defines a selected portion of a project.
double t1() const
double t0() const
static bool IsSyncLockSelected(const Track *pTrack)
Definition: SyncLock.cpp:82
bool IsSelected() const
Definition: Track.cpp:288
virtual bool SupportsBasicEditing() const
Whether this track type implements cut-copy-paste; by default, true.
Definition: Track.cpp:1213
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:347
static TrackPanel & Get(AudacityProject &project)
Definition: TrackPanel.cpp:233
NotifyingSelectedRegion selectedRegion
Definition: ViewInfo.h:215
static ViewInfo & Get(AudacityProject &project)
Definition: ViewInfo.cpp:235
static Viewport & Get(AudacityProject &project)
Definition: Viewport.cpp:32
static WaveChannelView & Get(WaveChannel &channel)
A Track that contains audio waveform data.
Definition: WaveTrack.h:222
WaveClipConstHolders GetClipsIntersecting(double t0, double t1) const
Definition: WaveTrack.cpp:3718
virtual bool Read(const wxString &key, bool *value) const =0
MessageBoxResult ShowMessageBox(const TranslatableString &message, MessageBoxOptions options={})
Show a modal message box with either Ok or Yes and No, and optionally Cancel.
Definition: BasicUI.h:277
constexpr auto Section
Definition: MenuRegistry.h:436
constexpr auto Command
Definition: MenuRegistry.h:456
constexpr auto Menu
Items will appear in a main toolbar menu or in a sub-menu.
Definition: MenuRegistry.h:445
auto end(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:159
std::unique_ptr< detail::IndirectItem< Item > > Indirect(const std::shared_ptr< Item > &ptr)
A convenience function.
Definition: Registry.h:175
void SelectNone(AudacityProject &project)
void DoSelectAll(AudacityProject &project)
void OnSetRegion(AudacityProject &project, bool left, bool selection, const TranslatableString &dialogTitle)
Adjust left or right of selection or play region.
void DoSelectTimeAndTracks(AudacityProject &project, bool bAllTime, bool bAllTracks)
void DoBoundaryMove(AudacityProject &project, int step, SeekInfo &info)
double NearestZeroCrossing(AudacityProject &project, double t0)
Definition: SelectMenus.cpp:35
bool OnlyHandleKeyUp(const CommandContext &context)
double OffsetTime(AudacityProject &project, double t, double offset, TimeUnit timeUnit, SnapMode snapMode)
void SeekWhenAudioActive(double seekStep, wxLongLong &lastSelectionAdjustment)
constexpr auto GetWindowSize(double projectRate)
Definition: SelectMenus.cpp:30
void MoveWhenAudioInactive(AudacityProject &project, double seekStep, TimeUnit timeUnit)
double GridMove(AudacityProject &project, double t, int minPix)
void SeekWhenAudioInactive(AudacityProject &project, double seekStep, TimeUnit timeUnit, SelectionOperation operation)
void SeekLeftOrRight(AudacityProject &project, double direction, SelectionOperation operation, SeekInfo &info)
void DoCursorMove(AudacityProject &project, double seekStep, wxLongLong &lastSelectionAdjustment)
double GetRate(const Track &track)
Definition: TimeTrack.cpp:196
MessageBoxOptions && Caption(TranslatableString caption_) &&
Definition: BasicUI.h:101
A convenient default parameter for class template Site.
Definition: ClientData.h:28
Options && LongName(const TranslatableString &value) &&
Definition: MenuRegistry.h:54
void OnSetLeftSelection(const CommandContext &context)
void OnCursorLeft(const CommandContext &context)
void OnSnapToOff(const CommandContext &context)
void OnCursorRight(const CommandContext &context)
void OnSelExtendRight(const CommandContext &context)
void OnSelectionSave(const CommandContext &context)
void OnSkipEnd(const CommandContext &context)
void OnSelContractRight(const CommandContext &context)
void OnSelContractLeft(const CommandContext &context)
void OnSelectAll(const CommandContext &context)
SelectedRegion mRegionSave
void OnCursorLongJumpRight(const CommandContext &context)
void UpdatePrefs() override
void OnSeekRightShort(const CommandContext &context)
Handler & operator=(const Handler &)=delete
Handler(const Handler &)=delete
void OnSnapToNearest(const CommandContext &context)
void OnSelToStart(const CommandContext &context)
void OnCursorLongJumpLeft(const CommandContext &context)
void OnSeekLeftShort(const CommandContext &context)
void OnSelectAllTime(const CommandContext &context)
void OnSnapToPrior(const CommandContext &context)
void OnSelectTrackStartToEnd(const CommandContext &context)
void OnCursorTrackEnd(const CommandContext &context)
void OnZeroCrossing(const CommandContext &context)
void OnCursorShortJumpRight(const CommandContext &context)
void OnCursorShortJumpLeft(const CommandContext &context)
void OnCursorTrackStart(const CommandContext &context)
void OnSelectNone(const CommandContext &context)
void OnSelSetExtendLeft(const CommandContext &context)
void OnSelectCursorEnd(const CommandContext &context)
void OnSelectSyncLockSel(const CommandContext &context)
void OnSelectCursorStoredCursor(const CommandContext &context)
void OnSelToEnd(const CommandContext &context)
void OnSelSetExtendRight(const CommandContext &context)
void OnCursorSelStart(const CommandContext &context)
void OnSkipStart(const CommandContext &context)
void OnSelectStartCursor(const CommandContext &context)
void OnSelectionRestore(const CommandContext &context)
void OnSeekRightLong(const CommandContext &context)
void OnSelExtendLeft(const CommandContext &context)
void OnSelectAllTracks(const CommandContext &context)
void OnSetRightSelection(const CommandContext &context)
void OnSeekLeftLong(const CommandContext &context)
void OnCursorPositionStore(const CommandContext &context)
void OnCursorSelEnd(const CommandContext &context)