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