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