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  }
415 
416  return result;
417 }
418 
420 (const SelectHandle &oldState, const SelectHandle &newState)
421 {
422  auto useSnap = oldState.mUseSnap;
423  // This is guaranteed when constructing the NEW handle:
424  wxASSERT( useSnap == newState.mUseSnap );
425  if (!useSnap)
426  return 0;
427 
428  auto &oldSnapState = oldState.mSnapStart;
429  auto &newSnapState = newState.mSnapStart;
430  if ( oldSnapState.Snapped() == newSnapState.Snapped() &&
431  (!oldSnapState.Snapped() ||
432  oldSnapState.outCoord == newSnapState.outCoord) )
433  return 0;
434 
436 }
437 
439 ( const std::shared_ptr<Track> &pTrack, bool useSnap,
440  const TrackList &trackList,
441  const TrackPanelMouseState &st, const ViewInfo &viewInfo )
442  : mpTrack{ pTrack }
443  , mSnapManager{ std::make_shared<SnapManager>(&trackList, &viewInfo) }
444 {
445  const wxMouseState &state = st.state;
446  mRect = st.rect;
447 
448  auto time = std::max(0.0, viewInfo.PositionToTime(state.m_x, mRect.x));
449  mSnapStart = mSnapManager->Snap(pTrack.get(), time, false);
450  if (mSnapStart.snappedPoint)
451  mSnapStart.outCoord += mRect.x;
452  else
453  mSnapStart.outCoord = -1;
454 
455  mUseSnap = useSnap;
456 }
457 
459 {
460 }
461 
462 namespace {
463  // Is the distance between A and B less than D?
464  template < class A, class B, class DIST > bool within(A a, B b, DIST d)
465  {
466  return (a > b - d) && (a < b + d);
467  }
468 
469  inline double findMaxRatio(double center, double rate)
470  {
471  const double minFrequency = 1.0;
472  const double maxFrequency = (rate / 2.0);
473  const double frequency =
474  std::min(maxFrequency,
475  std::max(minFrequency, center));
476  return
477  std::min(frequency / minFrequency, maxFrequency / frequency);
478  }
479 }
480 
482 {
483  SetUseSnap(true);
484 }
485 
487 {
488  mUseSnap = use;
489 
490  bool hasSnap = HasSnap();
491  if (hasSnap)
492  // Repaint to turn the snap lines on or off
494 
495  if (IsClicked()) {
496  // Readjust the moving selection end
498  ::GetActiveProject()->GetViewInfo(),
500  nullptr);
502  }
503 }
504 
506 {
507  return
508  (IsClicked() ? mSnapEnd : mSnapStart).snappedPoint;
509 }
510 
512 {
513  return HasSnap() && mUseSnap;
514 }
515 
517 {
518  if (SelectHandle::HasEscape()) {
519  SetUseSnap(false);
520  return true;
521  }
522  return false;
523 }
524 
526 (const TrackPanelMouseEvent &evt, AudacityProject *pProject)
527 {
530 
531  using namespace RefreshCode;
532 
533  wxMouseEvent &event = evt.event;
534  const auto sTrack = pProject->GetTracks()->Lock(mpTrack);
535  const auto pTrack = sTrack.get();
536  ViewInfo &viewInfo = pProject->GetViewInfo();
537 
538  mMostRecentX = event.m_x;
539  mMostRecentY = event.m_y;
540 
541  TrackPanel *const trackPanel = pProject->GetTrackPanel();
542 
543  if( pTrack->GetKind() == Track::Label &&
544  event.LeftDown() &&
545  event.ControlDown() ){
546  // We should reach this, only in default of other hits on glyphs or
547  // text boxes.
548  bool bShift = event.ShiftDown();
549  bool unsafe = pProject->IsAudioActive();
550  pProject->HandleListSelection(pTrack, bShift, true, !unsafe);
551  // Do not start a drag
552  return RefreshAll | Cancelled;
553  }
554 
555  auto &selectionState = pProject->GetSelectionState();
556  if (event.LeftDClick() && !event.ShiftDown()) {
557  TrackList *const trackList = pProject->GetTracks();
558 
559  // Deselect all other tracks and select this one.
560  selectionState.SelectNone( *trackList, pProject->GetMixerBoard() );
561 
562  selectionState.SelectTrack
563  ( *trackList, *pTrack, true, true, pProject->GetMixerBoard() );
564 
565  // Default behavior: select whole track
567  ( *trackList, viewInfo, *pTrack, pProject->IsSyncLocked() );
568 
569  // Special case: if we're over a clip in a WaveTrack,
570  // select just that clip
571  if (pTrack->GetKind() == Track::Wave) {
572  WaveTrack *const wt = static_cast<WaveTrack *>(pTrack);
573  WaveClip *const selectedClip = wt->GetClipAtX(event.m_x);
574  if (selectedClip) {
575  viewInfo.selectedRegion.setTimes(
576  selectedClip->GetOffset(), selectedClip->GetEndTime());
577  }
578  }
579 
580  pProject->ModifyState(false);
581 
582  // Do not start a drag
584  }
585  else if (!event.LeftDown())
586  return Cancelled;
587 
588  mInitialSelection = viewInfo.selectedRegion;
589 
590  TrackList *const trackList = pProject->GetTracks();
591  mSelectionStateChanger = std::make_shared< SelectionStateChanger >
592  ( selectionState, *trackList );
593 
594  mSelectionBoundary = 0;
595 
596  bool bShiftDown = event.ShiftDown();
597  bool bCtrlDown = event.ControlDown();
598 
599  auto pMixerBoard = pProject->GetMixerBoard();
600 
601  mSelStart = mUseSnap ? mSnapStart.outTime : mSnapStart.timeSnappedTime;
602  auto xx = viewInfo.TimeToPosition(mSelStart, mRect.x);
603 
604  // I. Shift-click adjusts an existing selection
605  if (bShiftDown || bCtrlDown) {
606  if (bShiftDown)
607  selectionState.ChangeSelectionOnShiftClick
608  ( *trackList, *pTrack, pMixerBoard );
609  if( bCtrlDown ){
610  //Commented out bIsSelected toggles, as in Track Control Panel.
611  //bool bIsSelected = pTrack->GetSelected();
612  //Actual bIsSelected will always add.
613  bool bIsSelected = false;
614  // Don't toggle away the last selected track.
615  if( !bIsSelected || trackPanel->GetSelectedTrackCount() > 1 )
616  selectionState.SelectTrack
617  ( *trackList, *pTrack, !bIsSelected, true, pMixerBoard );
618  }
619 
620  double value;
621  // Shift-click, choose closest boundary
622  SelectionBoundary boundary =
623  ChooseBoundary(viewInfo, xx, event.m_y, pTrack, mRect, false, false, &value);
624  mSelectionBoundary = boundary;
625  switch (boundary) {
626  case SBLeft:
627  case SBRight:
628  {
629 #ifdef EXPERIMENTAL_SPECTRAL_EDITING
630  // If drag starts, change time selection only
631  // (also exit frequency snapping)
632  mFreqSelMode = FREQ_SEL_INVALID;
633 #endif
634  mSelStartValid = true;
635  mSelStart = value;
636  mSnapStart = SnapResults{};
637  AdjustSelection(pProject, viewInfo, event.m_x, mRect.x, pTrack);
638  break;
639  }
640 #ifdef EXPERIMENTAL_SPECTRAL_EDITING
641  case SBBottom:
642  case SBTop:
643  {
644  mFreqSelTrack = Track::Pointer<const WaveTrack>( pTrack );
645  mFreqSelPin = value;
646  mFreqSelMode =
647  (boundary == SBBottom)
648  ? FREQ_SEL_BOTTOM_FREE : FREQ_SEL_TOP_FREE;
649 
650  // Drag frequency only, not time:
651  mSelStartValid = false;
652  AdjustFreqSelection(
653  static_cast<WaveTrack*>(pTrack),
654  viewInfo, event.m_y, mRect.y, mRect.height);
655  break;
656  }
657  case SBCenter:
658  {
659  const auto wt = static_cast<const WaveTrack*>(pTrack);
660  HandleCenterFrequencyClick(viewInfo, true, wt, value);
661  break;
662  }
663 #endif
664  default:
665  wxASSERT(false);
666  };
667 
668  // For persistence of the selection change:
669  pProject->ModifyState(false);
670 
671  // Get timer events so we can auto-scroll
672  Connect(pProject);
673 
674  // Full refresh since the label area may need to indicate
675  // newly selected tracks.
676  return RefreshAll | UpdateSelection;
677  }
678 
679  // II. Unmodified click starts a NEW selection
680 
681  //Make sure you are within the selected track
682  bool startNewSelection = true;
683  if (pTrack && pTrack->GetSelected()) {
684  // Adjusting selection edges can be turned off in the
685  // preferences now
686  if (viewInfo.bAdjustSelectionEdges) {
687 #ifdef EXPERIMENTAL_SPECTRAL_EDITING
688  if (mFreqSelMode == FREQ_SEL_SNAPPING_CENTER &&
689  isSpectralSelectionTrack(pTrack)) {
690  // This code is no longer reachable, but it had a place in the
691  // spectral selection prototype. It used to be that you could be
692  // in a center-frequency-snapping mode that was not a mouse drag
693  // but responded to mouse movements. Click exited that and dragged
694  // width instead. PRL.
695 
696  // Ignore whether we are inside the time selection.
697  // Exit center-snapping, start dragging the width.
698  mFreqSelMode = FREQ_SEL_PINNED_CENTER;
699  mFreqSelTrack = Track::Pointer<const WaveTrack>( pTrack );
700  mFreqSelPin = viewInfo.selectedRegion.fc();
701  // Do not adjust time boundaries
702  mSelStartValid = false;
703  AdjustFreqSelection(
704  static_cast<WaveTrack*>(pTrack),
705  viewInfo, event.m_y, mRect.y, mRect.height);
706  // For persistence of the selection change:
707  pProject->ModifyState(false);
708  mSelectionBoundary = SBWidth;
709  return UpdateSelection;
710  }
711  else
712 #endif
713  {
714  // Not shift-down, choose boundary only within snapping
715  double value;
716  SelectionBoundary boundary =
717  ChooseBoundary(viewInfo, xx, event.m_y, pTrack, mRect, true, true, &value);
718  mSelectionBoundary = boundary;
719  switch (boundary) {
720  case SBNone:
721  // startNewSelection remains true
722  break;
723  case SBLeft:
724  case SBRight:
725  startNewSelection = false;
726 #ifdef EXPERIMENTAL_SPECTRAL_EDITING
727  // Disable frequency selection
728  mFreqSelMode = FREQ_SEL_INVALID;
729 #endif
730  mSelStartValid = true;
731  mSelStart = value;
732  mSnapStart = SnapResults{};
733  break;
734 #ifdef EXPERIMENTAL_SPECTRAL_EDITING
735  case SBBottom:
736  case SBTop:
737  case SBWidth:
738  startNewSelection = false;
739  // Disable time selection
740  mSelStartValid = false;
741  mFreqSelTrack = Track::Pointer<const WaveTrack>( pTrack );
742  mFreqSelPin = value;
743  mFreqSelMode =
744  (boundary == SBWidth) ? FREQ_SEL_PINNED_CENTER :
745  (boundary == SBBottom) ? FREQ_SEL_BOTTOM_FREE :
746  FREQ_SEL_TOP_FREE;
747  break;
748  case SBCenter:
749  {
750  const auto wt = static_cast<const WaveTrack*>(pTrack);
751  HandleCenterFrequencyClick(viewInfo, false, wt, value);
752  startNewSelection = false;
753  break;
754  }
755 #endif
756  default:
757  wxASSERT(false);
758  }
759  }
760  } // bAdjustSelectionEdges
761  }
762 
763  // III. Common case for starting a NEW selection
764 
765  if (startNewSelection) {
766  // If we didn't move a selection boundary, start a NEW selection
767  selectionState.SelectNone( *trackList, pMixerBoard );
768 #ifdef EXPERIMENTAL_SPECTRAL_EDITING
769  StartFreqSelection (viewInfo, event.m_y, mRect.y, mRect.height, pTrack);
770 #endif
771  StartSelection(pProject);
772  selectionState.SelectTrack
773  ( *trackList, *pTrack, true, true, pMixerBoard );
774  trackPanel->SetFocusedTrack(pTrack);
775  //On-Demand: check to see if there is an OD thing associated with this track.
776  if (pTrack->GetKind() == Track::Wave) {
779  (static_cast<WaveTrack*>(pTrack),mSelStart);
780  }
781 
782  Connect(pProject);
783  return RefreshAll | UpdateSelection;
784  }
785  else {
786  Connect(pProject);
787  return RefreshAll;
788  }
789 }
790 
792 (const TrackPanelMouseEvent &evt, AudacityProject *pProject)
793 {
794  using namespace RefreshCode;
795 
796  ViewInfo &viewInfo = pProject->GetViewInfo();
797  const wxMouseEvent &event = evt.event;
798 
799  int x = mAutoScrolling ? mMostRecentX : event.m_x;
800  int y = mAutoScrolling ? mMostRecentY : event.m_y;
801  mMostRecentX = x;
802  mMostRecentY = y;
803 
807 
808  // Fuhggeddaboudit if we're not dragging and not autoscrolling.
809  if (!event.Dragging() && !mAutoScrolling)
810  return RefreshNone;
811 
812  if (event.CmdDown()) {
813  // Ctrl-drag has no meaning, fuhggeddaboudit
814  // JKC YES it has meaning.
815  //return RefreshNone;
816  }
817 
818  // Also fuhggeddaboudit if not in a track.
819  auto pTrack = pProject->GetTracks()->Lock(mpTrack);
820  if (!pTrack)
821  return RefreshNone;
822 
823  // JKC: Logic to prevent a selection smaller than 5 pixels to
824  // prevent accidental dragging when selecting.
825  // (if user really wants a tiny selection, they should zoom in).
826  // Can someone make this value of '5' configurable in
827  // preferences?
828  enum { minimumSizedSelection = 5 }; //measured in pixels
829 
830  // Might be dragging frequency bounds only, test
831  if (mSelStartValid) {
832  wxInt64 SelStart = viewInfo.TimeToPosition(mSelStart, mRect.x); //cvt time to pixels.
833  // Abandon this drag if selecting < 5 pixels.
834  if (wxLongLong(SelStart - x).Abs() < minimumSizedSelection)
835  return RefreshNone;
836  }
837 
838  if ( auto clickedTrack =
839  static_cast<CommonTrackPanelCell*>(evt.pCell.get())->FindTrack() ) {
840  // Handle which tracks are selected
841  Track *sTrack = pTrack.get();
842  Track *eTrack = clickedTrack.get();
843  auto trackList = pProject->GetTracks();
844  auto pMixerBoard = pProject->GetMixerBoard();
845  if ( sTrack && eTrack && !event.ControlDown() ) {
846  auto &selectionState = pProject->GetSelectionState();
847  selectionState.SelectRangeOfTracks
848  ( *trackList, *sTrack, *eTrack, pMixerBoard );
849  }
850 
851 #ifdef EXPERIMENTAL_SPECTRAL_EDITING
852 #ifndef SPECTRAL_EDITING_ESC_KEY
853  if (mFreqSelMode == FREQ_SEL_SNAPPING_CENTER &&
854  !viewInfo.selectedRegion.isPoint())
855  MoveSnappingFreqSelection
856  (pProject, viewInfo, y, mRect.y, mRect.height, pTrack.get());
857  else
858 #endif
859  if (pProject->GetTracks()->Lock(mFreqSelTrack) == pTrack)
860  AdjustFreqSelection(
861  static_cast<WaveTrack*>(pTrack.get()),
862  viewInfo, y, mRect.y, mRect.height);
863 #endif
864 
865  AdjustSelection(pProject, viewInfo, x, mRect.x, clickedTrack.get());
866  }
867 
868  return RefreshNone
869 
870  // If scrubbing does not use the helper poller thread, then
871  // don't refresh at every mouse event, because it slows down seek-scrub.
872  // Instead, let OnTimer do it, which is often enough.
873  // And even if scrubbing does use the thread, then skipping refresh does not
874  // bring that advantage, but it is probably still a good idea anyway.
875 
876  // | UpdateSelection
877 
878  ;
879 }
880 
882 (const TrackPanelMouseState &st, const AudacityProject *pProject)
883 {
884  if (!HasSnap() && !mUseSnap)
885  // Moved out of snapping; revert to un-escaped state
886  mUseSnap = true;
887 
888  auto pTrack = mpTrack.lock();
889  if (!pTrack)
890  return {};
891 
892  wxString tip;
893  wxCursor *pCursor = SelectCursor();
894  if ( IsClicked() )
895  // Use same cursor as at the clck
896  SetTipAndCursorForBoundary
897  (SelectionBoundary(mSelectionBoundary),
898  (mFreqSelMode == FREQ_SEL_SNAPPING_CENTER),
899  tip, pCursor);
900  else {
901  // Choose one of many cursors for mouse-over
902 
903  const ViewInfo &viewInfo = pProject->GetViewInfo();
904 
905  auto &state = st.state;
906  auto time = mUseSnap ? mSnapStart.outTime : mSnapStart.timeSnappedTime;
907  auto xx = viewInfo.TimeToPosition(time, mRect.x);
908 
909  const bool bMultiToolMode =
910  pProject->GetToolsToolBar()->IsDown(multiTool);
911 
912  //In Multi-tool mode, give multitool prompt if no-special-hit.
913  if (bMultiToolMode) {
914  // Look up the current key binding for Preferences.
915  // (Don't assume it's the default!)
916  wxString keyStr
917  (pProject->GetCommandManager()->GetKeyFromName(wxT("Preferences")));
918  if (keyStr.IsEmpty())
919  // No keyboard preference defined for opening Preferences dialog
920  /* i18n-hint: These are the names of a menu and a command in that menu */
921  keyStr = _("Edit, Preferences...");
922  else
923  keyStr = KeyStringDisplay(keyStr);
924  /* i18n-hint: %s is usually replaced by "Ctrl+P" for Windows/Linux, "Command+," for Mac */
925  tip = wxString::Format(
926  _("Multi-Tool Mode: %s for Mouse and Keyboard Preferences."),
927  keyStr);
928  // Later in this function we may point to some other string instead.
929  if (!pTrack->GetSelected() ||
930  !viewInfo.bAdjustSelectionEdges)
931  ;
932  else {
933  const wxRect &rect = st.rect;
934  const bool bShiftDown = state.ShiftDown();
935  const bool bCtrlDown = state.ControlDown();
936  const bool bModifierDown = bShiftDown || bCtrlDown;
937 
938  // If not shift-down and not snapping center, then
939  // choose boundaries only in snapping tolerance,
940  // and may choose center.
941  SelectionBoundary boundary =
942  ChooseBoundary(viewInfo, xx, state.m_y, pTrack.get(), rect, !bModifierDown, !bModifierDown);
943 
944  SetTipAndCursorForBoundary(boundary, !bShiftDown, tip, pCursor);
945  }
946  }
947 
948 #if 0
949  // This is a vestige of an idea in the prototype version.
950  // Center would snap without mouse button down, click would pin the center
951  // and drag width.
952 #ifdef EXPERIMENTAL_SPECTRAL_EDITING
953  if ((mFreqSelMode == FREQ_SEL_SNAPPING_CENTER) &&
954  isSpectralSelectionTrack(pTrack)) {
955  // Not shift-down, but center frequency snapping toggle is on
956  tip = _("Click and drag to set frequency bandwidth.");
957  pCursor = &*envelopeCursor;
958  return {};
959  }
960 #endif
961 #endif
962 
963  if (!pTrack->GetSelected() || !viewInfo.bAdjustSelectionEdges)
964  ;
965  else {
966  const wxRect &rect = st.rect;
967  const bool bShiftDown = state.ShiftDown();
968  const bool bCtrlDown = state.ControlDown();
969  const bool bModifierDown = bShiftDown || bCtrlDown;
970  SelectionBoundary boundary = ChooseBoundary(
971  viewInfo, xx, state.m_y, pTrack.get(), rect, !bModifierDown, !bModifierDown);
972  SetTipAndCursorForBoundary(boundary, !bShiftDown, tip, pCursor);
973  }
974 
975  MaySetOnDemandTip(pTrack.get(), tip);
976  }
977  if (tip == "") {
978  tip = _("Click and drag to select audio");
979  }
980  if (HasEscape() && mUseSnap) {
981  tip += wxT(" ") +
982 /* i18n-hint: "Snapping" means automatic alignment of selection edges to any nearby label or clip boundaries */
983  _("(snapping)");
984  }
985  return { tip, pCursor };
986 }
987 
990  wxWindow *)
991 {
992  using namespace RefreshCode;
993  pProject->ModifyState(false);
994  mFrequencySnapper.reset();
995  mSnapManager.reset();
996  if (mSelectionStateChanger) {
997  mSelectionStateChanger->Commit();
998  mSelectionStateChanger.reset();
999  }
1000 
1001  if (mUseSnap && (mSnapStart.outCoord != -1 || mSnapEnd.outCoord != -1))
1002  return RefreshAll;
1003  else
1004  return RefreshNone;
1005 }
1006 
1008 {
1009  mSelectionStateChanger.reset();
1011 
1012  // Refresh mixer board for change of set of selected tracks
1013  if (MixerBoard* pMixerBoard = pProject->GetMixerBoard())
1014  pMixerBoard->Refresh();
1015 
1016  return RefreshCode::RefreshAll;
1017 }
1018 
1020 (DrawingPass pass, wxDC * dc, const wxRegion &, const wxRect &)
1021 {
1022  if (pass == Panel) {
1023  // Draw snap guidelines if we have any
1024  if ( mSnapManager ) {
1025  auto coord1 = (mUseSnap || IsClicked()) ? mSnapStart.outCoord : -1;
1026  auto coord2 = (!mUseSnap || !IsClicked()) ? -1 : mSnapEnd.outCoord;
1027  mSnapManager->Draw( dc, coord1, coord2 );
1028  }
1029  }
1030 }
1031 
1033 {
1034  mTimerHandler = std::make_shared<TimerHandler>( this, pProject );
1035 }
1036 
1037 class SelectHandle::TimerHandler : public wxEvtHandler
1038 {
1039 public:
1041  : mParent{ pParent }
1042  , mConnectedProject{ pProject }
1043  {
1044  if (mConnectedProject)
1045  mConnectedProject->Bind(EVT_TRACK_PANEL_TIMER,
1047  this);
1048  }
1049 
1050  // Receives timer event notifications, to implement auto-scroll
1051  void OnTimer(wxCommandEvent &event);
1052 
1053 private:
1056 };
1057 
1058 void SelectHandle::TimerHandler::OnTimer(wxCommandEvent &event)
1059 {
1060  event.Skip();
1061 
1062  // AS: If the user is dragging the mouse and there is a track that
1063  // has captured the mouse, then scroll the screen, as necessary.
1064 
1066 
1067  // DM: If we're "autoscrolling" (which means that we're scrolling
1068  // because the user dragged from inside to outside the window,
1069  // not because the user clicked in the scroll bar), then
1070  // the selection code needs to be handled slightly differently.
1071  // We set this flag ("mAutoScrolling") to tell the selecting
1072  // code that we didn't get here as a result of a mouse event,
1073  // and therefore it should ignore the event,
1074  // and instead use the last known mouse position. Setting
1075  // this flag also causes the Mac to redraw immediately rather
1076  // than waiting for the next update event; this makes scrolling
1077  // smoother on MacOS 9.
1078 
1079  const auto project = mConnectedProject;
1080  const auto trackPanel = project->GetTrackPanel();
1081  if (mParent->mMostRecentX >= mParent->mRect.x + mParent->mRect.width) {
1082  mParent->mAutoScrolling = true;
1083  project->TP_ScrollRight();
1084  }
1085  else if (mParent->mMostRecentX < mParent->mRect.x) {
1086  mParent->mAutoScrolling = true;
1087  project->TP_ScrollLeft();
1088  }
1089  else {
1090  // Bug1387: enable autoscroll during drag, if the pointer is at either
1091  // extreme x coordinate of the screen, even if that is still within the
1092  // track area.
1093 
1094  int xx = mParent->mMostRecentX, yy = 0;
1095  trackPanel->ClientToScreen(&xx, &yy);
1096  if (xx == 0) {
1097  mParent->mAutoScrolling = true;
1098  project->TP_ScrollLeft();
1099  }
1100  else {
1101  int width, height;
1102  ::wxDisplaySize(&width, &height);
1103  if (xx == width - 1) {
1104  mParent->mAutoScrolling = true;
1105  project->TP_ScrollRight();
1106  }
1107  }
1108  }
1109 
1110  auto pTrack = mParent->mpTrack.lock(); // TrackList::Lock() ?
1111  if (mParent->mAutoScrolling && pTrack) {
1112  // AS: To keep the selection working properly as we scroll,
1113  // we fake a mouse event (remember, this method is called
1114  // from a timer tick).
1115 
1116  // AS: For some reason, GCC won't let us pass this directly.
1117  wxMouseEvent evt(wxEVT_MOTION);
1118  const auto size = trackPanel->GetSize();
1119  mParent->Drag(TrackPanelMouseEvent{ evt, mParent->mRect, size, pTrack }, project);
1120  mParent->mAutoScrolling = false;
1122  }
1123 }
1124 
1127 {
1128  ViewInfo &viewInfo = pProject->GetViewInfo();
1129  mSelStartValid = true;
1130 
1132 
1133  // PRL: commented out the Sonify stuff with the TrackPanel refactor.
1134  // It was no-op anyway.
1135  //SonifyBeginModifyState();
1136  pProject->ModifyState(false);
1137  //SonifyEndModifyState();
1138 }
1139 
1142 (AudacityProject *pProject,
1143  ViewInfo &viewInfo, int mouseXCoordinate, int trackLeftEdge,
1144  Track *track)
1145 {
1146  if (!mSelStartValid)
1147  // Must be dragging frequency bounds only.
1148  return;
1149 
1150  double selend =
1151  std::max(0.0, viewInfo.PositionToTime(mouseXCoordinate, trackLeftEdge));
1152  double origSelend = selend;
1153 
1154  auto pTrack = Track::Pointer( track );
1155  if (!pTrack)
1156  pTrack = pProject->GetTracks()->Lock(mpTrack);
1157 
1158  if (pTrack && mSnapManager.get()) {
1159  bool rightEdge = (selend > mSelStart);
1160  mSnapEnd = mSnapManager->Snap(pTrack.get(), selend, rightEdge);
1161  if (mSnapEnd.Snapped()) {
1162  if (mUseSnap)
1163  selend = mSnapEnd.outTime;
1164  if (mSnapEnd.snappedPoint)
1165  mSnapEnd.outCoord += trackLeftEdge;
1166  }
1167  if (!mSnapEnd.snappedPoint)
1168  mSnapEnd.outCoord = -1;
1169 
1170  // Check if selection endpoints are too close together to snap (unless
1171  // using snap-to-time -- then we always accept the snap results)
1172  if (mSnapStart.outCoord >= 0 &&
1173  mSnapEnd.outCoord >= 0 &&
1174  std::abs(mSnapStart.outCoord - mSnapEnd.outCoord) < 3) {
1175  if(!mSnapEnd.snappedTime)
1176  selend = origSelend;
1177  mSnapEnd.outCoord = -1;
1178  }
1179  }
1180  AssignSelection(viewInfo, selend, pTrack.get());
1181 }
1182 
1184 (ViewInfo &viewInfo, double selend, Track *pTrack)
1185 {
1186  double sel0, sel1;
1187  if (mSelStart < selend) {
1188  sel0 = mSelStart;
1189  sel1 = selend;
1190  }
1191  else {
1192  sel1 = mSelStart;
1193  sel0 = selend;
1194  }
1195 
1196  viewInfo.selectedRegion.setTimes(sel0, sel1);
1197 
1198  //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.
1199  if (pTrack && (pTrack->GetKind() == Track::Wave) && ODManager::IsInstanceCreated())
1201  (static_cast<WaveTrack*>(pTrack),sel0); //sel0 is sometimes less than mSelStart
1202 }
1203 
1205  int mouseYCoordinate, int trackTopEdge,
1206  int trackHeight, Track *pTrack)
1207 {
1208  mFreqSelTrack.reset();
1209  mFreqSelMode = FREQ_SEL_INVALID;
1211 
1212  if (isSpectralSelectionTrack(pTrack)) {
1213  // Spectral selection track is always wave
1214  auto shTrack = Track::Pointer<const WaveTrack>( pTrack );
1215  mFreqSelTrack = shTrack;
1216  mFreqSelMode = FREQ_SEL_FREE;
1217  mFreqSelPin =
1218  PositionToFrequency(shTrack.get(), false, mouseYCoordinate,
1219  trackTopEdge, trackHeight);
1221  }
1222 }
1223 
1225  const WaveTrack *wt, ViewInfo &viewInfo,
1226  int mouseYCoordinate, int trackTopEdge,
1227  int trackHeight)
1228 {
1229  if (mFreqSelMode == FREQ_SEL_INVALID ||
1230  mFreqSelMode == FREQ_SEL_SNAPPING_CENTER)
1231  return;
1232 
1233  // Extension happens only when dragging in the same track in which we
1234  // started, and that is of a spectrogram display type.
1235 
1236  const double rate = wt->GetRate();
1237  const double frequency =
1238  PositionToFrequency(wt, true, mouseYCoordinate,
1239  trackTopEdge, trackHeight);
1240 
1241  // Dragging center?
1242  if (mFreqSelMode == FREQ_SEL_DRAG_CENTER) {
1243  if (frequency == rate || frequency < 1.0)
1244  // snapped to top or bottom
1245  viewInfo.selectedRegion.setFrequencies(
1248  else {
1249  // mFreqSelPin holds the ratio of top to center
1250  const double maxRatio = findMaxRatio(frequency, rate);
1251  const double ratio = std::min(maxRatio, mFreqSelPin);
1252  viewInfo.selectedRegion.setFrequencies(
1253  frequency / ratio, frequency * ratio);
1254  }
1255  }
1256  else if (mFreqSelMode == FREQ_SEL_PINNED_CENTER) {
1257  if (mFreqSelPin >= 0) {
1258  // Change both upper and lower edges leaving centre where it is.
1259  if (frequency == rate || frequency < 1.0)
1260  // snapped to top or bottom
1261  viewInfo.selectedRegion.setFrequencies(
1264  else {
1265  // Given center and mouse position, find ratio of the larger to the
1266  // smaller, limit that to the frequency scale bounds, and adjust
1267  // top and bottom accordingly.
1268  const double maxRatio = findMaxRatio(mFreqSelPin, rate);
1269  double ratio = frequency / mFreqSelPin;
1270  if (ratio < 1.0)
1271  ratio = 1.0 / ratio;
1272  ratio = std::min(maxRatio, ratio);
1273  viewInfo.selectedRegion.setFrequencies(
1274  mFreqSelPin / ratio, mFreqSelPin * ratio);
1275  }
1276  }
1277  }
1278  else {
1279  // Dragging of upper or lower.
1280  const bool bottomDefined =
1281  !(mFreqSelMode == FREQ_SEL_TOP_FREE && mFreqSelPin < 0);
1282  const bool topDefined =
1283  !(mFreqSelMode == FREQ_SEL_BOTTOM_FREE && mFreqSelPin < 0);
1284  if (!bottomDefined || (topDefined && mFreqSelPin < frequency)) {
1285  // Adjust top
1286  if (frequency == rate)
1287  // snapped high; upper frequency is undefined
1289  else
1290  viewInfo.selectedRegion.setF1(std::max(1.0, frequency));
1291 
1292  viewInfo.selectedRegion.setF0(mFreqSelPin);
1293  }
1294  else {
1295  // Adjust bottom
1296  if (frequency < 1.0)
1297  // snapped low; lower frequency is undefined
1299  else
1300  viewInfo.selectedRegion.setF0(std::min(rate / 2.0, frequency));
1301 
1302  viewInfo.selectedRegion.setF1(mFreqSelPin);
1303  }
1304  }
1305 }
1306 
1308 (const ViewInfo &viewInfo, bool shiftDown, const WaveTrack *pTrack, double value)
1309 {
1310  if (shiftDown) {
1311  // Disable time selection
1312  mSelStartValid = false;
1313  mFreqSelTrack = Track::Pointer<const WaveTrack>( pTrack );
1314  mFreqSelPin = value;
1315  mFreqSelMode = FREQ_SEL_DRAG_CENTER;
1316  }
1317  else {
1318 #ifndef SPECTRAL_EDITING_ESC_KEY
1319  // Start center snapping
1320  // Turn center snapping on (the only way to do this)
1321  mFreqSelMode = FREQ_SEL_SNAPPING_CENTER;
1322  // Disable time selection
1323  mSelStartValid = false;
1324  mFrequencySnapper = std::make_shared<SpectrumAnalyst>();
1325  StartSnappingFreqSelection(*mFrequencySnapper, viewInfo, pTrack);
1326 #endif
1327  }
1328 }
1329 
1331  (SpectrumAnalyst &analyst,
1332  const ViewInfo &viewInfo, const WaveTrack *pTrack)
1333 {
1334  static const size_t minLength = 8;
1335 
1336  const double rate = pTrack->GetRate();
1337 
1338  // Grab samples, just for this track, at these times
1339  std::vector<float> frequencySnappingData;
1340  const auto start =
1341  pTrack->TimeToLongSamples(viewInfo.selectedRegion.t0());
1342  const auto end =
1343  pTrack->TimeToLongSamples(viewInfo.selectedRegion.t1());
1344  const auto length =
1345  std::min(frequencySnappingData.max_size(),
1346  limitSampleBufferSize(10485760, // as in FreqWindow.cpp
1347  end - start));
1348  const auto effectiveLength = std::max(minLength, length);
1349  frequencySnappingData.resize(effectiveLength, 0.0f);
1350  pTrack->Get(
1351  reinterpret_cast<samplePtr>(&frequencySnappingData[0]),
1352  floatSample, start, length, fillZero,
1353  // Don't try to cope with exceptions, just read zeroes instead.
1354  false);
1355 
1356  // Use same settings as are now used for spectrogram display,
1357  // except, shrink the window as needed so we get some answers
1358 
1359  const SpectrogramSettings &settings = pTrack->GetSpectrogramSettings();
1360  auto windowSize = settings.GetFFTLength();
1361 
1362  while(windowSize > effectiveLength)
1363  windowSize >>= 1;
1364  const int windowType = settings.windowType;
1365 
1366  analyst.Calculate(
1367  SpectrumAnalyst::Spectrum, windowType, windowSize, rate,
1368  &frequencySnappingData[0], length);
1369 
1370  // We can now throw away the sample data but we keep the spectrum.
1371 }
1372 
1374  (AudacityProject *pProject, ViewInfo &viewInfo, int mouseYCoordinate,
1375  int trackTopEdge,
1376  int trackHeight, Track *pTrack)
1377 {
1378  if (pTrack &&
1379  pTrack->GetSelected() &&
1380  isSpectralSelectionTrack(pTrack)) {
1381  // Spectral selection track is always wave
1382  WaveTrack *const wt = static_cast<WaveTrack*>(pTrack);
1383  // PRL:
1384  // What would happen if center snapping selection began in one spectrogram track,
1385  // then continues inside another? We do not then recalculate
1386  // the spectrum (as was done in StartSnappingFreqSelection)
1387  // but snap according to the peaks in the old track.
1388 
1389  // But if we always supply the original clicked track here that doesn't matter.
1390  const double rate = wt->GetRate();
1391  const double frequency =
1392  PositionToFrequency(wt, false, mouseYCoordinate,
1393  trackTopEdge, trackHeight);
1394  const double snappedFrequency =
1395  mFrequencySnapper->FindPeak(frequency, NULL);
1396  const double maxRatio = findMaxRatio(snappedFrequency, rate);
1397  double ratio = 2.0; // An arbitrary octave on each side, at most
1398  {
1399  const double f0 = viewInfo.selectedRegion.f0();
1400  const double f1 = viewInfo.selectedRegion.f1();
1401  if (f1 >= f0 && f0 >= 0)
1402  // Preserve already chosen ratio instead
1403  ratio = sqrt(f1 / f0);
1404  }
1405  ratio = std::min(ratio, maxRatio);
1406 
1407  mFreqSelPin = snappedFrequency;
1408  viewInfo.selectedRegion.setFrequencies(
1409  snappedFrequency / ratio, snappedFrequency * ratio);
1410 
1411  // A change here would affect what AdjustFreqSelection() does
1412  // in the prototype version where you switch from moving center to
1413  // dragging width with a click. No effect now.
1414  mFreqSelTrack = Track::Pointer<const WaveTrack>( wt );
1415 
1416  // SelectNone();
1417  // SelectTrack(pTrack, true);
1418  pProject->GetTrackPanel()->SetFocusedTrack(pTrack);
1419  }
1420 }
1421 
1423  (SpectrumAnalyst &analyst,
1424  ViewInfo &viewInfo, const WaveTrack *pTrack, bool up)
1425 {
1426  const SpectrogramSettings &settings = pTrack->GetSpectrogramSettings();
1427  const auto windowSize = settings.GetFFTLength();
1428  const double rate = pTrack->GetRate();
1429  const double nyq = rate / 2.0;
1430  const double binFrequency = rate / windowSize;
1431 
1432  double f1 = viewInfo.selectedRegion.f1();
1433  double centerFrequency = viewInfo.selectedRegion.fc();
1434  if (centerFrequency <= 0) {
1435  centerFrequency = up ? binFrequency : nyq;
1436  f1 = centerFrequency * sqrt(2.0);
1437  }
1438 
1439  double ratio = f1 / centerFrequency;
1440  const int originalBin = floor(0.5 + centerFrequency / binFrequency);
1441  const int limitingBin = up ? floor(0.5 + nyq / binFrequency) : 1;
1442 
1443  // This is crude and wasteful, doing the FFT each time the command is called.
1444  // It would be better to cache the data, but then invalidation of the cache would
1445  // need doing in all places that change the time selection.
1446  StartSnappingFreqSelection(analyst, viewInfo, pTrack);
1447  double snappedFrequency = centerFrequency;
1448  int bin = originalBin;
1449  if (up) {
1450  while (snappedFrequency <= centerFrequency &&
1451  bin < limitingBin)
1452  snappedFrequency = analyst.FindPeak(++bin * binFrequency, NULL);
1453  }
1454  else {
1455  while (snappedFrequency >= centerFrequency &&
1456  bin > limitingBin)
1457  snappedFrequency = analyst.FindPeak(--bin * binFrequency, NULL);
1458  }
1459 
1460  // PRL: added these two lines with the big TrackPanel refactor
1461  const double maxRatio = findMaxRatio(snappedFrequency, rate);
1462  ratio = std::min(ratio, maxRatio);
1463 
1465  (snappedFrequency / ratio, snappedFrequency * ratio);
1466 }
1467 
1468 #if 0
1469 // unused
1470 void SelectHandle::ResetFreqSelectionPin
1471  (const ViewInfo &viewInfo, double hintFrequency, bool logF)
1472 {
1473  switch (mFreqSelMode) {
1474  case FREQ_SEL_INVALID:
1476  mFreqSelPin = -1.0;
1477  break;
1478 
1480  mFreqSelPin = viewInfo.selectedRegion.fc();
1481  break;
1482 
1483  case FREQ_SEL_DRAG_CENTER:
1484  {
1485  // Re-pin the width
1486  const double f0 = viewInfo.selectedRegion.f0();
1487  const double f1 = viewInfo.selectedRegion.f1();
1488  if (f0 >= 0 && f1 >= 0)
1489  mFreqSelPin = sqrt(f1 / f0);
1490  else
1491  mFreqSelPin = -1.0;
1492  }
1493  break;
1494 
1495  case FREQ_SEL_FREE:
1496  // Pin which? Farther from the hint which is the presumed
1497  // mouse position.
1498  {
1499  // If this function finds use again, the following should be
1500  // generalized using NumberScale
1501 
1502  const double f0 = viewInfo.selectedRegion.f0();
1503  const double f1 = viewInfo.selectedRegion.f1();
1504  if (logF) {
1505  if (f1 < 0)
1506  mFreqSelPin = f0;
1507  else {
1508  const double logf1 = log(std::max(1.0, f1));
1509  const double logf0 = log(std::max(1.0, f0));
1510  const double logHint = log(std::max(1.0, hintFrequency));
1511  if (std::abs(logHint - logf1) < std::abs(logHint - logf0))
1512  mFreqSelPin = f0;
1513  else
1514  mFreqSelPin = f1;
1515  }
1516  }
1517  else {
1518  if (f1 < 0 ||
1519  std::abs(hintFrequency - f1) < std::abs(hintFrequency - f0))
1520  mFreqSelPin = f0;
1521  else
1522  mFreqSelPin = f1;
1523  }
1524  }
1525  break;
1526 
1527  case FREQ_SEL_TOP_FREE:
1528  mFreqSelPin = viewInfo.selectedRegion.f0();
1529  break;
1530 
1531  case FREQ_SEL_BOTTOM_FREE:
1532  mFreqSelPin = viewInfo.selectedRegion.f1();
1533  break;
1534 
1535  default:
1536  wxASSERT(false);
1537  }
1538 }
1539 #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:611
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 Get(samplePtr buffer, sampleFormat format, sampleCount start, size_t len, fillFormat fill=fillZero, bool mayThrow=true) const
Definition: WaveTrack.cpp:1977
bool mSelStartValid
Definition: SelectHandle.h:134
virtual ~SelectHandle()
float ValueToPosition(float val) const
Definition: NumberScale.h:251
bool GetSelected() const
Definition: Track.h:268
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:363
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:328
const SpectrogramSettings & GetSpectrogramSettings() const
Definition: WaveTrack.cpp:711
double t1() const
std::shared_ptr< SnapManager > mSnapManager
Definition: SelectHandle.h:130
MixerBoard * GetMixerBoard()
Definition: Project.h:500
bool Snapped() const
Definition: Snap.h:80
void DrawExtras(DrawingPass pass, wxDC *dc, const wxRegion &updateRegion, const wxRect &panelRect) override
bool snappedPoint
Definition: Snap.h:77
std::unique_ptr< wxCursor > MakeCursor(int WXUNUSED(CursorId), const char *pXpm[36], int HotX, int HotY)
Definition: TrackPanel.cpp:274
virtual int GetKind() const
Definition: Track.h:322
Result mChangeHighlight
Definition: UIHandle.h:150
ToolsToolBar * GetToolsToolBar()
Definition: Project.cpp:4866
NumberScale GetScale(float minFreq, float maxFreq) const
void Connect(AudacityProject *pProject)
void SetFocusedTrack(Track *t)
double f0() const
wxInt64 outCoord
Definition: Snap.h:76
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:158
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:2163
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:94
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:291
double mSelStart
Definition: SelectHandle.h:135
wxString KeyStringDisplay(const wxString &key, bool usesSpecialChars)
Definition: Keyboard.cpp:53
void HandleCenterFrequencyClick(const ViewInfo &viewInfo, bool shiftDown, const WaveTrack *pTrack, double value)
bool IsAudioActive() const
Definition: Project.cpp:1423
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:725
void ModifyState(bool bWantsAutoSave)
Definition: Project.cpp:4547
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
_("Move Track &Down")+wxT("\t")+(GetActiveProject() -> GetCommandManager() ->GetKeyFromName(wxT("TrackMoveDown"))), OnMoveTrack) POPUP_MENU_ITEM(OnMoveTopID, _("Move Track to &Top")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveTop"))), OnMoveTrack) POPUP_MENU_ITEM(OnMoveBottomID, _("Move Track to &Bottom")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveBottom"))), OnMoveTrack) void TrackMenuTable::OnSetName(wxCommandEvent &)
Result Release(const TrackPanelMouseEvent &event, AudacityProject *pProject, wxWindow *pParent) override
bool setTimes(double t0, double t1)
wxString GetKeyFromName(const wxString &name) const
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:5477
sampleCount TimeToLongSamples(double t0) const
Convert correctly between an (absolute) time in seconds and a number of samples.
Definition: WaveTrack.cpp:1849
AUDACITY_DLL_API AudacityProject * GetActiveProject()
Definition: Project.cpp:300
std::shared_ptr< TrackPanelCell > pCell
bool IsClicked() const
std::weak_ptr< Track > mpTrack
Definition: SelectHandle.h:126
bool snappedTime
Definition: Snap.h:78
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:289
bool setFrequencies(double f0, double f1)
double GetRate() const
Definition: WaveTrack.cpp:424
TrackList * GetTracks()
Definition: Project.h:174
WaveTrackDisplay GetDisplay() const
Definition: WaveTrack.h:588
static std::shared_ptr< Subclass > Pointer(Track *t)
Definition: Track.h:129
void SetUseSnap(bool use)
const ViewInfo & GetViewInfo() const
Definition: Project.h:189
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
double outTime
Definition: Snap.h:75
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:3419
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:74