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