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 "WaveTrackUtilities.h"
20#include "../LabelTrack.h"
21#include "CommandContext.h"
22#include "MenuRegistry.h"
23#include "../toolbars/ControlToolBar.h"
24#include "../tracks/ui/SelectHandle.h"
25#include "../tracks/labeltrack/ui/LabelTrackView.h"
26#include "../tracks/playabletrack/wavetrack/ui/WaveChannelView.h"
27
28// private helper classes and functions
29namespace {
30
31constexpr auto GetWindowSize(double projectRate)
32{
33 return size_t(std::max(1.0, projectRate / 100));
34}
35
37{
38 auto rate = ProjectRate::Get(project).GetRate();
39 auto &tracks = TrackList::Get( project );
40
41 // Window is 1/100th of a second.
42 auto windowSize = GetWindowSize(rate);
43 Floats dist{ windowSize, true };
44
45 int nTracks = 0;
46 for (auto one : tracks.Selected<const WaveTrack>()) {
47 const auto nChannels = one->NChannels();
48 auto oneWindowSize = size_t(std::max(1.0, one->GetRate() / 100));
49 Floats buffer1{ oneWindowSize };
50 Floats buffer2{ oneWindowSize };
51 float *const buffers[]{ buffer1.get(), buffer2.get() };
52 auto s = one->TimeToLongSamples(t0);
53
54 // fillTwo to ensure that missing values are treated as 2, and hence do
55 // not get used as zero crossings.
56 one->GetFloats(0, nChannels, buffers,
57 s - (int)oneWindowSize/2, oneWindowSize, false, FillFormat::fillTwo);
58
59
60 // Looking for actual crossings. Update dist
61 for (size_t iChannel = 0; iChannel < nChannels; ++iChannel) {
62 const auto oneDist = buffers[iChannel];
63 double prev = 2.0;
64 for (size_t i = 0; i < oneWindowSize; ++i) {
65 float fDist = fabs(oneDist[i]); // score is absolute value
66 if (prev * oneDist[i] > 0) // both same sign? No good.
67 fDist = fDist + 0.4; // No good if same sign.
68 else if (prev > 0.0)
69 fDist = fDist + 0.1; // medium penalty for downward crossing.
70 prev = oneDist[i];
71 oneDist[i] = fDist;
72 }
73
74 // TODO: The mixed rate zero crossing code is broken,
75 // if oneWindowSize > windowSize we'll miss out some
76 // samples - so they will still be zero, so we'll use them.
77 for (size_t i = 0; i < windowSize; i++) {
78 size_t j;
79 if (windowSize != oneWindowSize)
80 j = i * (oneWindowSize - 1) / (windowSize - 1);
81 else
82 j = i;
83
84 dist[i] += oneDist[j];
85 // Apply a small penalty for distance from the original endpoint
86 // We'll always prefer an upward
87 dist[i] +=
88 0.1 * (abs(int(i) - int(windowSize / 2))) / float(windowSize / 2);
89 }
90 }
91 nTracks++;
92 }
93
94 // Find minimum
95 int argmin = 0;
96 float min = 3.0;
97 for (size_t i = 0; i < windowSize; ++i) {
98 if (dist[i] < min) {
99 argmin = i;
100 min = dist[i];
101 }
102 }
103
104 // If we're worse than 0.2 on average, on one track, then no good.
105 if ((nTracks == 1) && (min > (0.2 * nTracks)))
106 return t0;
107 // If we're worse than 0.6 on average, on multi-track, then no good.
108 if ((nTracks > 1) && (min > (0.6 * nTracks)))
109 return t0;
110
111 return t0 + (argmin - (int)windowSize / 2) / rate;
112}
113
114// If this returns true, then there was a key up, and nothing more to do,
115// after this function has completed.
116// (at most this function just does a ModifyState for the keyup)
117bool OnlyHandleKeyUp( const CommandContext &context )
118{
119 auto &project = context.project;
120 auto evt = context.pEvt;
121 bool bKeyUp = (evt) && evt->GetEventType() == wxEVT_KEY_UP;
122
124 return bKeyUp;
125 if( !bKeyUp )
126 return false;
127
129 return true;
130}
131
134 DIRECTION_RIGHT = +1
136
142
147
149{
150 wxLongLong mLastSelectionAdjustment { ::wxGetUTCTimeMillis() };
151 double mSeekShort{ 0.0 };
152 double mSeekLong{ 0.0 };
153};
154
155void SeekWhenAudioActive(double seekStep, wxLongLong &lastSelectionAdjustment)
156{
157 auto gAudioIO = AudioIO::Get();
158#ifdef EXPERIMENTAL_IMPROVED_SEEKING
159 if (gAudioIO->GetLastPlaybackTime() < lastSelectionAdjustment) {
160 // Allow time for the last seek to output a buffer before
161 // discarding samples again
162 // Do not advance mLastSelectionAdjustment
163 return;
164 }
165#endif
166 lastSelectionAdjustment = ::wxGetUTCTimeMillis();
167
168 gAudioIO->SeekStream(seekStep);
169}
170
171// Handles moving a selection edge with the keyboard in snap-to-time mode;
172// returns the moved value.
173// Will move at least minPix pixels -- set minPix positive to move forward,
174// negative to move backward.
175// Helper for moving by keyboard with snap-to-grid enabled
177(AudacityProject &project, double t, int minPix)
178{
179 auto& projectSnap = ProjectSnap::Get(project);
180 auto &viewInfo = ViewInfo::Get( project );
181
182 auto result = projectSnap.SingleStep(t, minPix >= 0).time;
183
184 if (
185 std::abs(viewInfo.TimeToPosition(result) - viewInfo.TimeToPosition(t)) >=
186 abs(minPix))
187 return result;
188
189 // Otherwise, move minPix pixels, then snap to the time.
190 result = viewInfo.OffsetTimeByPixels(t, minPix);
191 return projectSnap.SnapTime(result).time;
192}
193
196 double t, double offset, TimeUnit timeUnit, SnapMode snapMode)
197{
198 auto &viewInfo = ViewInfo::Get( project );
199
200 if (timeUnit == TIME_UNIT_SECONDS)
201 return t + offset; // snapping is currently ignored for non-pixel moves
202
203 if (snapMode == SnapMode::SNAP_OFF)
204 return viewInfo.OffsetTimeByPixels(t, (int)offset);
205
206 return GridMove(project, t, (int)offset);
207}
208
209// Moving a cursor, and collapsed selection.
211(AudacityProject &project, double seekStep, TimeUnit timeUnit)
212{
213 auto &viewInfo = ViewInfo::Get(project);
214 auto &trackPanel = TrackPanel::Get(project);
217 const auto &settings = ProjectSnap::Get(project);
218 auto &viewport = Viewport::Get(project);
219
220 // If TIME_UNIT_SECONDS, snap-to will be off.
221 auto snapMode = settings.GetSnapMode();
222
223 // Move the cursor
224 // Already in cursor mode?
225 if (viewInfo.selectedRegion.isPoint())
226 {
227 double newT = OffsetTime(
228 project, viewInfo.selectedRegion.t0(), seekStep, timeUnit, snapMode);
229 // constrain.
230 newT = std::max(0.0, newT);
231 // Move
232 viewInfo.selectedRegion.setT0(
233 newT,
234 false); // do not swap selection boundaries
235 viewInfo.selectedRegion.collapseToT0();
236
237 // Move the visual cursor, avoiding an unnecessary complete redraw
238 trackPanel.DrawOverlays(false);
239 ruler.DrawOverlays(false);
240 }
241 else
242 {
243 // Transition to cursor mode.
244 constexpr auto maySwapBoundaries = false;
245 if (seekStep < 0)
246 {
247 if (snapMode != SnapMode::SNAP_OFF)
248 viewInfo.selectedRegion.setT0(
249 settings.SnapTime(viewInfo.selectedRegion.t0()).time,
250 maySwapBoundaries);
251 viewInfo.selectedRegion.collapseToT0();
252 }
253 else
254 {
255 if (snapMode != SnapMode::SNAP_OFF)
256 viewInfo.selectedRegion.setT1(
257 settings.SnapTime(viewInfo.selectedRegion.t1()).time,
258 maySwapBoundaries);
259 viewInfo.selectedRegion.collapseToT1();
260 }
261 trackPanel.Refresh(false);
262 }
263
264 // Make sure NEW position is in view
265 viewport.ScrollIntoView(viewInfo.selectedRegion.t1());
266 return;
267}
268
270(AudacityProject &project, double seekStep, TimeUnit timeUnit,
271SelectionOperation operation)
272{
273 auto &viewInfo = ViewInfo::Get(project);
275 const auto &settings = ProjectSnap::Get(project);
276 auto &viewport = Viewport::Get(project);
277
278 if (operation == CURSOR_MOVE)
279 {
280 MoveWhenAudioInactive( project, seekStep, timeUnit);
281 return;
282 }
283
284 auto snapMode = settings.GetSnapMode();
285 const double t0 = viewInfo.selectedRegion.t0();
286 const double t1 = viewInfo.selectedRegion.t1();
287 const double end = std::max(
288 tracks.GetEndTime(), viewInfo.GetScreenEndTime());
289
290 // Is it t0 or t1 moving?
291 bool bMoveT0 = (operation == SELECTION_CONTRACT && seekStep > 0) ||
292 (operation == SELECTION_EXTEND && seekStep < 0);
293 // newT is where we want to move to
294 double newT = OffsetTime( project,
295 bMoveT0 ? t0 : t1, seekStep, timeUnit, snapMode);
296 // constrain to be in the track/screen limits.
297 newT = std::max( 0.0, newT );
298 newT = std::min( newT, end);
299 // optionally constrain to be a contraction, i.e. so t0/t1 do not cross over
300 if( operation == SELECTION_CONTRACT )
301 newT = bMoveT0 ? std::min( t1, newT ) : std::max( t0, newT );
302
303 // Actually move
304 if( bMoveT0 )
305 viewInfo.selectedRegion.setT0( newT );
306 else
307 viewInfo.selectedRegion.setT1( newT );
308
309 // Ensure it is visible
310 viewport.ScrollIntoView(newT);
311}
312
313// Handle small cursor and play head movements
315(AudacityProject &project, double direction, SelectionOperation operation,
316 SeekInfo &info)
317{
318 // PRL: What I found and preserved, strange though it be:
319 // During playback: jump depends on preferences and is independent of the
320 // zoom and does not vary if the key is held
321 // Else: jump depends on the zoom and gets bigger if the key is held
322
323 if( ProjectAudioIO::Get( project ).IsAudioActive() )
324 {
325 if( operation == CURSOR_MOVE )
326 SeekWhenAudioActive( info.mSeekShort * direction,
328 else if( operation == SELECTION_EXTEND )
329 SeekWhenAudioActive( info.mSeekLong * direction,
331 // Note: no action for CURSOR_CONTRACT
332 return;
333 }
334
335 // If the last adjustment was very recent, we are
336 // holding the key down and should move faster.
337 const wxLongLong curtime = ::wxGetUTCTimeMillis();
338 enum { MIN_INTERVAL = 50 };
339 const bool fast =
340 (curtime - info.mLastSelectionAdjustment < MIN_INTERVAL);
341
342 info.mLastSelectionAdjustment = curtime;
343
344 // How much faster should the cursor move if shift is down?
345 enum { LARGER_MULTIPLIER = 4 };
346 const double seekStep = (fast ? LARGER_MULTIPLIER : 1.0) * direction;
347
348 SeekWhenAudioInactive( project, seekStep, TIME_UNIT_PIXELS, operation);
349}
350
351// Move the cursor forward or backward, while paused or while playing.
353 AudacityProject &project, double seekStep,
354 wxLongLong &lastSelectionAdjustment)
355{
356 if (ProjectAudioIO::Get( project ).IsAudioActive()) {
357 SeekWhenAudioActive(seekStep, lastSelectionAdjustment);
358 }
359 else
360 {
361 lastSelectionAdjustment = ::wxGetUTCTimeMillis();
363 }
364
366}
367
369{
370 auto &viewInfo = ViewInfo::Get(project);
372 auto &viewport = Viewport::Get(project);
373
374 // step is negative, then is moving left. step positive, moving right.
375 // Move the left/right selection boundary, to expand the selection
376
377 // If the last adjustment was very recent, we are
378 // holding the key down and should move faster.
379 wxLongLong curtime = ::wxGetUTCTimeMillis();
380 int pixels = step;
381 if (curtime - info.mLastSelectionAdjustment < 50)
382 pixels *= 4;
383 info.mLastSelectionAdjustment = curtime;
384
385 // we used to have a parameter boundaryContract to say if expanding or
386 // contracting. it is no longer needed.
387 bool bMoveT0 = (step < 0 );// ^ boundaryContract ;
388
390 auto gAudioIO = AudioIO::Get();
391 double indicator = gAudioIO->GetStreamTime();
392 if( bMoveT0 )
393 viewInfo.selectedRegion.setT0(indicator, false);
394 else
395 viewInfo.selectedRegion.setT1(indicator);
396
398 return;
399 }
400
401 const double t0 = viewInfo.selectedRegion.t0();
402 const double t1 = viewInfo.selectedRegion.t1();
403 const double end = std::max(
404 tracks.GetEndTime(), viewInfo.GetScreenEndTime());
405
406 double newT = viewInfo.OffsetTimeByPixels( bMoveT0 ? t0 : t1, pixels);
407 // constrain to be in the track/screen limits.
408 newT = std::max( 0.0, newT );
409 newT = std::min( newT, end);
410 // optionally constrain to be a contraction, i.e. so t0/t1 do not cross over
411 //if( boundaryContract )
412 // newT = bMoveT0 ? std::min( t1, newT ) : std::max( t0, newT );
413
414 // Actually move
415 if( bMoveT0 )
416 viewInfo.selectedRegion.setT0( newT );
417 else
418 viewInfo.selectedRegion.setT1( newT );
419
420 // Ensure it is visible
421 viewport.ScrollIntoView(newT);
422
424}
425
426}
427
428namespace SelectActions {
429
430
431// Menu handler functions
432
434 : CommandHandlerObject // MUST be the first base class!
437{
438
439void OnSelectAll(const CommandContext &context)
440{
441 auto& trackPanel = TrackPanel::Get(context.project);
442 auto& tracks = TrackList::Get(context.project);
443
444 for (auto lt : tracks.Selected<LabelTrack>()) {
445 auto& view = LabelTrackView::Get(*lt);
446 if (view.SelectAllText(context.project)) {
447 trackPanel.Refresh(false);
448 return;
449 }
450 }
451
452 //Presumably, there might be not more than one track
453 //that expects text input
454 for (auto wt : tracks.Any<WaveTrack>()) {
455 auto& view = WaveChannelView::GetFirst(*wt);
456 if (view.SelectAllText(context.project)) {
457 trackPanel.Refresh(false);
458 return;
459 }
460 }
461
463}
464
465void OnSelectNone(const CommandContext &context)
466{
467 auto &project = context.project;
468 auto &selectedRegion = ViewInfo::Get( project ).selectedRegion;
469
470 selectedRegion.collapseToT0();
473}
474
476{
477 auto &project = context.project;
479}
480
482{
483 auto &project = context.project;
484 auto &tracks = TrackList::Get( project );
485
486 bool selected = false;
487 for (auto t : tracks.Any() + &Track::SupportsBasicEditing
489 t->SetSelected(true);
490 selected = true;
491 }
492
493 if (selected)
495}
496
498{
500 true, true, XO("Set Left Selection Boundary"));
501}
502
504{
506 false, true, XO("Set Right Selection Boundary"));
507}
508
510{
511 auto &project = context.project;
513 auto &selectedRegion = ViewInfo::Get(project).selectedRegion;
514
515 double kWayOverToRight = std::numeric_limits<double>::max();
516
517 auto range = tracks.Selected();
518 if (!range)
519 return;
520
521 double minOffset = range.min(&Track::GetStartTime);
522
523 if( minOffset >=
524 (kWayOverToRight * (1 - std::numeric_limits<double>::epsilon()) ))
525 return;
526
527 selectedRegion.setT0(minOffset);
529}
530
532{
533 auto &project = context.project;
535 auto &selectedRegion = ViewInfo::Get(project).selectedRegion;
536
537 double kWayOverToLeft = std::numeric_limits<double>::lowest();
538
539 auto range = tracks.Selected();
540 if (!range)
541 return;
542
543 double maxEndOffset = range.max(&Track::GetEndTime);
544
545 if( maxEndOffset <=
546 (kWayOverToLeft * (1 - std::numeric_limits<double>::epsilon()) ))
547 return;
548
549 selectedRegion.setT1(maxEndOffset);
551}
552
554{
555 auto &project = context.project;
556 auto &viewInfo = ViewInfo::Get(project);
558
559 auto range = tracks.Selected();
560 double maxEndOffset = range.max(&Track::GetEndTime);
561 double minOffset = range.min(&Track::GetStartTime);
562
563 if( maxEndOffset < minOffset)
564 return;
565
566 viewInfo.selectedRegion.setTimes(minOffset, maxEndOffset);
568}
569
570// Handler state:
572
573void OnSelectionSave(const CommandContext &context)
574{
575 auto &project = context.project;
576 auto &selectedRegion = ViewInfo::Get( project ).selectedRegion;
577
578 mRegionSave = selectedRegion;
579}
580
582{
583 auto &project = context.project;
584 auto &selectedRegion = ViewInfo::Get( project ).selectedRegion;
585 auto &viewport = Viewport::Get(project);
586
587 if ((mRegionSave.t0() == 0.0) &&
588 (mRegionSave.t1() == 0.0))
589 return;
590
591 selectedRegion = mRegionSave;
592 viewport.ScrollIntoView(selectedRegion.t0());
593
595}
596
597// Handler state:
600
602{
603 auto &project = context.project;
604 auto &selectedRegion = ViewInfo::Get( project ).selectedRegion;
605 auto isAudioActive = ProjectAudioIO::Get( project ).IsAudioActive();
606
608 auto gAudioIO = AudioIO::Get();
609 double cursorPositionCurrent = isAudioActive
610 ? gAudioIO->GetStreamTime()
611 : selectedRegion.t0();
612 selectedRegion.setTimes(
613 std::min(cursorPositionCurrent, mCursorPositionStored),
614 std::max(cursorPositionCurrent, mCursorPositionStored));
615
617 }
618}
619
621{
622 auto &project = context.project;
623 auto &selectedRegion = ViewInfo::Get( project ).selectedRegion;
624 auto isAudioActive = ProjectAudioIO::Get( project ).IsAudioActive();
625
626 auto gAudioIO = AudioIO::Get();
628 isAudioActive ? gAudioIO->GetStreamTime() : selectedRegion.t0();
630}
631
632void OnZeroCrossing(const CommandContext &context)
633{
634 auto &project = context.project;
635 auto &selectedRegion = ViewInfo::Get( project ).selectedRegion;
636 const auto& tracks = TrackList::Get(project);
637
638 // Selecting precise sample indices across tracks that may have clips with
639 // various stretch ratios in itself is not possible. Even in single-track
640 // mode, we cannot know what the final waveform will look like until
641 // stretching is applied, making this operation futile. Hence we disallow
642 // it if any stretched clip is involved.
643 const auto projectRate = ProjectRate(project).GetRate();
644 const auto searchWindowDuration = GetWindowSize(projectRate) / projectRate;
645 const auto wouldSearchClipWithPitchOrSpeed =
646 [searchWindowDuration](const WaveTrack& track, double t) {
647 const auto clips = WaveTrackUtilities::GetClipsIntersecting(track,
648 t - searchWindowDuration / 2, t + searchWindowDuration / 2);
649 return any_of(
650 clips.begin(), clips.end(),
651 [](const auto& clip) { return clip->HasPitchOrSpeed(); });
652 };
653 const auto selected = tracks.Selected<const WaveTrack>();
654 if (std::any_of(
655 selected.begin(), selected.end(), [&](const WaveTrack* track) {
656 return wouldSearchClipWithPitchOrSpeed(
657 *track, selectedRegion.t0()) ||
658 wouldSearchClipWithPitchOrSpeed(
659 *track, selectedRegion.t1());
660 }))
661 {
662 using namespace BasicUI;
664 XO("Zero-crossing search regions intersect stretched clip(s)."),
665 MessageBoxOptions {}.Caption(XO("Error")).IconStyle(Icon::Error));
666 return;
667 }
668
669 const double t0 = NearestZeroCrossing(project, selectedRegion.t0());
670 if (selectedRegion.isPoint())
671 selectedRegion.setTimes(t0, t0);
672 else {
673 const double t1 = NearestZeroCrossing(project, selectedRegion.t1());
674 // Empty selection is generally not much use, so do not make it if empty.
675 if( fabs( t1 - t0 ) * ProjectRate::Get(project).GetRate() > 1.5 )
676 selectedRegion.setTimes(t0, t1);
677 }
678
680}
681
682void OnSnapToOff(const CommandContext &context)
683{
684 auto &project = context.project;
686}
687
688void OnSnapToNearest(const CommandContext &context)
689{
690 auto &project = context.project;
692}
693
694void OnSnapToPrior(const CommandContext &context)
695{
696 auto &project = context.project;
698}
699
700void OnSelToStart(const CommandContext &context)
701{
702 auto &project = context.project;
703 auto &viewport = Viewport::Get(project);
704 viewport.ScrollToStart(true);
706}
707
708void OnSelToEnd(const CommandContext &context)
709{
710 auto &project = context.project;
711 auto &viewport = Viewport::Get(project);
712 viewport.ScrollToEnd(true);
714}
715
716// Handler state:
717SeekInfo mSeekInfo;
718
719void OnSelExtendLeft(const CommandContext &context)
720{
721 if( !OnlyHandleKeyUp( context ) )
723 mSeekInfo );
724}
725
727{
728 if( !OnlyHandleKeyUp( context ) )
730 mSeekInfo );
731}
732
734{
736}
737
739{
741}
742
744{
745 if( !OnlyHandleKeyUp( context ) )
747 mSeekInfo );
748}
749
751{
752 if( !OnlyHandleKeyUp( context ) )
754 mSeekInfo );
755}
756
758{
759 auto &project = context.project;
760 auto &selectedRegion = ViewInfo::Get( project ).selectedRegion;
761 auto &viewport = Viewport::Get(project);
762
763 selectedRegion.collapseToT0();
765 viewport.ScrollIntoView(selectedRegion.t0());
766}
767
768void OnCursorSelEnd(const CommandContext &context)
769{
770 auto &project = context.project;
771 auto &selectedRegion = ViewInfo::Get( project ).selectedRegion;
772 auto &viewport = Viewport::Get(project);
773
774 selectedRegion.collapseToT1();
776 viewport.ScrollIntoView(selectedRegion.t1());
777}
778
780{
781 auto &project = context.project;
783 auto &selectedRegion = ViewInfo::Get(project).selectedRegion;
784 auto &viewport = Viewport::Get(project);
785
786 double kWayOverToRight = std::numeric_limits<double>::max();
787
788 auto trackRange = tracks.Selected() + &Track::SupportsBasicEditing;
789 if (trackRange.empty())
790 // This should have been prevented by command manager
791 return;
792
793 // Range is surely nonempty now
794 auto minOffset = std::max(0.0, trackRange.min(&Track::GetStartTime));
795
796 if( minOffset >=
797 (kWayOverToRight * (1 - std::numeric_limits<double>::epsilon()) ))
798 return;
799
800 selectedRegion.setTimes(minOffset, minOffset);
802 viewport.ScrollIntoView(selectedRegion.t0());
803}
804
806{
807 auto &project = context.project;
809 auto &selectedRegion = ViewInfo::Get(project).selectedRegion;
810 auto &viewport = Viewport::Get(project);
811
812 double kWayOverToLeft = std::numeric_limits<double>::lowest();
813
814 auto trackRange = tracks.Selected() + &Track::SupportsBasicEditing;
815 if (trackRange.empty())
816 // This should have been prevented by command manager
817 return;
818
819 // Range is surely nonempty now
820 auto maxEndOffset = trackRange.max(&Track::GetEndTime);
821
822 if( maxEndOffset <
823 (kWayOverToLeft * (1 - std::numeric_limits<double>::epsilon()) ))
824 return;
825
826 selectedRegion.setTimes(maxEndOffset, maxEndOffset);
828 viewport.ScrollIntoView(selectedRegion.t1());
829}
830
831void OnSkipStart(const CommandContext &context)
832{
833 auto &project = context.project;
834 wxCommandEvent evt;
835
836 auto &controlToolBar = ControlToolBar::Get( project );
837 controlToolBar.OnRewind(evt);
839}
840
841void OnSkipEnd(const CommandContext &context)
842{
843 auto &project = context.project;
844 wxCommandEvent evt;
845
846 auto &controlToolBar = ControlToolBar::Get( project );
847 controlToolBar.OnFF(evt);
849}
850
851void OnCursorLeft(const CommandContext &context)
852{
853 if( !OnlyHandleKeyUp( context ) )
855 mSeekInfo );
856}
857
858void OnCursorRight(const CommandContext &context)
859{
860 if( !OnlyHandleKeyUp( context ) )
862 mSeekInfo );
863}
864
866{
867 DoCursorMove( context.project,
868 -mSeekInfo.mSeekShort, mSeekInfo.mLastSelectionAdjustment );
869}
870
872{
873 DoCursorMove( context.project,
874 mSeekInfo.mSeekShort, mSeekInfo.mLastSelectionAdjustment );
875}
876
878{
879 DoCursorMove( context.project,
880 -mSeekInfo.mSeekLong, mSeekInfo.mLastSelectionAdjustment );
881}
882
884{
885 DoCursorMove( context.project,
886 mSeekInfo.mSeekLong, mSeekInfo.mLastSelectionAdjustment );
887}
888
889void OnSeekLeftShort(const CommandContext &context)
890{
891 auto &project = context.project;
893}
894
896{
897 auto &project = context.project;
899}
900
901void OnSeekLeftLong(const CommandContext &context)
902{
903 auto &project = context.project;
905}
906
907void OnSeekRightLong(const CommandContext &context)
908{
909 auto &project = context.project;
911}
912
913#if 1
914// Legacy functions, not used as of version 2.3.0
915void OnSelectAllTime(const CommandContext &context)
916{
917 auto &project = context.project;
919}
920#endif
921
922void UpdatePrefs() override
923{
924 gPrefs->Read(wxT("/AudioIO/SeekShortPeriod"), &mSeekInfo.mSeekShort, 1.0);
925 gPrefs->Read(wxT("/AudioIO/SeekLongPeriod"), &mSeekInfo.mSeekLong, 15.0);
926}
928{
929 UpdatePrefs();
930}
931Handler( const Handler & ) = delete;
932Handler &operator=( const Handler & ) = delete;
933
934}; // struct Handler
935
936} // namespace
937
938// Handler is stateful. Needs a factory registered with
939// AudacityProject.
941 [](AudacityProject&) {
942 return std::make_unique< SelectActions::Handler >(); } };
943
945 return project.AttachedObjects::Get< SelectActions::Handler >( key );
946};
947
948// Menu definitions
949
950#define FN(X) (& SelectActions::Handler :: X)
951
952namespace {
953using namespace MenuRegistry;
955{
956 static auto menu = std::shared_ptr{
958 /* i18n-hint: (verb) It's an item on a menu. */
959 Menu( wxT("Select"), XXO("&Select"),
960 Section( "Basic",
961 Command( wxT("SelectAll"), XXO("&All"), FN(OnSelectAll),
963 Options{ wxT("Ctrl+A"), XO("Select All") } ),
964 Command( wxT("SelectNone"), XXO("&None"), FN(OnSelectNone),
966 Options{ wxT("Ctrl+Shift+A"), XO("Select None") } ),
967
969
970 Menu( wxT("Tracks"), XXO("&Tracks"),
971 Command( wxT("SelAllTracks"), XXO("In All &Tracks"),
972 FN(OnSelectAllTracks),
974 wxT("Ctrl+Shift+K") ),
975 Command( wxT("SelSyncLockTracks"), XXO("In All &Sync-Locked Tracks"),
976 FN(OnSelectSyncLockSel),
978 Options{ wxT("Ctrl+Shift+Y"), XO("Select Sync-Locked") } )
979 ),
980
982
983 Menu( wxT("Region"), XXO("R&egion"),
984 Section( "",
985 Command( wxT("SetLeftSelection"), XXO("&Left at Playback Position"),
986 FN(OnSetLeftSelection), TracksExistFlag(),
987 Options{ wxT("["), XO("Set Selection Left at Play Position") } ),
988 Command( wxT("SetRightSelection"), XXO("&Right at Playback Position"),
989 FN(OnSetRightSelection), TracksExistFlag(),
990 Options{ wxT("]"), XO("Set Selection Right at Play Position") } ),
991 Command( wxT("SelTrackStartToCursor"), XXO("Track &Start to Cursor"),
992 FN(OnSelectStartCursor), AlwaysEnabledFlag,
993 Options{ wxT("Shift+J"), XO("Select Track Start to Cursor") } ),
994 Command( wxT("SelCursorToTrackEnd"), XXO("Cursor to Track &End"),
995 FN(OnSelectCursorEnd), AlwaysEnabledFlag,
996 Options{ wxT("Shift+K"), XO("Select Cursor to Track End") } ),
997 Command( wxT("SelTrackStartToEnd"), XXO("Track Start to En&d"),
998 FN(OnSelectTrackStartToEnd), AlwaysEnabledFlag,
999 Options{}.LongName( XO("Select Track Start to End") ) )
1000 ),
1001
1002 Section( "",
1003 // GA: Audacity had 'Store Re&gion' here previously. There is no
1004 // one-step way to restore the 'Saved Cursor Position' in Select Menu,
1005 // so arguably using the word 'Selection' to do duty for both saving
1006 // the region or the cursor is better. But it does not belong in a
1007 // 'Region' submenu.
1008 Command( wxT("SelSave"), XXO("S&tore Selection"), FN(OnSelectionSave),
1010 // Audacity had 'Retrieve Regio&n' here previously.
1011 Command( wxT("SelRestore"), XXO("Retrieve Selectio&n"),
1012 FN(OnSelectionRestore), TracksExistFlag() )
1013 )
1014 )
1015
1017
1018 ),
1019
1020 Section( "",
1021 Command( wxT("SelCursorStoredCursor"),
1022 XXO("Cursor to Stored &Cursor Position"),
1023 FN(OnSelectCursorStoredCursor), TracksExistFlag(),
1024 Options{}.LongName( XO("Select Cursor to Stored") ) ),
1025
1026 Command( wxT("StoreCursorPosition"), XXO("Store Cursor Pos&ition"),
1027 FN(OnCursorPositionStore),
1029 // Save cursor position is used in some selections.
1030 // Maybe there should be a restore for it?
1031 ),
1032
1033 Section( "",
1034 Command( wxT("ZeroCross"), XXO("At &Zero Crossings"),
1035 FN(OnZeroCrossing), EditableTracksSelectedFlag(),
1036 Options{ wxT("Z"), XO("Select Zero Crossing") } )
1037 )
1038 ) ) };
1039 return menu;
1040}
1041
1043
1045{
1046 static auto menu = std::shared_ptr{
1048 Menu( wxT("Select"), XXO("&Selection"),
1049 Command( wxT("SnapToOff"), XXO("Snap-To &Off"), FN(OnSnapToOff),
1051 Command( wxT("SnapToNearest"), XXO("Snap-To &Nearest"),
1052 FN(OnSnapToNearest), AlwaysEnabledFlag ),
1053 Command( wxT("SnapToPrior"), XXO("Snap-To &Prior"), FN(OnSnapToPrior),
1055 Command( wxT("SelStart"), XXO("Selection to &Start"), FN(OnSelToStart),
1056 AlwaysEnabledFlag, wxT("Shift+Home") ),
1057 Command( wxT("SelEnd"), XXO("Selection to En&d"), FN(OnSelToEnd),
1058 AlwaysEnabledFlag, wxT("Shift+End") ),
1059 Command( wxT("SelExtLeft"), XXO("Selection Extend &Left"),
1060 FN(OnSelExtendLeft),
1062 Options{ wxT("Shift+Left") }.WantKeyUp().AllowDup() ),
1063 Command( wxT("SelExtRight"), XXO("Selection Extend &Right"),
1064 FN(OnSelExtendRight),
1066 Options{ wxT("Shift+Right") }.WantKeyUp().AllowDup() ),
1067 Command( wxT("SelSetExtLeft"), XXO("Set (or Extend) Le&ft Selection"),
1068 FN(OnSelSetExtendLeft),
1070 Command( wxT("SelSetExtRight"), XXO("Set (or Extend) Rig&ht Selection"),
1071 FN(OnSelSetExtendRight),
1073 Command( wxT("SelCntrLeft"), XXO("Selection Contract L&eft"),
1074 FN(OnSelContractLeft),
1076 Options{ wxT("Ctrl+Shift+Right") }.WantKeyUp() ),
1077 Command( wxT("SelCntrRight"), XXO("Selection Contract R&ight"),
1078 FN(OnSelContractRight),
1080 Options{ wxT("Ctrl+Shift+Left") }.WantKeyUp() )
1081 ) ) };
1082 return menu;
1083}
1084
1086 wxT("Optional/Extra/Part1")
1087};
1088}
1089
1090namespace {
1092{
1093 static const auto CanStopFlags = AudioIONotBusyFlag() | CanStopAudioStreamFlag();
1094
1095 // JKC: ANSWER-ME: How is 'cursor to' different to 'Skip To' and how is it
1096 // useful?
1097 // GA: 'Skip to' moves the viewpoint to center of the track and preserves the
1098 // selection. 'Cursor to' does neither. 'Center at' might describe it better
1099 // than 'Skip'.
1100 static auto menu = std::shared_ptr{
1102 Menu( wxT("Cursor"), XXO("&Cursor to"),
1103 Command( wxT("CursSelStart"), XXO("Selection Star&t"),
1104 FN(OnCursorSelStart),
1106 Options{}.LongName( XO("Cursor to Selection Start") ) ),
1107 Command( wxT("CursSelEnd"), XXO("Selection En&d"),
1108 FN(OnCursorSelEnd),
1110 Options{}.LongName( XO("Cursor to Selection End") ) ),
1111
1112 Command( wxT("CursTrackStart"), XXO("Track &Start"),
1113 FN(OnCursorTrackStart),
1115 Options{ wxT("J"), XO("Cursor to Track Start") } ),
1116 Command( wxT("CursTrackEnd"), XXO("Track &End"),
1117 FN(OnCursorTrackEnd),
1119 Options{ wxT("K"), XO("Cursor to Track End") } ),
1120
1121 Command( wxT("CursProjectStart"), XXO("&Project Start"),
1122 FN(OnSkipStart),
1124 Options{ wxT("Home"), XO("Cursor to Project Start") } ),
1125 Command( wxT("CursProjectEnd"), XXO("Project E&nd"), FN(OnSkipEnd),
1127 Options{ wxT("End"), XO("Cursor to Project End") } )
1128 ) ) };
1129 return menu;
1130}
1131
1133 wxT("Transport/Basic")
1134};
1135
1137{
1138 static auto menu = std::shared_ptr{
1140 Menu( wxT("Cursor"), XXO("&Cursor"),
1141 Command( wxT("CursorLeft"), XXO("Cursor &Left"), FN(OnCursorLeft),
1143 Options{ wxT("Left") }.WantKeyUp().AllowDup() ),
1144 Command( wxT("CursorRight"), XXO("Cursor &Right"), FN(OnCursorRight),
1146 Options{ wxT("Right") }.WantKeyUp().AllowDup() ),
1147 Command( wxT("CursorShortJumpLeft"), XXO("Cursor Sh&ort Jump Left"),
1148 FN(OnCursorShortJumpLeft),
1150 Command( wxT("CursorShortJumpRight"), XXO("Cursor Shor&t Jump Right"),
1151 FN(OnCursorShortJumpRight),
1153 Command( wxT("CursorLongJumpLeft"), XXO("Cursor Long J&ump Left"),
1154 FN(OnCursorLongJumpLeft),
1155 TracksExistFlag() | TrackPanelHasFocus(), wxT("Shift+,") ),
1156 Command( wxT("CursorLongJumpRight"), XXO("Cursor Long Ju&mp Right"),
1157 FN(OnCursorLongJumpRight),
1158 TracksExistFlag() | TrackPanelHasFocus(), wxT("Shift+.") )
1159 ) ) };
1160 return menu;
1161}
1162
1164 wxT("Optional/Extra/Part2")
1165};
1166
1168{
1169 static auto menu = std::shared_ptr{
1171 Menu( wxT("Seek"), XXO("See&k"),
1172 Command( wxT("SeekLeftShort"), XXO("Short Seek &Left During Playback"),
1173 FN(OnSeekLeftShort), AudioIOBusyFlag(),
1174 Options{ wxT("Left") }.AllowDup() ),
1175 Command( wxT("SeekRightShort"),
1176 XXO("Short Seek &Right During Playback"), FN(OnSeekRightShort),
1178 Options{ wxT("Right") }.AllowDup() ),
1179 Command( wxT("SeekLeftLong"), XXO("Long Seek Le&ft During Playback"),
1180 FN(OnSeekLeftLong), AudioIOBusyFlag(),
1181 Options{ wxT("Shift+Left") }.AllowDup() ),
1182 Command( wxT("SeekRightLong"), XXO("Long Seek Rig&ht During Playback"),
1183 FN(OnSeekRightLong), AudioIOBusyFlag(),
1184 Options{ wxT("Shift+Right") }.AllowDup() )
1185 ) ) };
1186 return menu;
1187}
1188
1190 wxT("Optional/Extra/Part1")
1191};
1192
1193}
1194
1195#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:61
double GetStartTime() const
Get the minimum of Start() values of intervals, or 0 when none.
Definition: Channel.cpp:50
Client code makes static instance from a factory of attachments; passes it to Get or Find as a retrie...
Definition: ClientData.h:275
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:95
static LabelTrackView & Get(LabelTrack &)
A listener notified of changes in preferences.
Definition: Prefs.h:652
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 IsSyncLockSelectedP(const Track *pTrack)
Definition: SyncLock.h:51
bool IsSelected() const
Definition: Track.cpp:258
virtual bool SupportsBasicEditing() const
Whether this track type implements cut-copy-paste; by default, true.
Definition: Track.cpp:797
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:314
static TrackPanel & Get(AudacityProject &project)
Definition: TrackPanel.cpp:234
NotifyingSelectedRegion selectedRegion
Definition: ViewInfo.h:215
static ViewInfo & Get(AudacityProject &project)
Definition: ViewInfo.cpp:235
static Viewport & Get(AudacityProject &project)
Definition: Viewport.cpp:33
static WaveChannelView & GetFirst(WaveTrack &wt)
Get the view of the first channel.
A Track that contains audio waveform data.
Definition: WaveTrack.h:203
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:279
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
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)
WAVE_TRACK_API WaveTrack::IntervalConstHolders GetClipsIntersecting(const WaveTrack &track, double t0, double t1)
void DoBoundaryMove(AudacityProject &project, int step, SeekInfo &info)
double NearestZeroCrossing(AudacityProject &project, double t0)
Definition: SelectMenus.cpp:36
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:31
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:182
const char * end(const char *str) noexcept
Definition: StringUtils.h:106
MessageBoxOptions && Caption(TranslatableString caption_) &&
Definition: BasicUI.h:101
A convenient default parameter for class template Site.
Definition: ClientData.h:29
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)