Audacity 3.2.0
SelectHandle.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3Audacity: A Digital Audio Editor
4
5SelectHandle.cpp
6
7Paul Licameli split from TrackPanel.cpp
8
9**********************************************************************/
10
11
12#include "SelectHandle.h"
13
14#include "Scrubbing.h"
15#include "TrackView.h"
16
17#include "AColor.h"
18#include "../../SpectrumAnalyst.h"
19#include "../../LabelTrack.h"
20#include "NumberScale.h"
21#include "Project.h"
22#include "ProjectAudioIO.h"
23#include "ProjectHistory.h"
24#include "../../ProjectSettings.h"
25#include "../../ProjectWindow.h"
26#include "../../RefreshCode.h"
27#include "../../SelectUtilities.h"
28#include "SelectionState.h"
29#include "SyncLock.h"
30#include "../../TrackArtist.h"
31#include "../../TrackPanelAx.h"
32#include "../../TrackPanel.h"
33#include "../../TrackPanelDrawingContext.h"
34#include "../../TrackPanelMouseEvent.h"
35#include "ViewInfo.h"
36#include "WaveClip.h"
37#include "WaveTrack.h"
38#include "../../prefs/SpectrogramSettings.h"
39#include "../../../images/Cursors.h"
40
41// Only for definition of SonifyBeginModifyState:
42//#include "../../NoteTrack.h"
43
44enum {
45 //This constant determines the size of the horizontal region (in pixels) around
46 //the right and left selection bounds that can be used for horizontal selection adjusting
47 //(or, vertical distance around top and bottom bounds in spectrograms,
48 // for vertical selection adjusting)
50
51 // Seems 4 is too small to work at the top. Why?
53};
54
55// #define SPECTRAL_EDITING_ESC_KEY
56
58{
59 return mSelectionStateChanger.get() != NULL;
60}
61
62namespace
63{
65 wxInt64 FrequencyToPosition(const WaveTrack *wt,
66 double frequency,
67 wxInt64 trackTopEdge,
68 int trackHeight)
69 {
70 const auto &settings = SpectrogramSettings::Get(*wt);
71 float minFreq, maxFreq;
72 SpectrogramBounds::Get(*wt).GetBounds(*wt, minFreq, maxFreq);
73 const NumberScale numberScale(settings.GetScale(minFreq, maxFreq));
74 const float p = numberScale.ValueToPosition(frequency);
75 return trackTopEdge + wxInt64((1.0 - p) * trackHeight);
76 }
77
81 bool maySnap,
82 wxInt64 mouseYCoordinate,
83 wxInt64 trackTopEdge,
84 int trackHeight)
85 {
86 const double rate = wt->GetRate();
87
88 // Handle snapping
89 if (maySnap &&
90 mouseYCoordinate - trackTopEdge < FREQ_SNAP_DISTANCE)
91 return rate;
92 if (maySnap &&
93 trackTopEdge + trackHeight - mouseYCoordinate < FREQ_SNAP_DISTANCE)
94 return -1;
95
96 const auto &settings = SpectrogramSettings::Get(*wt);
97 float minFreq, maxFreq;
99 .GetBounds(*wt, minFreq, maxFreq);
100 const NumberScale numberScale(settings.GetScale(minFreq, maxFreq));
101 const double p = double(mouseYCoordinate - trackTopEdge) / trackHeight;
102 return numberScale.PositionToValue(1.0 - p);
103 }
104
105 template<typename T>
106 inline void SetIfNotNull(T * pValue, const T Value)
107 {
108 if (pValue == NULL)
109 return;
110 *pValue = Value;
111 }
112
113 // This returns true if we're a spectral editing track.
114 inline bool isSpectralSelectionView(const TrackView *pTrackView) {
115 return
116 pTrackView &&
117 pTrackView->IsSpectral() &&
118 pTrackView->FindTrack() &&
119 pTrackView->FindTrack()->TypeSwitch< bool >(
120 [&](const WaveTrack *wt) {
121 const auto &settings = SpectrogramSettings::Get(*wt);
122 return settings.SpectralSelectionEnabled();
123 });
124 }
125
129#ifdef EXPERIMENTAL_SPECTRAL_EDITING
130 SBBottom, SBTop, SBCenter, SBWidth,
131#endif
132 };
133
135 (
136 const double t0, const double t1,
137 const ViewInfo &viewInfo,
138 double selend, bool onlyWithinSnapDistance,
139 wxInt64 *pPixelDist, double *pPinValue)
140 {
141 const wxInt64 posS = viewInfo.TimeToPosition(selend);
142 const wxInt64 pos0 = viewInfo.TimeToPosition(t0);
143 wxInt64 pixelDist = std::abs(posS - pos0);
144 bool chooseLeft = true;
145
146 if (t1<=t0)
147 // Special case when selection is a point, and thus left
148 // and right distances are the same
149 chooseLeft = (selend < t0);
150 else {
151 const wxInt64 pos1 = viewInfo.TimeToPosition(t1);
152 const wxInt64 rightDist = std::abs(posS - pos1);
153 if (rightDist < pixelDist)
154 chooseLeft = false, pixelDist = rightDist;
155 }
156
157 SetIfNotNull(pPixelDist, pixelDist);
158
159 if (onlyWithinSnapDistance &&
160 pixelDist >= SELECTION_RESIZE_REGION) {
161 SetIfNotNull(pPinValue, -1.0);
162 return SBNone;
163 }
164 else if (chooseLeft) {
165 SetIfNotNull(pPinValue, t1);
166 return SBLeft;
167 }
168 else {
169 SetIfNotNull(pPinValue, t0);
170 return SBRight;
171 }
172 }
173
175 (const ViewInfo &viewInfo,
176 wxCoord xx, wxCoord yy, const TrackView *pTrackView, const wxRect &rect,
177 bool mayDragWidth, bool onlyWithinSnapDistance,
178 double *pPinValue = NULL)
179 {
180 // Choose one of four boundaries to adjust, or the center frequency.
181 // May choose frequencies only if in a spectrogram view and
182 // within the time boundaries.
183 // May choose no boundary if onlyWithinSnapDistance is true.
184 // Otherwise choose the eligible boundary nearest the mouse click.
185 const double selend = viewInfo.PositionToTime(xx, rect.x);
186 wxInt64 pixelDist = 0;
187 const double t0 = viewInfo.selectedRegion.t0();
188 const double t1 = viewInfo.selectedRegion.t1();
189
190 SelectionBoundary boundary =
191 ChooseTimeBoundary(t0,t1,viewInfo, selend, onlyWithinSnapDistance,
192 &pixelDist, pPinValue);
193
194#ifdef EXPERIMENTAL_SPECTRAL_EDITING
195 //const double t0 = viewInfo.selectedRegion.t0();
196 //const double t1 = viewInfo.selectedRegion.t1();
197 const double f0 = viewInfo.selectedRegion.f0();
198 const double f1 = viewInfo.selectedRegion.f1();
199 const double fc = viewInfo.selectedRegion.fc();
200 double ratio = 0;
201
202 bool chooseTime = true;
203 bool chooseBottom = true;
204 bool chooseCenter = false;
205 // Consider adjustment of frequencies only if mouse is
206 // within the time boundaries
207 if (!viewInfo.selectedRegion.isPoint() &&
208 t0 <= selend && selend < t1 &&
209 isSpectralSelectionView(pTrackView)) {
210 // Spectral selection track is always wave
211 auto pTrack = pTrackView->FindTrack();
212 const WaveTrack *const wt =
213 static_cast<const WaveTrack*>(pTrack.get());
214 const wxInt64 bottomSel = (f0 >= 0)
215 ? FrequencyToPosition(wt, f0, rect.y, rect.height)
216 : rect.y + rect.height;
217 const wxInt64 topSel = (f1 >= 0)
218 ? FrequencyToPosition(wt, f1, rect.y, rect.height)
219 : rect.y;
220 wxInt64 signedBottomDist = (int)(yy - bottomSel);
221 wxInt64 verticalDist = std::abs(signedBottomDist);
222 if (bottomSel == topSel)
223 // Top and bottom are too close to resolve on screen
224 chooseBottom = (signedBottomDist >= 0);
225 else {
226 const wxInt64 topDist = std::abs((int)(yy - topSel));
227 if (topDist < verticalDist)
228 chooseBottom = false, verticalDist = topDist;
229 }
230 if (fc > 0
231#ifdef SPECTRAL_EDITING_ESC_KEY
232 && mayDragWidth
233#endif
234 ) {
235 const wxInt64 centerSel =
236 FrequencyToPosition(wt, fc, rect.y, rect.height);
237 const wxInt64 centerDist = abs((int)(yy - centerSel));
238 if (centerDist < verticalDist)
239 chooseCenter = true, verticalDist = centerDist,
240 ratio = f1 / fc;
241 }
242 if (verticalDist >= 0 &&
243 verticalDist < pixelDist) {
244 pixelDist = verticalDist;
245 chooseTime = false;
246 }
247 }
248
249 if (!chooseTime) {
250 // PRL: Seems I need a larger tolerance to make snapping work
251 // at top of track, not sure why
252 if (onlyWithinSnapDistance &&
253 pixelDist >= FREQ_SNAP_DISTANCE) {
254 SetIfNotNull(pPinValue, -1.0);
255 return SBNone;
256 }
257 else if (chooseCenter) {
258 SetIfNotNull(pPinValue, ratio);
259 return SBCenter;
260 }
261 else if (mayDragWidth && fc > 0) {
262 SetIfNotNull(pPinValue, fc);
263 return SBWidth;
264 }
265 else if (chooseBottom) {
266 SetIfNotNull(pPinValue, f1);
267 return SBBottom;
268 }
269 else {
270 SetIfNotNull(pPinValue, f0);
271 return SBTop;
272 }
273 }
274 else
275#endif
276 {
277 return boundary;
278 }
279 }
280
281 wxCursor *SelectCursor()
282 {
283 static auto selectCursor =
284 ::MakeCursor(wxCURSOR_IBEAM, IBeamCursorXpm, 17, 16);
285 return &*selectCursor;
286 }
287
288 wxCursor *EnvelopeCursor()
289 {
290 // This one doubles as the center frequency cursor for spectral selection:
291 static auto envelopeCursor =
292 ::MakeCursor(wxCURSOR_ARROW, EnvCursorXpm, 16, 16);
293 return &*envelopeCursor;
294 }
295
297 (SelectionBoundary boundary, bool frequencySnapping,
298 TranslatableString &tip, wxCursor *&pCursor)
299 {
300 static wxCursor adjustLeftSelectionCursor{ wxCURSOR_POINT_LEFT };
301 static wxCursor adjustRightSelectionCursor{ wxCURSOR_POINT_RIGHT };
302
303 static auto bottomFrequencyCursor =
304 ::MakeCursor(wxCURSOR_ARROW, BottomFrequencyCursorXpm, 16, 16);
305 static auto topFrequencyCursor =
306 ::MakeCursor(wxCURSOR_ARROW, TopFrequencyCursorXpm, 16, 16);
307 static auto bandWidthCursor =
308 ::MakeCursor(wxCURSOR_ARROW, BandWidthCursorXpm, 16, 16);
309
310 switch (boundary) {
311 case SBNone:
312 pCursor = SelectCursor();
313 break;
314 case SBLeft:
315 tip = XO("Click and drag to move left selection boundary.");
316 pCursor = &adjustLeftSelectionCursor;
317 break;
318 case SBRight:
319 tip = XO("Click and drag to move right selection boundary.");
320 pCursor = &adjustRightSelectionCursor;
321 break;
322#ifdef EXPERIMENTAL_SPECTRAL_EDITING
323 case SBBottom:
324 tip = XO("Click and drag to move bottom selection frequency.");
325 pCursor = &*bottomFrequencyCursor;
326 break;
327 case SBTop:
328 tip = XO("Click and drag to move top selection frequency.");
329 pCursor = &*topFrequencyCursor;
330 break;
331 case SBCenter:
332 {
333#ifndef SPECTRAL_EDITING_ESC_KEY
334 tip =
335 frequencySnapping ?
336 XO("Click and drag to move center selection frequency to a spectral peak.") :
337 XO("Click and drag to move center selection frequency.");
338
339#else
340 shiftDown;
341
342 tip =
343 XO("Click and drag to move center selection frequency.");
344
345#endif
346
347 pCursor = EnvelopeCursor();
348 }
349 break;
350 case SBWidth:
351 tip = XO("Click and drag to adjust frequency bandwidth.");
352 pCursor = &*bandWidthCursor;
353 break;
354#endif
355 default:
356 wxASSERT(false);
357 } // switch
358 // Falls through the switch if there was no boundary found.
359 }
360}
361
363(std::weak_ptr<SelectHandle> &holder,
364 const TrackPanelMouseState &st, const AudacityProject *pProject,
365 const std::shared_ptr<TrackView> &pTrackView)
366{
367 // This handle is a little special because there may be some state to
368 // preserve during movement before the click.
369 auto old = holder.lock();
370 bool oldUseSnap = true;
371 if (old) {
372 // It should not have started listening to timer events
373 if( old->mTimerHandler ) {
374 wxASSERT(false);
375 // Handle this eventuality anyway, don't leave a dangling back-pointer
376 // in the attached event handler.
377 old->mTimerHandler.reset();
378 }
379 oldUseSnap = old->mUseSnap;
380 }
381
382 const auto &viewInfo = ViewInfo::Get( *pProject );
383 auto result = std::make_shared<SelectHandle>(
384 pTrackView, oldUseSnap, TrackList::Get( *pProject ), st, viewInfo );
385
386 result = AssignUIHandlePtr(holder, result);
387
388 //Make sure we are within the selected track
389 // Adjusting the selection edges can be turned off in
390 // the preferences...
391 auto pTrack = pTrackView->FindTrack();
392 if (!pTrack->GetSelected() || !viewInfo.bAdjustSelectionEdges)
393 {
394 return result;
395 }
396
397 {
398 const wxRect &rect = st.rect;
399 wxInt64 leftSel = viewInfo.TimeToPosition(viewInfo.selectedRegion.t0(), rect.x);
400 wxInt64 rightSel = viewInfo.TimeToPosition(viewInfo.selectedRegion.t1(), rect.x);
401 // Something is wrong if right edge comes before left edge
402 wxASSERT(!(rightSel < leftSel));
403 static_cast<void>(leftSel); // Suppress unused variable warnings if not in debug-mode
404 static_cast<void>(rightSel);
405 }
406
407 return result;
408}
409
411(const SelectHandle &oldState, const SelectHandle &newState)
412{
413 auto useSnap = oldState.mUseSnap;
414 // This is guaranteed when constructing the NEW handle:
415 wxASSERT( useSnap == newState.mUseSnap );
416 if (!useSnap)
417 return 0;
418
419 auto &oldSnapState = oldState.mSnapStart;
420 auto &newSnapState = newState.mSnapStart;
421 if ( oldSnapState.Snapped() == newSnapState.Snapped() &&
422 (!oldSnapState.Snapped() ||
423 oldSnapState.outCoord == newSnapState.outCoord) )
424 return 0;
425
427}
428
430( const std::shared_ptr<TrackView> &pTrackView, bool useSnap,
431 const TrackList &trackList,
432 const TrackPanelMouseState &st, const ViewInfo &viewInfo )
433 : mpView{ pTrackView }
434 // Selection dragging can snap to play region boundaries
435 , mSnapManager{ std::make_shared<SnapManager>(
436 *trackList.GetOwner(), trackList, viewInfo, SnapPointArray{
437 SnapPoint{ viewInfo.playRegion.GetLastActiveStart() },
438 SnapPoint{ viewInfo.playRegion.GetLastActiveEnd() },
439 } ) }
440{
441 const wxMouseState &state = st.state;
442 mRect = st.rect;
443
444 auto time = std::max(0.0, viewInfo.PositionToTime(state.m_x, mRect.x));
445 auto pTrack = pTrackView->FindTrack();
446 mSnapStart = mSnapManager->Snap(pTrack.get(), time, false);
449 else
450 mSnapStart.outCoord = -1;
451
452 mUseSnap = useSnap;
453}
454
456{
457}
458
459namespace {
460 // Is the distance between A and B less than D?
461 template < class A, class B, class DIST > bool within(A a, B b, DIST d)
462 {
463 return (a > b - d) && (a < b + d);
464 }
465
466 inline double findMaxRatio(double center, double rate)
467 {
468 const double minFrequency = 1.0;
469 const double maxFrequency = (rate / 2.0);
470 const double frequency =
471 std::min(maxFrequency,
472 std::max(minFrequency, center));
473 return
474 std::min(frequency / minFrequency, maxFrequency / frequency);
475 }
476}
477
479{
480 SetUseSnap(true, project);
481}
482
484{
485 mUseSnap = use;
486
487 bool hasSnap = HasSnap();
488 if (hasSnap)
489 // Repaint to turn the snap lines on or off
491
492 if (IsClicked()) {
493 // Readjust the moving selection end
495 ViewInfo::Get( *project ),
497 nullptr);
498 }
499}
500
502{
503 return
504 (IsClicked() ? mSnapEnd : mSnapStart).snappedPoint;
505}
506
508{
509 return HasSnap() && mUseSnap;
510}
511
513{
514 if (SelectHandle::HasEscape(project)) {
515 SetUseSnap(false, project);
516 return true;
517 }
518 return false;
519}
520
522(const TrackPanelMouseEvent &evt, AudacityProject *pProject)
523{
526
527 using namespace RefreshCode;
528
529 const auto pView = mpView.lock();
530 if ( !pView )
531 return Cancelled;
532
533 wxMouseEvent &event = evt.event;
534 const auto sTrack = TrackList::Get( *pProject ).Lock( FindTrack() );
535 const auto pTrack = sTrack.get();
536 auto &trackPanel = TrackPanel::Get( *pProject );
537 auto &viewInfo = ViewInfo::Get( *pProject );
538
539 mMostRecentX = event.m_x;
540 mMostRecentY = event.m_y;
541
542 bool selectChange = (
543 event.LeftDown() &&
544 event.ControlDown() &&
545 pTrack->TypeSwitch<bool>( [&](LabelTrack *){
546 // We should reach this, only in default of other hits on glyphs or
547 // text boxes.
548 bool bShift = event.ShiftDown();
549 bool unsafe = ProjectAudioIO::Get( *pProject ).IsAudioActive();
551 *pProject, pTrack, bShift, true, !unsafe);
552 return true;
553 } )
554 );
555 if ( selectChange )
556 // Do not start a drag
557 return RefreshAll | Cancelled;
558
559 auto &selectionState = SelectionState::Get( *pProject );
560 if (event.LeftDClick() && !event.ShiftDown()) {
561 auto &trackList = TrackList::Get( *pProject );
562
563 // Deselect all other tracks and select this one.
564 selectionState.SelectNone( trackList );
565
566 selectionState.SelectTrack( *pTrack, true, true );
567
568 // Default behavior: select whole track
570 ( viewInfo, *pTrack, SyncLockState::Get(*pProject).IsSyncLocked() );
571
572 // Special case: if we're over a clip in a WaveTrack,
573 // select just that clip
574 pTrack->TypeSwitch( [&] ( WaveTrack *wt ) {
575 auto time = viewInfo.PositionToTime(event.m_x, mRect.x);
576 WaveClip *const selectedClip = wt->GetClipAtTime(time);
577 if (selectedClip) {
578 viewInfo.selectedRegion.setTimes(
579 selectedClip->GetPlayStartTime(), selectedClip->GetPlayEndTime());
580 }
581 } );
582
583 ProjectHistory::Get( *pProject ).ModifyState(false);
584
585 // Do not start a drag
586 return RefreshAll | Cancelled;
587 }
588 else if (!event.LeftDown())
589 return Cancelled;
590
591 mInitialSelection = viewInfo.selectedRegion;
592
593 auto &trackList = TrackList::Get( *pProject );
595 std::make_shared< SelectionStateChanger >( selectionState, trackList );
596
598
599 bool bShiftDown = event.ShiftDown();
600 bool bCtrlDown = event.ControlDown();
601
603 auto xx = viewInfo.TimeToPosition(mSelStart, mRect.x);
604
605 // I. Shift-click adjusts an existing selection
606 if (bShiftDown || bCtrlDown) {
607 if (bShiftDown)
608 selectionState.ChangeSelectionOnShiftClick( trackList, *pTrack );
609 if( bCtrlDown ){
610 //Commented out bIsSelected toggles, as in Track Control Panel.
611 //bool bIsSelected = pTrack->GetSelected();
612 //Actual bIsSelected will always add.
613 bool bIsSelected = false;
614 // Don't toggle away the last selected track.
615 if( !bIsSelected || trackPanel.GetSelectedTrackCount() > 1 )
616 selectionState.SelectTrack( *pTrack, !bIsSelected, true );
617 }
618
619 double value;
620 // Shift-click, choose closest boundary
621 SelectionBoundary boundary =
622 ChooseBoundary(viewInfo, xx, event.m_y,
623 pView.get(), mRect, false, false, &value);
624 mSelectionBoundary = boundary;
625 switch (boundary) {
626 case SBLeft:
627 case SBRight:
628 {
629#ifdef EXPERIMENTAL_SPECTRAL_EDITING
630 // If drag starts, change time selection only
631 // (also exit frequency snapping)
632 mFreqSelMode = FREQ_SEL_INVALID;
633#endif
634 mSelStartValid = true;
635 mSelStart = value;
637 AdjustSelection(pProject, viewInfo, event.m_x, mRect.x, pTrack);
638 break;
639 }
640#ifdef EXPERIMENTAL_SPECTRAL_EDITING
641 case SBBottom:
642 case SBTop:
643 {
644 mFreqSelTrack = pTrack->SharedPointer<const WaveTrack>();
645 mFreqSelPin = value;
646 mFreqSelMode =
647 (boundary == SBBottom)
649
650 // Drag frequency only, not time:
651 mSelStartValid = false;
653 static_cast<WaveTrack*>(pTrack),
654 viewInfo, event.m_y, mRect.y, mRect.height);
655 break;
656 }
657 case SBCenter:
658 {
659 const auto wt = static_cast<const WaveTrack*>(pTrack);
660 HandleCenterFrequencyClick(viewInfo, true, wt, value);
661 break;
662 }
663#endif
664 default:
665 wxASSERT(false);
666 };
667
668 // For persistence of the selection change:
669 ProjectHistory::Get( *pProject ).ModifyState(false);
670
671 // Get timer events so we can auto-scroll
672 Connect(pProject);
673
674 // Full refresh since the label area may need to indicate
675 // newly selected tracks.
676 return RefreshAll;
677 }
678
679 // II. Unmodified click starts a NEW selection
680
681 //Make sure you are within the selected track
682 bool startNewSelection = true;
683 if (pTrack && pTrack->GetSelected()) {
684 // Adjusting selection edges can be turned off in the
685 // preferences now
686 if (viewInfo.bAdjustSelectionEdges) {
687#ifdef EXPERIMENTAL_SPECTRAL_EDITING
688 if (mFreqSelMode == FREQ_SEL_SNAPPING_CENTER &&
689 isSpectralSelectionView(pView.get())) {
690 // This code is no longer reachable, but it had a place in the
691 // spectral selection prototype. It used to be that you could be
692 // in a center-frequency-snapping mode that was not a mouse drag
693 // but responded to mouse movements. Click exited that and dragged
694 // width instead. PRL.
695
696 // Ignore whether we are inside the time selection.
697 // Exit center-snapping, start dragging the width.
698 mFreqSelMode = FREQ_SEL_PINNED_CENTER;
699 mFreqSelTrack = pTrack->SharedPointer<const WaveTrack>();
700 mFreqSelPin = viewInfo.selectedRegion.fc();
701 // Do not adjust time boundaries
702 mSelStartValid = false;
704 static_cast<WaveTrack*>(pTrack),
705 viewInfo, event.m_y, mRect.y, mRect.height);
706 // For persistence of the selection change:
707 ProjectHistory::Get( *pProject ).ModifyState(false);
708 mSelectionBoundary = SBWidth;
709 return RefreshNone;
710 }
711 else
712#endif
713 {
714 // Not shift-down, choose boundary only within snapping
715 double value;
716 SelectionBoundary boundary =
717 ChooseBoundary(viewInfo, xx, event.m_y,
718 pView.get(), mRect, true, true, &value);
719 mSelectionBoundary = boundary;
720 switch (boundary) {
721 case SBNone:
722 // startNewSelection remains true
723 break;
724 case SBLeft:
725 case SBRight:
726 startNewSelection = false;
727#ifdef EXPERIMENTAL_SPECTRAL_EDITING
728 // Disable frequency selection
729 mFreqSelMode = FREQ_SEL_INVALID;
730#endif
731 mSelStartValid = true;
732 mSelStart = value;
734 break;
735#ifdef EXPERIMENTAL_SPECTRAL_EDITING
736 case SBBottom:
737 case SBTop:
738 case SBWidth:
739 startNewSelection = false;
740 // Disable time selection
741 mSelStartValid = false;
742 mFreqSelTrack = pTrack->SharedPointer<const WaveTrack>();
743 mFreqSelPin = value;
744 mFreqSelMode =
745 (boundary == SBWidth) ? FREQ_SEL_PINNED_CENTER :
746 (boundary == SBBottom) ? FREQ_SEL_BOTTOM_FREE :
748 break;
749 case SBCenter:
750 {
751 const auto wt = static_cast<const WaveTrack*>(pTrack);
752 HandleCenterFrequencyClick(viewInfo, false, wt, value);
753 startNewSelection = false;
754 break;
755 }
756#endif
757 default:
758 wxASSERT(false);
759 }
760 }
761 } // bAdjustSelectionEdges
762 }
763
764 // III. Common case for starting a NEW selection
765
766 if (startNewSelection) {
767 // If we didn't move a selection boundary, start a NEW selection
768 selectionState.SelectNone( trackList );
769#ifdef EXPERIMENTAL_SPECTRAL_EDITING
770 StartFreqSelection (viewInfo, event.m_y, mRect.y, mRect.height,
771 pView.get());
772#endif
773 StartSelection(pProject);
774 selectionState.SelectTrack( *pTrack, true, true );
775 TrackFocus::Get( *pProject ).Set(pTrack);
776
777 Connect(pProject);
778 return RefreshAll;
779 }
780 else {
781 Connect(pProject);
782 return RefreshAll;
783 }
784}
785
787(const TrackPanelMouseEvent &evt, AudacityProject *pProject)
788{
789 using namespace RefreshCode;
790
791 const auto pView = mpView.lock();
792 if ( !pView )
793 return Cancelled;
794
795 auto &viewInfo = ViewInfo::Get( *pProject );
796 const wxMouseEvent &event = evt.event;
797
798 int x = mAutoScrolling ? mMostRecentX : event.m_x;
799 int y = mAutoScrolling ? mMostRecentY : event.m_y;
800 mMostRecentX = x;
801 mMostRecentY = y;
802
806
807 // Fuhggeddaboudit if we're not dragging and not autoscrolling.
808 if (!event.Dragging() && !mAutoScrolling)
809 return RefreshNone;
810
811 if (event.CmdDown()) {
812 // Ctrl-drag has no meaning, fuhggeddaboudit
813 // JKC YES it has meaning.
814 //return RefreshNone;
815 }
816
817 // Also fuhggeddaboudit if not in a track.
818 auto pTrack = TrackList::Get( *pProject ).Lock( FindTrack() );
819 if (!pTrack)
820 return RefreshNone;
821
822 // JKC: Logic to prevent a selection smaller than 5 pixels to
823 // prevent accidental dragging when selecting.
824 // (if user really wants a tiny selection, they should zoom in).
825 // Can someone make this value of '5' configurable in
826 // preferences?
827 enum { minimumSizedSelection = 5 }; //measured in pixels
828
829 // Might be dragging frequency bounds only, test
830 if (mSelStartValid) {
831 wxInt64 SelStart = viewInfo.TimeToPosition(mSelStart, mRect.x); //cvt time to pixels.
832 // Abandon this drag if selecting < 5 pixels.
833 if (wxLongLong(SelStart - x).Abs() < minimumSizedSelection)
834 return RefreshNone;
835 }
836
837 if (evt.pCell) {
838 if ( auto clickedTrack =
839 static_cast<CommonTrackPanelCell*>(evt.pCell.get())->FindTrack() ) {
840 // Handle which tracks are selected
841 Track *sTrack = pTrack.get();
842 Track *eTrack = clickedTrack.get();
843 auto &trackList = TrackList::Get( *pProject );
844 if ( sTrack && eTrack && !event.ControlDown() ) {
845 auto &selectionState = SelectionState::Get( *pProject );
846 selectionState.SelectRangeOfTracks( trackList, *sTrack, *eTrack );
847 }
848
849 #ifdef EXPERIMENTAL_SPECTRAL_EDITING
850 #ifndef SPECTRAL_EDITING_ESC_KEY
851 if (mFreqSelMode == FREQ_SEL_SNAPPING_CENTER &&
852 !viewInfo.selectedRegion.isPoint())
854 (pProject, viewInfo, y, mRect.y, mRect.height, pView.get());
855 else
856 #endif
857 if ( TrackList::Get( *pProject ).Lock(mFreqSelTrack) == pTrack )
859 static_cast<WaveTrack*>(pTrack.get()),
860 viewInfo, y, mRect.y, mRect.height);
861 #endif
862
863 AdjustSelection(pProject, viewInfo, x, mRect.x, clickedTrack.get());
864 }
865 }
866
867 return RefreshNone
868
869 // If scrubbing does not use the helper poller thread, then
870 // don't refresh at every mouse event, because it slows down seek-scrub.
871 // Instead, let OnTimer do it, which is often enough.
872 // And even if scrubbing does use the thread, then skipping refresh does not
873 // bring that advantage, but it is probably still a good idea anyway.
874
875 // | UpdateSelection
876
877 ;
878}
879
881(const TrackPanelMouseState &st, AudacityProject *pProject)
882{
883 if (!HasSnap() && !mUseSnap)
884 // Moved out of snapping; revert to un-escaped state
885 mUseSnap = true;
886
887 const auto pView = mpView.lock();
888 if ( !pView )
889 return {};
890
891 auto pTrack = FindTrack().lock();
892 if (!pTrack)
893 return {};
894
896 wxCursor *pCursor = SelectCursor();
897 if ( IsClicked() )
898 // Use same cursor as at the click
901 (mFreqSelMode == FREQ_SEL_SNAPPING_CENTER),
902 tip, pCursor);
903 else {
904 // Choose one of many cursors for mouse-over
905
906 auto &viewInfo = ViewInfo::Get( *pProject );
907
908 auto &state = st.state;
910 auto xx = viewInfo.TimeToPosition(time, mRect.x);
911
912 const bool bMultiToolMode =
914
915 //In Multi-tool mode, give multitool prompt if no-special-hit.
916 if (bMultiToolMode) {
917 // Look up the current key binding for Preferences.
918 // (Don't assume it's the default!)
919 auto keyStr =
920 CommandManager::Get( *pProject ).GetKeyFromName(wxT("Preferences"))
921 .Display( true );
922 if (keyStr.empty())
923 // No keyboard preference defined for opening Preferences dialog
924 /* i18n-hint: These are the names of a menu and a command in that menu */
925 keyStr = _("Edit, Preferences...");
926
927 /* i18n-hint: %s is usually replaced by "Ctrl+P" for Windows/Linux, "Command+," for Mac */
928 tip = XO("Multi-Tool Mode: %s for Mouse and Keyboard Preferences.")
929 .Format( keyStr );
930 // Later in this function we may point to some other string instead.
931 if (!pTrack->GetSelected() ||
932 !viewInfo.bAdjustSelectionEdges)
933 ;
934 else {
935 const wxRect &rect = st.rect;
936 const bool bShiftDown = state.ShiftDown();
937 const bool bCtrlDown = state.ControlDown();
938 const bool bModifierDown = bShiftDown || bCtrlDown;
939
940 // If not shift-down and not snapping center, then
941 // choose boundaries only in snapping tolerance,
942 // and may choose center.
943 SelectionBoundary boundary =
944 ChooseBoundary(viewInfo, xx, state.m_y,
945 pView.get(), rect, !bModifierDown, !bModifierDown);
946
947 SetTipAndCursorForBoundary(boundary, !bShiftDown, tip, pCursor);
948 }
949 }
950
951#if 0
952 // This is a vestige of an idea in the prototype version.
953 // Center would snap without mouse button down, click would pin the center
954 // and drag width.
955#ifdef EXPERIMENTAL_SPECTRAL_EDITING
956 if ((mFreqSelMode == FREQ_SEL_SNAPPING_CENTER) &&
958 // Not shift-down, but center frequency snapping toggle is on
959 tip = XO("Click and drag to set frequency bandwidth.");
960 pCursor = &*envelopeCursor;
961 return {};
962 }
963#endif
964#endif
965
966 if (!pTrack->GetSelected() || !viewInfo.bAdjustSelectionEdges)
967 ;
968 else {
969 const wxRect &rect = st.rect;
970 const bool bShiftDown = state.ShiftDown();
971 const bool bCtrlDown = state.ControlDown();
972 const bool bModifierDown = bShiftDown || bCtrlDown;
974 viewInfo, xx, state.m_y,
975 pView.get(), rect, !bModifierDown, !bModifierDown);
976 SetTipAndCursorForBoundary(boundary, !bShiftDown, tip, pCursor);
977 }
978 }
979 if (tip.empty()) {
980 tip = XO("Click and drag to select audio");
981 }
982 if (HasEscape(pProject) && mUseSnap) {
983 tip.Join(
984/* i18n-hint: "Snapping" means automatic alignment of selection edges to any nearby label or clip boundaries */
985 XO("(snapping)"), wxT(" ")
986 );
987 }
988 return { tip, pCursor };
989}
990
992(const TrackPanelMouseEvent &, AudacityProject *pProject,
993 wxWindow *)
994{
995 using namespace RefreshCode;
996 ProjectHistory::Get( *pProject ).ModifyState(false);
997 mFrequencySnapper.reset();
998 mSnapManager.reset();
1000 mSelectionStateChanger->Commit();
1001 mSelectionStateChanger.reset();
1002 }
1003
1004 if (mUseSnap && (mSnapStart.outCoord != -1 || mSnapEnd.outCoord != -1))
1005 return RefreshAll;
1006 else
1007 return RefreshNone;
1008}
1009
1011{
1012 mSelectionStateChanger.reset();
1014
1016}
1017
1019 TrackPanelDrawingContext &context,
1020 const wxRect &rect, unsigned iPass )
1021{
1022 if ( iPass == TrackArtist::PassSnapping ) {
1023 auto &dc = context.dc;
1024 // Draw snap guidelines if we have any
1025 if ( mSnapManager ) {
1026 auto coord1 = (mUseSnap || IsClicked()) ? mSnapStart.outCoord : -1;
1027 auto coord2 = (!mUseSnap || !IsClicked()) ? -1 : mSnapEnd.outCoord;
1028 mSnapManager->Draw( &dc, coord1, coord2 );
1029 }
1030 }
1031}
1032
1035 const wxRect &rect, const wxRect &panelRect, unsigned iPass )
1036{
1037 if ( iPass == TrackArtist::PassSnapping )
1038 return MaximizeHeight( rect, panelRect );
1039 else
1040 return rect;
1041}
1042
1043std::weak_ptr<Track> SelectHandle::FindTrack()
1044{
1045 auto pView = mpView.lock();
1046 if (!pView)
1047 return {};
1048 else
1049 return pView->FindTrack();
1050}
1051
1053{
1054 mTimerHandler = std::make_shared<TimerHandler>( this, pProject );
1055}
1056
1058{
1059public:
1061 : mParent{ pParent }
1062 , mConnectedProject{ pProject }
1063 {
1067 }
1068
1069 // Receives timer event notifications, to implement auto-scroll
1071
1072private:
1076};
1077
1079{
1080 // AS: If the user is dragging the mouse and there is a track that
1081 // has captured the mouse, then scroll the screen, as necessary.
1082
1084
1085 // DM: If we're "autoscrolling" (which means that we're scrolling
1086 // because the user dragged from inside to outside the window,
1087 // not because the user clicked in the scroll bar), then
1088 // the selection code needs to be handled slightly differently.
1089 // We set this flag ("mAutoScrolling") to tell the selecting
1090 // code that we didn't get here as a result of a mouse event,
1091 // and therefore it should ignore the event,
1092 // and instead use the last known mouse position. Setting
1093 // this flag also causes the Mac to redraw immediately rather
1094 // than waiting for the next update event; this makes scrolling
1095 // smoother on MacOS 9.
1096
1097 const auto project = mConnectedProject;
1098 const auto &trackPanel = TrackPanel::Get( *project );
1099 auto &window = ProjectWindow::Get( *project );
1100 if (mParent->mMostRecentX >= mParent->mRect.x + mParent->mRect.width) {
1101 mParent->mAutoScrolling = true;
1102 window.TP_ScrollRight();
1103 }
1104 else if (mParent->mMostRecentX < mParent->mRect.x) {
1105 mParent->mAutoScrolling = true;
1106 window.TP_ScrollLeft();
1107 }
1108 else {
1109 // Bug1387: enable autoscroll during drag, if the pointer is at either
1110 // extreme x coordinate of the screen, even if that is still within the
1111 // track area.
1112
1113 int xx = mParent->mMostRecentX, yy = 0;
1114 trackPanel.ClientToScreen(&xx, &yy);
1115 if (xx == 0) {
1116 mParent->mAutoScrolling = true;
1117 window.TP_ScrollLeft();
1118 }
1119 else {
1120 int width, height;
1121 ::wxDisplaySize(&width, &height);
1122 if (xx == width - 1) {
1123 mParent->mAutoScrolling = true;
1124 window.TP_ScrollRight();
1125 }
1126 }
1127 }
1128
1129 auto pTrack = mParent->FindTrack().lock(); // TrackList::Lock() ?
1130 if (mParent->mAutoScrolling && pTrack) {
1131 // AS: To keep the selection working properly as we scroll,
1132 // we fake a mouse event (remember, this method is called
1133 // from a timer tick).
1134
1135 // AS: For some reason, GCC won't let us pass this directly.
1136 wxMouseEvent evt(wxEVT_MOTION);
1137 const auto size = trackPanel.GetSize();
1138 mParent->Drag(
1140 evt, mParent->mRect, size,
1141 TrackView::Get( *pTrack ).shared_from_this() },
1142 project
1143 );
1144 mParent->mAutoScrolling = false;
1145 TrackPanel::Get( *mConnectedProject ).Refresh(false);
1146 }
1147}
1148
1151{
1152 auto &viewInfo = ViewInfo::Get( *pProject );
1153 mSelStartValid = true;
1154
1155 viewInfo.selectedRegion.setTimes(mSelStart, mSelStart);
1156
1157 // PRL: commented out the Sonify stuff with the TrackPanel refactor.
1158 // It was no-op anyway.
1159 //SonifyBeginModifyState();
1160 ProjectHistory::Get( *pProject ).ModifyState(false);
1161 //SonifyEndModifyState();
1162}
1163
1166(AudacityProject *pProject,
1167 ViewInfo &viewInfo, int mouseXCoordinate, int trackLeftEdge,
1168 Track *track)
1169{
1170 if (!mSelStartValid)
1171 // Must be dragging frequency bounds only.
1172 return;
1173
1174 double selend =
1175 std::max(0.0, viewInfo.PositionToTime(mouseXCoordinate, trackLeftEdge));
1176 double origSelend = selend;
1177
1178 auto pTrack = Track::SharedPointer( track );
1179 if (!pTrack)
1180 pTrack = TrackList::Get( *pProject ).Lock( FindTrack() );
1181
1182 if (pTrack && mSnapManager.get()) {
1183 bool rightEdge = (selend > mSelStart);
1184 mSnapEnd = mSnapManager->Snap(pTrack.get(), selend, rightEdge);
1185 if (mSnapEnd.Snapped()) {
1186 if (mUseSnap)
1187 selend = mSnapEnd.outTime;
1189 mSnapEnd.outCoord += trackLeftEdge;
1190 }
1192 mSnapEnd.outCoord = -1;
1193
1194 // Check if selection endpoints are too close together to snap (unless
1195 // using snap-to-time -- then we always accept the snap results)
1196 if (mSnapStart.outCoord >= 0 &&
1197 mSnapEnd.outCoord >= 0 &&
1198 std::abs(mSnapStart.outCoord - mSnapEnd.outCoord) < 3) {
1200 selend = origSelend;
1201 mSnapEnd.outCoord = -1;
1202 }
1203 }
1204 AssignSelection(viewInfo, selend, pTrack.get());
1205}
1206
1208(ViewInfo &viewInfo, double selend, Track *pTrack)
1209{
1210 double sel0, sel1;
1211 if (mSelStart < selend) {
1212 sel0 = mSelStart;
1213 sel1 = selend;
1214 }
1215 else {
1216 sel1 = mSelStart;
1217 sel0 = selend;
1218 }
1219
1220 viewInfo.selectedRegion.setTimes(sel0, sel1);
1221}
1222
1224 int mouseYCoordinate, int trackTopEdge,
1225 int trackHeight, TrackView *pTrackView)
1226{
1227 mFreqSelTrack.reset();
1228 mFreqSelMode = FREQ_SEL_INVALID;
1230
1231 if (isSpectralSelectionView(pTrackView)) {
1232 // Spectral selection track is always wave
1233 auto shTrack = pTrackView->FindTrack()->SharedPointer<const WaveTrack>();
1234 mFreqSelTrack = shTrack;
1235 mFreqSelMode = FREQ_SEL_FREE;
1236 mFreqSelPin =
1237 PositionToFrequency(shTrack.get(), false, mouseYCoordinate,
1238 trackTopEdge, trackHeight);
1240 }
1241}
1242
1244 const WaveTrack *wt, ViewInfo &viewInfo,
1245 int mouseYCoordinate, int trackTopEdge,
1246 int trackHeight)
1247{
1248 if (mFreqSelMode == FREQ_SEL_INVALID ||
1249 mFreqSelMode == FREQ_SEL_SNAPPING_CENTER)
1250 return;
1251
1252 // Extension happens only when dragging in the same track in which we
1253 // started, and that is of a spectrogram display type.
1254
1255 const double rate = wt->GetRate();
1256 const double frequency =
1257 PositionToFrequency(wt, true, mouseYCoordinate,
1258 trackTopEdge, trackHeight);
1259
1260 // Dragging center?
1261 if (mFreqSelMode == FREQ_SEL_DRAG_CENTER) {
1262 if (frequency == rate || frequency < 1.0)
1263 // snapped to top or bottom
1267 else {
1268 // mFreqSelPin holds the ratio of top to center
1269 const double maxRatio = findMaxRatio(frequency, rate);
1270 const double ratio = std::min(maxRatio, mFreqSelPin);
1272 frequency / ratio, frequency * ratio);
1273 }
1274 }
1275 else if (mFreqSelMode == FREQ_SEL_PINNED_CENTER) {
1276 if (mFreqSelPin >= 0) {
1277 // Change both upper and lower edges leaving centre where it is.
1278 if (frequency == rate || frequency < 1.0)
1279 // snapped to top or bottom
1283 else {
1284 // Given center and mouse position, find ratio of the larger to the
1285 // smaller, limit that to the frequency scale bounds, and adjust
1286 // top and bottom accordingly.
1287 const double maxRatio = findMaxRatio(mFreqSelPin, rate);
1288 double ratio = frequency / mFreqSelPin;
1289 if (ratio < 1.0)
1290 ratio = 1.0 / ratio;
1291 ratio = std::min(maxRatio, ratio);
1293 mFreqSelPin / ratio, mFreqSelPin * ratio);
1294 }
1295 }
1296 }
1297 else {
1298 // Dragging of upper or lower.
1299 const bool bottomDefined =
1300 !(mFreqSelMode == FREQ_SEL_TOP_FREE && mFreqSelPin < 0);
1301 const bool topDefined =
1302 !(mFreqSelMode == FREQ_SEL_BOTTOM_FREE && mFreqSelPin < 0);
1303 if (!bottomDefined || (topDefined && mFreqSelPin < frequency)) {
1304 // Adjust top
1305 if (frequency == rate)
1306 // snapped high; upper frequency is undefined
1308 else
1309 viewInfo.selectedRegion.setF1(std::max(1.0, frequency));
1310
1312 }
1313 else {
1314 // Adjust bottom
1315 if (frequency < 1.0)
1316 // snapped low; lower frequency is undefined
1318 else
1319 viewInfo.selectedRegion.setF0(std::min(rate / 2.0, frequency));
1320
1322 }
1323 }
1324}
1325
1327(const ViewInfo &viewInfo, bool shiftDown, const WaveTrack *pTrack, double value)
1328{
1329 if (shiftDown) {
1330 // Disable time selection
1331 mSelStartValid = false;
1332 mFreqSelTrack = pTrack->SharedPointer<const WaveTrack>();
1333 mFreqSelPin = value;
1334 mFreqSelMode = FREQ_SEL_DRAG_CENTER;
1335 }
1336 else {
1337#ifndef SPECTRAL_EDITING_ESC_KEY
1338 // Start center snapping
1339 // Turn center snapping on (the only way to do this)
1340 mFreqSelMode = FREQ_SEL_SNAPPING_CENTER;
1341 // Disable time selection
1342 mSelStartValid = false;
1343 mFrequencySnapper = std::make_shared<SpectrumAnalyst>();
1345#endif
1346 }
1347}
1348
1350 (SpectrumAnalyst &analyst,
1351 const ViewInfo &viewInfo, const WaveTrack *pTrack)
1352{
1353 static const size_t minLength = 8;
1354
1355 const double rate = pTrack->GetRate();
1356
1357 // Grab samples, just for this track, at these times
1358 std::vector<float> frequencySnappingData;
1359 const auto start =
1360 pTrack->TimeToLongSamples(viewInfo.selectedRegion.t0());
1361 const auto end =
1362 pTrack->TimeToLongSamples(viewInfo.selectedRegion.t1());
1363 const auto length =
1364 std::min(frequencySnappingData.max_size(),
1365 limitSampleBufferSize(10485760, // as in FreqWindow.cpp
1366 end - start));
1367 const auto effectiveLength = std::max(minLength, length);
1368 frequencySnappingData.resize(effectiveLength, 0.0f);
1369 pTrack->GetFloats(
1370 &frequencySnappingData[0],
1371 start, length, fillZero,
1372 // Don't try to cope with exceptions, just read zeroes instead.
1373 false);
1374
1375 // Use same settings as are now used for spectrogram display,
1376 // except, shrink the window as needed so we get some answers
1377
1378 const auto &settings = SpectrogramSettings::Get(*pTrack);
1379 auto windowSize = settings.GetFFTLength();
1380
1381 while(windowSize > effectiveLength)
1382 windowSize >>= 1;
1383 const int windowType = settings.windowType;
1384
1385 analyst.Calculate(
1386 SpectrumAnalyst::Spectrum, windowType, windowSize, rate,
1387 &frequencySnappingData[0], length);
1388
1389 // We can now throw away the sample data but we keep the spectrum.
1390}
1391
1393 (AudacityProject *pProject, ViewInfo &viewInfo, int mouseYCoordinate,
1394 int trackTopEdge,
1395 int trackHeight, TrackView *pTrackView)
1396{
1397 auto pTrack = pTrackView->FindTrack().get();
1398 if (pTrack &&
1399 pTrack->GetSelected() &&
1400 isSpectralSelectionView(pTrackView)) {
1401 // Spectral selection track is always wave
1402 WaveTrack *const wt = static_cast<WaveTrack*>(pTrack);
1403 // PRL:
1404 // What would happen if center snapping selection began in one spectrogram track,
1405 // then continues inside another? We do not then recalculate
1406 // the spectrum (as was done in StartSnappingFreqSelection)
1407 // but snap according to the peaks in the old track.
1408
1409 // But if we always supply the original clicked track here that doesn't matter.
1410 const double rate = wt->GetRate();
1411 const double frequency =
1412 PositionToFrequency(wt, false, mouseYCoordinate,
1413 trackTopEdge, trackHeight);
1414 const double snappedFrequency =
1415 mFrequencySnapper->FindPeak(frequency, NULL);
1416 const double maxRatio = findMaxRatio(snappedFrequency, rate);
1417 double ratio = 2.0; // An arbitrary octave on each side, at most
1418 {
1419 const double f0 = viewInfo.selectedRegion.f0();
1420 const double f1 = viewInfo.selectedRegion.f1();
1421 if (f1 >= f0 && f0 >= 0)
1422 // Preserve already chosen ratio instead
1423 ratio = sqrt(f1 / f0);
1424 }
1425 ratio = std::min(ratio, maxRatio);
1426
1427 mFreqSelPin = snappedFrequency;
1429 snappedFrequency / ratio, snappedFrequency * ratio);
1430
1431 // A change here would affect what AdjustFreqSelection() does
1432 // in the prototype version where you switch from moving center to
1433 // dragging width with a click. No effect now.
1434 mFreqSelTrack = wt->SharedPointer<const WaveTrack>();
1435
1436 // SelectNone();
1437 // SelectTrack(pTrack, true);
1438 TrackFocus::Get( *pProject ).Set(pTrack);
1439 }
1440}
1441
1443 (SpectrumAnalyst &analyst,
1444 ViewInfo &viewInfo, const WaveTrack *pTrack, bool up)
1445{
1446 const auto &settings = SpectrogramSettings::Get(*pTrack);
1447 const auto windowSize = settings.GetFFTLength();
1448 const double rate = pTrack->GetRate();
1449 const double nyq = rate / 2.0;
1450 const double binFrequency = rate / windowSize;
1451
1452 double f1 = viewInfo.selectedRegion.f1();
1453 double centerFrequency = viewInfo.selectedRegion.fc();
1454 if (centerFrequency <= 0) {
1455 centerFrequency = up ? binFrequency : nyq;
1456 f1 = centerFrequency * sqrt(2.0);
1457 }
1458
1459 double ratio = f1 / centerFrequency;
1460 const int originalBin = floor(0.5 + centerFrequency / binFrequency);
1461 const int limitingBin = up ? floor(0.5 + nyq / binFrequency) : 1;
1462
1463 // This is crude and wasteful, doing the FFT each time the command is called.
1464 // It would be better to cache the data, but then invalidation of the cache would
1465 // need doing in all places that change the time selection.
1466 StartSnappingFreqSelection(analyst, viewInfo, pTrack);
1467 double snappedFrequency = centerFrequency;
1468 int bin = originalBin;
1469 if (up) {
1470 while (snappedFrequency <= centerFrequency &&
1471 bin < limitingBin)
1472 snappedFrequency = analyst.FindPeak(++bin * binFrequency, NULL);
1473 }
1474 else {
1475 while (snappedFrequency >= centerFrequency &&
1476 bin > limitingBin)
1477 snappedFrequency = analyst.FindPeak(--bin * binFrequency, NULL);
1478 }
1479
1480 // PRL: added these two lines with the big TrackPanel refactor
1481 const double maxRatio = findMaxRatio(snappedFrequency, rate);
1482 ratio = std::min(ratio, maxRatio);
1483
1485 (snappedFrequency / ratio, snappedFrequency * ratio);
1486}
1487
1488#if 0
1489// unused
1490void SelectHandle::ResetFreqSelectionPin
1491 (const ViewInfo &viewInfo, double hintFrequency, bool logF)
1492{
1493 switch (mFreqSelMode) {
1494 case FREQ_SEL_INVALID:
1496 mFreqSelPin = -1.0;
1497 break;
1498
1500 mFreqSelPin = viewInfo.selectedRegion.fc();
1501 break;
1502
1504 {
1505 // Re-pin the width
1506 const double f0 = viewInfo.selectedRegion.f0();
1507 const double f1 = viewInfo.selectedRegion.f1();
1508 if (f0 >= 0 && f1 >= 0)
1509 mFreqSelPin = sqrt(f1 / f0);
1510 else
1511 mFreqSelPin = -1.0;
1512 }
1513 break;
1514
1515 case FREQ_SEL_FREE:
1516 // Pin which? Farther from the hint which is the presumed
1517 // mouse position.
1518 {
1519 // If this function finds use again, the following should be
1520 // generalized using NumberScale
1521
1522 const double f0 = viewInfo.selectedRegion.f0();
1523 const double f1 = viewInfo.selectedRegion.f1();
1524 if (logF) {
1525 if (f1 < 0)
1526 mFreqSelPin = f0;
1527 else {
1528 const double logf1 = log(std::max(1.0, f1));
1529 const double logf0 = log(std::max(1.0, f0));
1530 const double logHint = log(std::max(1.0, hintFrequency));
1531 if (std::abs(logHint - logf1) < std::abs(logHint - logf0))
1532 mFreqSelPin = f0;
1533 else
1534 mFreqSelPin = f1;
1535 }
1536 }
1537 else {
1538 if (f1 < 0 ||
1539 std::abs(hintFrequency - f1) < std::abs(hintFrequency - f0))
1540 mFreqSelPin = f0;
1541 else
1542 mFreqSelPin = f1;
1543 }
1544 }
1545 break;
1546
1547 case FREQ_SEL_TOP_FREE:
1548 mFreqSelPin = viewInfo.selectedRegion.f0();
1549 break;
1550
1552 mFreqSelPin = viewInfo.selectedRegion.f1();
1553 break;
1554
1555 default:
1556 wxASSERT(false);
1557 }
1558}
1559#endif
wxT("CloseDown"))
std::shared_ptr< UIHandle > UIHandlePtr
Definition: CellularPanel.h:28
int min(int a, int b)
XO("Cut/Copy/Paste")
#define _(s)
Definition: Internat.h:73
size_t limitSampleBufferSize(size_t bufferSize, sampleCount limit)
Definition: SampleCount.cpp:22
@ fillZero
Definition: SampleFormat.h:60
@ FREQ_SNAP_DISTANCE
@ SELECTION_RESIZE_REGION
std::vector< SnapPoint > SnapPointArray
Definition: Snap.h:43
#define A(N)
Definition: ToChars.cpp:62
static Settings & settings()
Definition: TrackInfo.cpp:87
std::unique_ptr< wxCursor > MakeCursor(int WXUNUSED(CursorId), const char *const pXpm[36], int HotX, int HotY)
Definition: TrackPanel.cpp:186
bool within(A a, B b, DIST d)
Definition: TrackPanel.cpp:167
std::shared_ptr< Subclass > AssignUIHandlePtr(std::weak_ptr< Subclass > &holder, const std::shared_ptr< Subclass > &pNew)
Definition: UIHandle.h:151
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 CommandManager & Get(AudacityProject &project)
NormalizedKeyString GetKeyFromName(const CommandID &name) const
std::shared_ptr< Track > FindTrack()
A LabelTrack is a Track that holds labels (LabelStruct).
Definition: LabelTrack.h:87
double t1() const
Definition: ViewInfo.h:36
double f1() const
Definition: ViewInfo.h:38
bool setTimes(double t0, double t1)
Definition: ViewInfo.cpp:51
bool setF0(double f, bool maySwap=true)
Definition: ViewInfo.cpp:115
double fc() const
Definition: ViewInfo.h:39
bool setFrequencies(double f0, double f1)
Definition: ViewInfo.cpp:105
bool isPoint() const
Definition: ViewInfo.h:40
double f0() const
Definition: ViewInfo.h:37
double t0() const
Definition: ViewInfo.h:35
bool setF1(double f, bool maySwap=true)
Definition: ViewInfo.cpp:125
float PositionToValue(float pp) const
Definition: NumberScale.h:155
float ValueToPosition(float val) const
Definition: NumberScale.h:256
Subscription Subscribe(Callback callback)
Connect a callback to the Publisher; later-connected are called earlier.
Definition: Observer.h:199
A move-only handle representing a connection to a Publisher.
Definition: Observer.h:70
bool IsAudioActive() const
static ProjectAudioIO & Get(AudacityProject &project)
void ModifyState(bool bWantsAutoSave)
static ProjectHistory & Get(AudacityProject &project)
static ProjectSettings & Get(AudacityProject &project)
int GetTool() const
PlaybackScroller & GetPlaybackScroller()
static ProjectWindow & Get(AudacityProject &project)
bool GetFloats(float *buffer, sampleCount start, size_t len, fillFormat fill=fillZero, bool mayThrow=true, sampleCount *pNumWithinClips=nullptr) const
Retrieve samples from a track in floating-point format, regardless of the storage format.
Definition: SampleTrack.h:82
sampleCount TimeToLongSamples(double t0) const
Convert correctly between an (absolute) time in seconds and a number of samples.
Definition: SampleTrack.cpp:43
void OnTimer(Observer::Message)
TimerHandler(SelectHandle *pParent, AudacityProject *pProject)
AudacityProject * mConnectedProject
Observer::Subscription mSubscription
SelectHandle(const SelectHandle &)
bool mAutoScrolling
Definition: SelectHandle.h:167
Result Cancel(AudacityProject *) override
wxRect DrawingArea(TrackPanelDrawingContext &, const wxRect &rect, const wxRect &panelRect, unsigned iPass) override
virtual ~SelectHandle()
bool HasSnap() const
void MoveSnappingFreqSelection(AudacityProject *pProject, ViewInfo &viewInfo, int mouseYCoordinate, int trackTopEdge, int trackHeight, TrackView *pTrackView)
SnapResults mSnapEnd
Definition: SelectHandle.h:136
void HandleCenterFrequencyClick(const ViewInfo &viewInfo, bool shiftDown, const WaveTrack *pTrack, double value)
std::weak_ptr< Track > FindTrack()
std::weak_ptr< TrackView > mpView
Definition: SelectHandle.h:131
std::weak_ptr< const WaveTrack > mFreqSelTrack
Definition: SelectHandle.h:155
Result Release(const TrackPanelMouseEvent &event, AudacityProject *pProject, wxWindow *pParent) override
void Enter(bool forward, AudacityProject *pProject) override
void SetUseSnap(bool use, AudacityProject *pProject)
SnapResults mSnapStart
Definition: SelectHandle.h:136
void AssignSelection(ViewInfo &viewInfo, double selend, Track *pTrack)
HitTestPreview Preview(const TrackPanelMouseState &state, AudacityProject *pProject) override
static void SnapCenterOnce(SpectrumAnalyst &analyst, ViewInfo &viewInfo, const WaveTrack *pTrack, bool up)
bool HasEscape(AudacityProject *pProject) const override
Result Click(const TrackPanelMouseEvent &event, AudacityProject *pProject) override
bool mSelStartValid
Definition: SelectHandle.h:139
std::shared_ptr< TimerHandler > mTimerHandler
Definition: SelectHandle.h:173
std::shared_ptr< SelectionStateChanger > mSelectionStateChanger
Definition: SelectHandle.h:169
Result Drag(const TrackPanelMouseEvent &event, AudacityProject *pProject) override
std::shared_ptr< SpectrumAnalyst > mFrequencySnapper
Definition: SelectHandle.h:163
static void StartSnappingFreqSelection(SpectrumAnalyst &analyst, const ViewInfo &viewInfo, const WaveTrack *pTrack)
bool IsClicked() const
void AdjustSelection(AudacityProject *pProject, ViewInfo &viewInfo, int mouseXCoordinate, int trackLeftEdge, Track *pTrack)
Extend or contract the existing selection.
double mSelStart
Definition: SelectHandle.h:140
bool Escape(AudacityProject *pProject) override
std::shared_ptr< SnapManager > mSnapManager
Definition: SelectHandle.h:135
static UIHandlePtr HitTest(std::weak_ptr< SelectHandle > &holder, const TrackPanelMouseState &state, const AudacityProject *pProject, const std::shared_ptr< TrackView > &pTrackView)
enum SelectHandle::eFreqSelMode FREQ_SEL_INVALID
@ FREQ_SEL_SNAPPING_CENTER
Definition: SelectHandle.h:147
@ FREQ_SEL_PINNED_CENTER
Definition: SelectHandle.h:148
static UIHandle::Result NeedChangeHighlight(const SelectHandle &oldState, const SelectHandle &newState)
void Draw(TrackPanelDrawingContext &context, const wxRect &rect, unsigned iPass) override
void StartFreqSelection(ViewInfo &viewInfo, int mouseYCoordinate, int trackTopEdge, int trackHeight, TrackView *pTrackView)
int mSelectionBoundary
Definition: SelectHandle.h:142
void AdjustFreqSelection(const WaveTrack *wt, ViewInfo &viewInfo, int mouseYCoordinate, int trackTopEdge, int trackHeight)
SelectedRegion mInitialSelection
Definition: SelectHandle.h:133
void Connect(AudacityProject *pProject)
double mFreqSelPin
Definition: SelectHandle.h:162
void StartSelection(AudacityProject *pProject)
Reset our selection markers.
static const int UndefinedFrequency
static void SelectTrackLength(ViewInfo &viewInfo, Track &track, bool syncLocked)
static SelectionState & Get(AudacityProject &project)
Definition: Snap.h:31
void GetBounds(const WaveTrack &wt, float &min, float &max) const
static SpectrogramBounds & Get(WaveTrack &track)
Get either the global default settings, or the track's own if previously created.
static SpectrogramSettings & Get(const WaveTrack &track)
Mutative access to attachment even if the track argument is const.
Used for finding the peaks, for snapping to peaks.
float FindPeak(float xPos, float *pY) const
bool Calculate(Algorithm alg, int windowFunc, size_t windowSize, double rate, const float *data, size_t dataLen, float *pYMin=NULL, float *pYMax=NULL, FreqGauge *progress=NULL)
bool IsSyncLocked() const
Definition: SyncLock.cpp:43
static SyncLockState & Get(AudacityProject &project)
Definition: SyncLock.cpp:26
Track * Get()
Abstract base class for an object holding data associated with points on a time axis.
Definition: Track.h:226
std::shared_ptr< Subclass > SharedPointer()
Definition: Track.h:298
A flat linked list of tracks supporting Add, Remove, Clear, and Contains, serialization of the list o...
Definition: Track.h:1339
std::shared_ptr< Subclass > Lock(const std::weak_ptr< Subclass > &wTrack)
Definition: Track.h:1606
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:487
static wxRect MaximizeHeight(const wxRect &rect, const wxRect &panelRect)
void Refresh(bool eraseBackground=true, const wxRect *rect=(const wxRect *) NULL) override
Definition: TrackPanel.cpp:781
static TrackPanel & Get(AudacityProject &project)
Definition: TrackPanel.cpp:231
virtual bool IsSpectral() const
Definition: TrackView.cpp:136
static TrackView & Get(Track &)
Definition: TrackView.cpp:69
Holds a msgid for the translation catalog; may also bind format arguments.
TranslatableString & Join(TranslatableString arg, const wxString &separator={}) &
Append another translatable string.
Result mChangeHighlight
Definition: UIHandle.h:139
unsigned Result
Definition: UIHandle.h:38
NotifyingSelectedRegion selectedRegion
Definition: ViewInfo.h:219
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:51
WaveClip * GetClipAtTime(double time)
Definition: WaveTrack.cpp:2127
double GetRate() const override
Definition: WaveTrack.cpp:360
double PositionToTime(int64 position, int64 origin=0, bool ignoreFisheye=false) const
Definition: ZoomInfo.cpp:35
int64 TimeToPosition(double time, int64 origin=0, bool ignoreFisheye=false) const
STM: Converts a project time to screen x position.
Definition: ZoomInfo.cpp:45
auto end(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:159
Namespace containing an enum 'what to do on a refresh?'.
Definition: RefreshCode.h:16
void DoListSelection(AudacityProject &project, Track *t, bool shift, bool ctrl, bool modifyState)
double findMaxRatio(double center, double rate)
void SetTipAndCursorForBoundary(SelectionBoundary boundary, bool frequencySnapping, TranslatableString &tip, wxCursor *&pCursor)
wxInt64 FrequencyToPosition(const WaveTrack *wt, double frequency, wxInt64 trackTopEdge, int trackHeight)
Converts a frequency to screen y position.
double PositionToFrequency(const WaveTrack *wt, bool maySnap, wxInt64 mouseYCoordinate, wxInt64 trackTopEdge, int trackHeight)
void SetIfNotNull(T *pValue, const T Value)
SelectionBoundary ChooseTimeBoundary(const double t0, const double t1, const ViewInfo &viewInfo, double selend, bool onlyWithinSnapDistance, wxInt64 *pPixelDist, double *pPinValue)
SelectionBoundary ChooseBoundary(const ViewInfo &viewInfo, wxCoord xx, wxCoord yy, const TrackView *pTrackView, const wxRect &rect, bool mayDragWidth, bool onlyWithinSnapDistance, double *pPinValue=NULL)
bool isSpectralSelectionView(const TrackView *pTrackView)
STL namespace.
wxString Display(bool usesSpecialChars=false) const
Definition: Keyboard.cpp:56
Default message type for Publisher.
Definition: Observer.h:26
bool snappedTime
Definition: Snap.h:50
double outTime
Definition: Snap.h:47
double timeSnappedTime
Definition: Snap.h:46
wxInt64 outCoord
Definition: Snap.h:48
bool Snapped() const
Definition: Snap.h:52
bool snappedPoint
Definition: Snap.h:49
std::shared_ptr< TrackPanelCell > pCell