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