Audacity  2.2.2
TimeShiftHandle.cpp
Go to the documentation of this file.
1 /**********************************************************************
2 
3 Audacity: A Digital Audio Editor
4 
5 TimeShiftHandle.cpp
6 
7 Paul Licameli split from TrackPanel.cpp
8 
9 **********************************************************************/
10 
11 #include "../../Audacity.h"
12 #include "TimeShiftHandle.h"
13 #include "../../Experimental.h"
14 
15 #include "TrackControls.h"
16 #include "../../AColor.h"
17 #include "../../HitTestResult.h"
18 #include "../../Project.h"
19 #include "../../RefreshCode.h"
20 #include "../../TrackPanelMouseEvent.h"
21 #include "../../toolbars/ToolsToolBar.h"
22 #include "../../UndoManager.h"
23 #include "../../WaveTrack.h"
24 #include "../../../images/Cursors.h"
25 
27 ( const std::shared_ptr<Track> &pTrack, bool gripHit )
28  : mCapturedTrack{ pTrack }
29  , mGripHit{ gripHit }
30 {
31 }
32 
34 {
35 #ifdef EXPERIMENTAL_TRACK_PANEL_HIGHLIGHTING
37 #endif
38 }
39 
41 (const AudacityProject *WXUNUSED(pProject), bool unsafe)
42 {
43  static auto disabledCursor =
44  ::MakeCursor(wxCURSOR_NO_ENTRY, DisabledCursorXpm, 16, 16);
45  static auto slideCursor =
46  MakeCursor(wxCURSOR_SIZEWE, TimeCursorXpm, 16, 16);
47  // TODO: Should it say "track or clip" ? Non-wave tracks can move, or clips in a wave track.
48  // TODO: mention effects of shift (move all clips of selected wave track) and ctrl (move vertically only) ?
49  // -- but not all of that is available in multi tool.
50  auto message = _("Click and drag to move a track in time");
51 
52  return {
53  message,
54  (unsafe
55  ? &*disabledCursor
56  : &*slideCursor)
57  };
58 }
59 
61 (std::weak_ptr<TimeShiftHandle> &holder,
62  const std::shared_ptr<Track> &pTrack, bool gripHit)
63 {
64  // After all that, it still may be unsafe to drag.
65  // Even if so, make an informative cursor change from default to "banned."
66  auto result = std::make_shared<TimeShiftHandle>( pTrack, gripHit );
67  result = AssignUIHandlePtr(holder, result);
68  return result;
69 }
70 
72 (std::weak_ptr<TimeShiftHandle> &holder,
73  const wxMouseState &state, const wxRect &rect,
74  const std::shared_ptr<Track> &pTrack)
75 {
79 
80  // Perhaps we should delegate this to TrackArtist as only TrackArtist
81  // knows what the real sizes are??
82 
83  // The drag Handle width includes border, width and a little extra margin.
84  const int adjustedDragHandleWidth = 14;
85  // The hotspot for the cursor isn't at its centre. Adjust for this.
86  const int hotspotOffset = 5;
87 
88  // We are doing an approximate test here - is the mouse in the right or left border?
89  if (!(state.m_x + hotspotOffset < rect.x + adjustedDragHandleWidth ||
90  state.m_x + hotspotOffset >= rect.x + rect.width - adjustedDragHandleWidth))
91  return {};
92 
93  return HitAnywhere( holder, pTrack, true );
94 }
95 
97 {
98 }
99 
100 namespace
101 {
102  // Adds a track's clips to mCapturedClipArray within a specified time
103  void AddClipsToCaptured
104  (TrackClipArray &capturedClipArray, Track *pTrack, double t0, double t1)
105  {
106  if (pTrack->GetKind() == Track::Wave)
107  {
108  for(const auto &clip: static_cast<WaveTrack*>(pTrack)->GetClips())
109  {
110  if (!clip->AfterClip(t0) && !clip->BeforeClip(t1))
111  {
112  // Avoid getting clips that were already captured
113  bool newClip = true;
114  for (unsigned int ii = 0; newClip && ii < capturedClipArray.size(); ++ii)
115  newClip = (capturedClipArray[ii].clip != clip.get());
116  if (newClip)
117  capturedClipArray.push_back(TrackClip(pTrack, clip.get()));
118  }
119  }
120  }
121  else
122  {
123  // This handles label tracks rather heavy-handedly -- it would be nice to
124  // treat individual labels like clips
125 
126  // Avoid adding a track twice
127  bool newClip = true;
128  for (unsigned int ii = 0; newClip && ii < capturedClipArray.size(); ++ii)
129  newClip = (capturedClipArray[ii].track != pTrack);
130  if (newClip) {
131 #ifdef USE_MIDI
132  // do not add NoteTrack if the data is outside of time bounds
133  if (pTrack->GetKind() == Track::Note) {
134  if (pTrack->GetEndTime() < t0 || pTrack->GetStartTime() > t1)
135  return;
136  }
137 #endif
138  capturedClipArray.push_back(TrackClip(pTrack, NULL));
139  }
140  }
141  }
142 
143  // Helper for the above, adds a track's clips to mCapturedClipArray (eliminates
144  // duplication of this logic)
145  void AddClipsToCaptured
146  (TrackClipArray &capturedClipArray,
147  const ViewInfo &viewInfo, Track *pTrack, bool withinSelection)
148  {
149  if (withinSelection)
150  AddClipsToCaptured(capturedClipArray, pTrack,
151  viewInfo.selectedRegion.t0(), viewInfo.selectedRegion.t1());
152  else
153  AddClipsToCaptured(capturedClipArray, pTrack,
154  pTrack->GetStartTime(), pTrack->GetEndTime());
155  }
156 
157  // Adds a track's clips to state.capturedClipArray within a specified time
158  void AddClipsToCaptured
159  ( ClipMoveState &state, Track *t, double t0, double t1 )
160  {
161  if (t->GetKind() == Track::Wave)
162  {
163  for(const auto &clip: static_cast<WaveTrack*>(t)->GetClips())
164  {
165  if ( ! clip->AfterClip(t0) && ! clip->BeforeClip(t1) )
166  {
167  // Avoid getting clips that were already captured
168  bool newClip = true;
169  for (unsigned int i = 0; i < state.capturedClipArray.size(); ++i) {
170  if ( state.capturedClipArray[i].clip == clip.get() ) {
171  newClip = false;
172  break;
173  }
174  }
175 
176  if (newClip)
177  state.capturedClipArray.push_back( TrackClip(t, clip.get()) );
178  }
179  }
180  }
181  else
182  {
183  // This handles label tracks rather heavy-handedly -- it would be nice to
184  // treat individual labels like clips
185 
186  // Avoid adding a track twice
187  bool newClip = true;
188  for ( unsigned int i = 0; i < state.capturedClipArray.size(); ++i ) {
189  if ( state.capturedClipArray[i].track == t ) {
190  newClip = false;
191  break;
192  }
193  }
194 
195  if (newClip) {
196  #ifdef USE_MIDI
197  // do not add NoteTrack if the data is outside of time bounds
198  if (t->GetKind() == Track::Note) {
199  if (t->GetEndTime() < t0 || t->GetStartTime() > t1)
200  return;
201  }
202  #endif
203  state.capturedClipArray.push_back(TrackClip(t, NULL));
204  }
205  }
206  }
207 
208  // Helper for the above, adds a track's clips to mCapturedClipArray (eliminates
209  // duplication of this logic)
210  void AddClipsToCaptured
211  ( ClipMoveState &state, const ViewInfo &viewInfo,
212  Track *t, bool withinSelection )
213  {
214  if (withinSelection)
215  AddClipsToCaptured( state, t, viewInfo.selectedRegion.t0(),
216  viewInfo.selectedRegion.t1() );
217  else
218  AddClipsToCaptured( state, t, t->GetStartTime(), t->GetEndTime() );
219  }
220 
221  // Don't count right channels.
222  WaveTrack *NthAudioTrack(TrackList &list, int nn)
223  {
224  if (nn >= 0) {
226  Track *pTrack = iter.First();
227  while (pTrack && nn--)
228  pTrack = iter.Next(true);
229  return static_cast<WaveTrack*>(pTrack);
230  }
231 
232  return NULL;
233  }
234 
235  // Don't count right channels.
236  int TrackPosition(TrackList &list, Track *pFindTrack)
237  {
238  Track *const partner = pFindTrack->GetLink();
240  int nn = 0;
241  for (Track *pTrack = iter.First(); pTrack; pTrack = iter.Next(true), ++nn) {
242  if (pTrack == pFindTrack ||
243  pTrack == partner)
244  return nn;
245  }
246  return -1;
247  }
248 
249  WaveClip *FindClipAtTime(WaveTrack *pTrack, double time)
250  {
251  if (pTrack) {
252  // WaveClip::GetClipAtX doesn't work unless the clip is on the screen and can return bad info otherwise
253  // instead calculate the time manually
254  double rate = pTrack->GetRate();
255  auto s0 = (sampleCount)(time * rate + 0.5);
256 
257  if (s0 >= 0)
258  return pTrack->GetClipAtSample(s0);
259  }
260 
261  return 0;
262  }
263 }
264 
266  ( ClipMoveState &state, const ViewInfo &viewInfo, Track &capturedTrack,
267  TrackList &trackList, bool syncLocked, double clickTime )
268 {
269 // The captured clip is the focus, but we need to create a list
270  // of all clips that have to move, also...
271 
272  state.capturedClipArray.clear();
273 
274  // First, if click was in selection, capture selected clips; otherwise
275  // just the clicked-on clip
276  if ( state.capturedClipIsSelection ) {
277  TrackListIterator iter( &trackList );
278  for (Track *t = iter.First(); t; t = iter.Next()) {
279  if (t->GetSelected()) {
280  AddClipsToCaptured( state, viewInfo, t, true );
281  if (t->GetKind() != Track::Wave)
282  state.trackExclusions.push_back(t);
283  }
284  }
285  }
286  else {
287  state.capturedClipArray.push_back
288  (TrackClip( &capturedTrack, state.capturedClip ));
289 
290  // Check for stereo partner
291  Track *partner = capturedTrack.GetLink();
292  WaveTrack *wt;
293  if (state.capturedClip &&
294  // Assume linked track is wave or null
295  nullptr != (wt = static_cast<WaveTrack*>(partner))) {
296  WaveClip *const clip = FindClipAtTime(wt, clickTime);
297 
298  if (clip)
299  state.capturedClipArray.push_back(TrackClip(partner, clip));
300  }
301  }
302 
303  // Now, if sync-lock is enabled, capture any clip that's linked to a
304  // captured clip.
305  if ( syncLocked ) {
306  // AWD: mCapturedClipArray expands as the loop runs, so newly-added
307  // clips are considered (the effect is like recursion and terminates
308  // because AddClipsToCaptured doesn't add duplicate clips); to remove
309  // this behavior just store the array size beforehand.
310  for (unsigned int i = 0; i < state.capturedClipArray.size(); ++i) {
311  // Capture based on tracks that have clips -- that means we
312  // don't capture based on links to label tracks for now (until
313  // we can treat individual labels as clips)
314  if ( state.capturedClipArray[i].clip ) {
315  // Iterate over sync-lock group tracks.
316  SyncLockedTracksIterator git( &trackList );
317  for (Track *t = git.StartWith( state.capturedClipArray[i].track );
318  t; t = git.Next() )
319  {
320  AddClipsToCaptured(state, t,
321  state.capturedClipArray[i].clip->GetStartTime(),
322  state.capturedClipArray[i].clip->GetEndTime() );
323  if (t->GetKind() != Track::Wave)
324  state.trackExclusions.push_back(t);
325  }
326  }
327 #ifdef USE_MIDI
328  // Capture additional clips from NoteTracks
329  Track *nt = state.capturedClipArray[i].track;
330  if (nt->GetKind() == Track::Note) {
331  // Iterate over sync-lock group tracks.
332  SyncLockedTracksIterator git( &trackList );
333  for (Track *t = git.StartWith(nt); t; t = git.Next())
334  {
335  AddClipsToCaptured
336  ( state, t, nt->GetStartTime(), nt->GetEndTime() );
337  if (t->GetKind() != Track::Wave)
338  state.trackExclusions.push_back(t);
339  }
340  }
341 #endif
342  }
343  }
344 }
345 
347  ( ClipMoveState &state, TrackList &trackList, Track &capturedTrack )
348 {
349 #ifdef USE_MIDI
350  if ( state.capturedClipArray.size() )
351 #else
352  if ( state.capturedClip )
353 #endif
354  {
355  double allowed;
356  double initialAllowed;
357  double safeBigDistance = 1000 + 2.0 * ( trackList.GetEndTime() -
358  trackList.GetStartTime() );
359 
360  do { // loop to compute allowed, does not actually move anything yet
361  initialAllowed = state.hSlideAmount;
362 
363  unsigned int i, j;
364  for ( i = 0; i < state.capturedClipArray.size(); ++i ) {
365  WaveTrack *track = (WaveTrack *)state.capturedClipArray[i].track;
366  WaveClip *clip = state. capturedClipArray[i].clip;
367 
368  if (clip) { // only audio clips are used to compute allowed
369  // Move all other selected clips totally out of the way
370  // temporarily because they're all moving together and
371  // we want to find out if OTHER clips are in the way,
372  // not one of the moving ones
373  for ( j = 0; j < state.capturedClipArray.size(); j++ ) {
374  WaveClip *clip2 = state.capturedClipArray[j].clip;
375  if (clip2 && clip2 != clip)
376  clip2->Offset(-safeBigDistance);
377  }
378 
379  if ( track->CanOffsetClip(clip, state.hSlideAmount, &allowed) ) {
380  if ( state.hSlideAmount != allowed ) {
381  state.hSlideAmount = allowed;
382  state.snapLeft = state.snapRight = -1; // see bug 1067
383  }
384  }
385  else {
386  state.hSlideAmount = 0.0;
387  state.snapLeft = state.snapRight = -1; // see bug 1067
388  }
389 
390  for ( j = 0; j < state.capturedClipArray.size(); ++j ) {
391  WaveClip *clip2 = state.capturedClipArray[j].clip;
392  if (clip2 && clip2 != clip)
393  clip2->Offset(safeBigDistance);
394  }
395  }
396  }
397  } while ( state.hSlideAmount != initialAllowed );
398 
399  if ( state.hSlideAmount != 0.0 ) { // finally, here is where clips are moved
400  unsigned int i;
401  for ( i = 0; i < state.capturedClipArray.size(); ++i ) {
402  Track *track = state.capturedClipArray[i].track;
403  WaveClip *clip = state.capturedClipArray[i].clip;
404  if (clip)
405  clip->Offset( state.hSlideAmount );
406  else
407  track->Offset( state.hSlideAmount );
408  }
409  }
410  }
411  else {
412  // For Shift key down, or
413  // For non wavetracks, specifically label tracks ...
414  capturedTrack.Offset( state.hSlideAmount );
415  Track* link = capturedTrack.GetLink();
416  if (link)
417  link->Offset( state.hSlideAmount );
418  }
419 }
420 
422 (const TrackPanelMouseEvent &evt, AudacityProject *pProject)
423 {
424  using namespace RefreshCode;
425  const bool unsafe = pProject->IsAudioActive();
426  if ( unsafe )
427  return Cancelled;
428 
429  const wxMouseEvent &event = evt.event;
430  const wxRect &rect = evt.rect;
431  const ViewInfo &viewInfo = pProject->GetViewInfo();
432 
433  const auto pTrack = std::static_pointer_cast<Track>(evt.pCell);
434 
435  TrackList *const trackList = pProject->GetTracks();
436 
437  mClipMoveState.clear();
438  mDidSlideVertically = false;
439 
440  ToolsToolBar *const ttb = pProject->GetToolsToolBar();
441  const bool multiToolModeActive = (ttb && ttb->IsDown(multiTool));
442 
443  const double clickTime =
444  viewInfo.PositionToTime(event.m_x, rect.x);
445  mClipMoveState.capturedClipIsSelection =
446  (pTrack->GetSelected() &&
447  clickTime >= viewInfo.selectedRegion.t0() &&
448  clickTime < viewInfo.selectedRegion.t1());
449 
450  WaveTrack *wt = pTrack->GetKind() == Track::Wave
451  ? static_cast<WaveTrack*>(pTrack.get()) : nullptr;
452 
453  if ((wt
454 #ifdef USE_MIDI
455  || pTrack->GetKind() == Track::Note
456 #endif
457  ) && !event.ShiftDown())
458  {
459 #ifdef USE_MIDI
460  if (!wt)
461  mClipMoveState.capturedClip = nullptr;
462  else
463 #endif
464  {
465  mClipMoveState.capturedClip = wt->GetClipAtX(event.m_x);
466  if (mClipMoveState.capturedClip == NULL)
467  return Cancelled;
468  }
469 
470  CreateListOfCapturedClips
471  ( mClipMoveState, viewInfo, *pTrack, *trackList,
472  pProject->IsSyncLocked(), clickTime );
473  }
474  else {
475  // Shift was down, or track was not Wave or Note
476  mClipMoveState.capturedClip = NULL;
477  mClipMoveState.capturedClipArray.clear();
478  }
479 
480  mSlideUpDownOnly = event.CmdDown() && !multiToolModeActive;
481  mRect = rect;
482  mMouseClickX = event.m_x;
483  const double selStart = viewInfo.PositionToTime(event.m_x, mRect.x);
484  mSnapManager = std::make_shared<SnapManager>(trackList,
485  &viewInfo,
486  &mClipMoveState.capturedClipArray,
487  &mClipMoveState.trackExclusions,
488  true); // don't snap to time
489  mClipMoveState.snapLeft = -1;
490  mClipMoveState.snapRight = -1;
491  mSnapPreferRightEdge =
492  mClipMoveState.capturedClip &&
493  (fabs(selStart - mClipMoveState.capturedClip->GetEndTime()) <
494  fabs(selStart - mClipMoveState.capturedClip->GetStartTime()));
495 
496  return RefreshNone;
497 }
498 
500 (const TrackPanelMouseEvent &evt, AudacityProject *pProject)
501 {
502  const wxMouseEvent &event = evt.event;
503  ViewInfo &viewInfo = pProject->GetViewInfo();
504 
505  // We may switch pTrack to its stereo partner below
506  Track *track = dynamic_cast<Track*>(evt.pCell.get());
507 
508  // Uncommenting this permits drag to continue to work even over the controls area
509  /*
510  pTrack = static_cast<CommonTrackPanelCell*>(evt.pCell)->FindTrack().get();
511  */
512 
513  if (!track) {
514  // Allow sliding if the pointer is not over any track, but only if x is
515  // within the bounds of the tracks area.
516  if (event.m_x >= mRect.GetX() &&
517  event.m_x < mRect.GetX() + mRect.GetWidth())
518  track = mCapturedTrack.get();
519  }
520 
521  // May need a shared_ptr to reassign mCapturedTrack below
522  auto pTrack = Track::Pointer( track );
523  if (!pTrack)
525 
526 
527  using namespace RefreshCode;
528  const bool unsafe = pProject->IsAudioActive();
529  if (unsafe) {
530  this->Cancel(pProject);
531  return RefreshAll | Cancelled;
532  }
533 
534  TrackList *const trackList = pProject->GetTracks();
535 
536  // GM: DoSlide now implementing snap-to
537  // samples functionality based on sample rate.
538 
539  // Start by undoing the current slide amount; everything
540  // happens relative to the original horizontal position of
541  // each clip...
542 #ifdef USE_MIDI
543  if (mClipMoveState.capturedClipArray.size())
544 #else
545  if (mClipMoveState.capturedClip)
546 #endif
547  {
548  for (unsigned ii = 0; ii < mClipMoveState.capturedClipArray.size(); ++ii) {
549  if (mClipMoveState.capturedClipArray[ii].clip)
550  mClipMoveState.capturedClipArray[ii].clip->Offset
551  ( -mClipMoveState.hSlideAmount );
552  else
553  mClipMoveState.capturedClipArray[ii].track->Offset
554  ( -mClipMoveState.hSlideAmount );
555  }
556  }
557  else {
558  // Was a shift-click
559  mCapturedTrack->Offset( -mClipMoveState.hSlideAmount );
560  Track *const link = mCapturedTrack->GetLink();
561  if (link)
562  link->Offset( -mClipMoveState.hSlideAmount );
563  }
564 
565  if ( mClipMoveState.capturedClipIsSelection ) {
566  // Slide the selection, too
567  viewInfo.selectedRegion.move( -mClipMoveState.hSlideAmount );
568  }
569  mClipMoveState.hSlideAmount = 0.0;
570 
571  double desiredSlideAmount;
572  if (mSlideUpDownOnly) {
573  desiredSlideAmount = 0.0;
574  }
575  else {
576  desiredSlideAmount =
577  viewInfo.PositionToTime(event.m_x) -
578  viewInfo.PositionToTime(mMouseClickX);
579  bool trySnap = false;
580  double clipLeft = 0, clipRight = 0;
581 #ifdef USE_MIDI
582  if (pTrack->GetKind() == Track::Wave) {
583  WaveTrack *const mtw = static_cast<WaveTrack*>(pTrack.get());
584  const double rate = mtw->GetRate();
585  // set it to a sample point
586  desiredSlideAmount = rint(desiredSlideAmount * rate) / rate;
587  }
588 
589  // Adjust desiredSlideAmount using SnapManager
590  if (mSnapManager.get() && mClipMoveState.capturedClipArray.size()) {
591  trySnap = true;
592  if (mClipMoveState.capturedClip) {
593  clipLeft = mClipMoveState.capturedClip->GetStartTime()
594  + desiredSlideAmount;
595  clipRight = mClipMoveState.capturedClip->GetEndTime()
596  + desiredSlideAmount;
597  }
598  else {
599  clipLeft = mCapturedTrack->GetStartTime() + desiredSlideAmount;
600  clipRight = mCapturedTrack->GetEndTime() + desiredSlideAmount;
601  }
602  }
603 #else
604  {
605  trySnap = true;
606  if (pTrack->GetKind() == Track::Wave) {
607  auto wt = static_cast<const WaveTrack *>(pTrack);
608  const double rate = wt->GetRate();
609  // set it to a sample point
610  desiredSlideAmount = rint(desiredSlideAmount * rate) / rate;
611  if (mSnapManager && mClipMoveState.capturedClip) {
612  clipLeft = mClipMoveState.capturedClip->GetStartTime()
613  + desiredSlideAmount;
614  clipRight = mClipMoveState.capturedClip->GetEndTime()
615  + desiredSlideAmount;
616  }
617  }
618  }
619 #endif
620  if (trySnap)
621  {
622  auto results =
623  mSnapManager->Snap(mCapturedTrack.get(), clipLeft, false);
624  auto newClipLeft = results.outTime;
625  results =
626  mSnapManager->Snap(mCapturedTrack.get(), clipRight, false);
627  auto newClipRight = results.outTime;
628 
629  // Only one of them is allowed to snap
630  if (newClipLeft != clipLeft && newClipRight != clipRight) {
631  // Un-snap the un-preferred edge
632  if (mSnapPreferRightEdge)
633  newClipLeft = clipLeft;
634  else
635  newClipRight = clipRight;
636  }
637 
638  // Take whichever one snapped (if any) and compute the NEW desiredSlideAmount
639  mClipMoveState.snapLeft = -1;
640  mClipMoveState.snapRight = -1;
641  if (newClipLeft != clipLeft) {
642  const double difference = (newClipLeft - clipLeft);
643  desiredSlideAmount += difference;
644  mClipMoveState.snapLeft =
645  viewInfo.TimeToPosition(newClipLeft, mRect.x);
646  }
647  else if (newClipRight != clipRight) {
648  const double difference = (newClipRight - clipRight);
649  desiredSlideAmount += difference;
650  mClipMoveState.snapRight =
651  viewInfo.TimeToPosition(newClipRight, mRect.x);
652  }
653  }
654  }
655 
656  // Scroll during vertical drag.
657  // EnsureVisible(pTrack); //vvv Gale says this has problems on Linux, per bug 393 thread. Revert for 2.0.2.
658  bool slidVertically = false;
659 
660  // If the mouse is over a track that isn't the captured track,
661  // decide which tracks the captured clips should go to.
662  if (mClipMoveState.capturedClip &&
663  pTrack != mCapturedTrack &&
664  pTrack->GetKind() == Track::Wave
665  /* && !mCapturedClipIsSelection*/)
666  {
667  const int diff =
668  TrackPosition(*trackList, pTrack.get()) -
669  TrackPosition(*trackList, mCapturedTrack.get());
670  for ( unsigned ii = 0, nn = mClipMoveState.capturedClipArray.size();
671  ii < nn; ++ii ) {
672  TrackClip &trackClip = mClipMoveState.capturedClipArray[ii];
673  if (trackClip.clip) {
674  // Move all clips up or down by an equal count of audio tracks.
675  Track *const pSrcTrack = trackClip.track;
676  auto pDstTrack = NthAudioTrack(*trackList,
677  diff + TrackPosition(*trackList, pSrcTrack));
678  // Can only move mono to mono, or left to left, or right to right
679  // And that must be so for each captured clip
680  bool stereo = (pSrcTrack->GetLink() != 0);
681  if (pDstTrack && stereo && !pSrcTrack->GetLinked())
682  // Assume linked track is wave or null
683  pDstTrack = static_cast<WaveTrack*>(pDstTrack->GetLink());
684  bool ok = pDstTrack &&
685  (stereo == (pDstTrack->GetLink() != 0)) &&
686  (!stereo || (pSrcTrack->GetLinked() == pDstTrack->GetLinked()));
687  if (ok)
688  trackClip.dstTrack = pDstTrack;
689  else
690  return RefreshAll;
691  }
692  }
693 
694  // Having passed that test, remove clips temporarily from their
695  // tracks, so moving clips don't interfere with each other
696  // when we call CanInsertClip()
697  for ( unsigned ii = 0, nn = mClipMoveState.capturedClipArray.size();
698  ii < nn; ++ii ) {
699  TrackClip &trackClip = mClipMoveState.capturedClipArray[ii];
700  WaveClip *const pSrcClip = trackClip.clip;
701  if (pSrcClip)
702  trackClip.holder =
703  // Assume track is wave because it has a clip
704  static_cast<WaveTrack*>(trackClip.track)->
705  RemoveAndReturnClip(pSrcClip);
706  }
707 
708  // Now check that the move is possible
709  bool ok = true;
710  // The test for tolerance will need review with FishEye!
711  // The tolerance is supposed to be the time for one pixel, i.e. one pixel tolerance
712  // at current zoom.
713  double slide = desiredSlideAmount; // remember amount requested.
714  double tolerance = viewInfo.PositionToTime(event.m_x+1) - viewInfo.PositionToTime(event.m_x);
715 
716  // The desiredSlideAmount may change and the tolerance may get used up.
717  for ( unsigned ii = 0, nn = mClipMoveState.capturedClipArray.size();
718  ok && ii < nn; ++ii) {
719  TrackClip &trackClip = mClipMoveState.capturedClipArray[ii];
720  WaveClip *const pSrcClip = trackClip.clip;
721  if (pSrcClip)
722  ok = trackClip.dstTrack->CanInsertClip(pSrcClip, desiredSlideAmount, tolerance);
723  }
724 
725  if( ok ) {
726  // fits ok, but desiredSlideAmount could have been updated to get the clip to fit.
727  // Check again, in the new position, this time with zero tolerance.
728  tolerance = 0.0;
729  for ( unsigned ii = 0, nn = mClipMoveState.capturedClipArray.size();
730  ok && ii < nn; ++ii) {
731  TrackClip &trackClip = mClipMoveState.capturedClipArray[ii];
732  WaveClip *const pSrcClip = trackClip.clip;
733  if (pSrcClip)
734  ok = trackClip.dstTrack->CanInsertClip(pSrcClip, desiredSlideAmount, tolerance);
735  }
736  }
737 
738  if (!ok) {
739  // Failure, even with using tolerance.
740  // Failure -- we'll put clips back where they were
741  // ok will next indicate if a horizontal slide is OK.
742  ok = true; // assume slide is OK.
743  tolerance = 0.0;
744  desiredSlideAmount = slide;
745  for ( unsigned ii = 0, nn = mClipMoveState.capturedClipArray.size();
746  ii < nn; ++ii) {
747  TrackClip &trackClip = mClipMoveState.capturedClipArray[ii];
748  WaveClip *const pSrcClip = trackClip.clip;
749  if (pSrcClip){
750  // back to the track it came from...
751  trackClip.dstTrack = static_cast<WaveTrack*>(trackClip.track);
752  ok = ok && trackClip.dstTrack->CanInsertClip(pSrcClip, desiredSlideAmount, tolerance);
753  }
754  }
755  for ( unsigned ii = 0, nn = mClipMoveState.capturedClipArray.size();
756  ii < nn; ++ii) {
757  TrackClip &trackClip = mClipMoveState.capturedClipArray[ii];
758  WaveClip *const pSrcClip = trackClip.clip;
759  if (pSrcClip){
760 
761  // Attempt to move to a new track did not work.
762  // Put the clip back appropriately shifted!
763  if( ok)
764  trackClip.holder->Offset(slide);
765  // Assume track is wave because it has a clip
766  static_cast<WaveTrack*>(trackClip.track)->
767  AddClip(std::move(trackClip.holder));
768  }
769  }
770  // Make the offset permanent; start from a "clean slate"
771  if( ok ) {
772  mMouseClickX = event.m_x;
773  if (mClipMoveState.capturedClipIsSelection) {
774  // Slide the selection, too
775  viewInfo.selectedRegion.move( slide );
776  }
777  mClipMoveState.hSlideAmount = 0;
778  }
779 
780  return RefreshAll;
781  }
782  else {
783  // Do the vertical moves of clips
784  for ( unsigned ii = 0, nn = mClipMoveState.capturedClipArray.size();
785  ii < nn; ++ii ) {
786  TrackClip &trackClip = mClipMoveState.capturedClipArray[ii];
787  WaveClip *const pSrcClip = trackClip.clip;
788  if (pSrcClip) {
789  const auto dstTrack = trackClip.dstTrack;
790  dstTrack->AddClip(std::move(trackClip.holder));
791  trackClip.track = dstTrack;
792  }
793  }
794 
795  mCapturedTrack = pTrack;
796  mDidSlideVertically = true;
797 
798  // Make the offset permanent; start from a "clean slate"
799  mMouseClickX = event.m_x;
800  }
801 
802  // Not done yet, check for horizontal movement.
803  slidVertically = true;
804  }
805 
806  if (desiredSlideAmount == 0.0)
807  return RefreshAll;
808 
809  mClipMoveState.hSlideAmount = desiredSlideAmount;
810 
811  DoSlideHorizontal( mClipMoveState, *trackList, *mCapturedTrack );
812 
813  if (mClipMoveState.capturedClipIsSelection) {
814  // Slide the selection, too
815  viewInfo.selectedRegion.move( mClipMoveState.hSlideAmount );
816  }
817 
818  if (slidVertically) {
819  // NEW origin
820  mClipMoveState.hSlideAmount = 0;
821  }
822 
823  return RefreshAll;
824 }
825 
827 (const TrackPanelMouseState &, const AudacityProject *pProject)
828 {
829  // After all that, it still may be unsafe to drag.
830  // Even if so, make an informative cursor change from default to "banned."
831  const bool unsafe = pProject->IsAudioActive();
832  return HitPreview(pProject, unsafe);
833 }
834 
837  wxWindow *)
838 {
839  using namespace RefreshCode;
840  const bool unsafe = pProject->IsAudioActive();
841  if (unsafe)
842  return this->Cancel(pProject);
843 
844  Result result = RefreshNone;
845 
846  // Do not draw yellow lines
847  if ( mClipMoveState.snapLeft != -1 || mClipMoveState.snapRight != -1) {
848  mClipMoveState.snapLeft = mClipMoveState.snapRight = -1;
849  result |= RefreshAll;
850  }
851 
852  if ( !mDidSlideVertically && mClipMoveState.hSlideAmount == 0 )
853  return result;
854 
855  for ( size_t ii = 0; ii < mClipMoveState.capturedClipArray.size(); ++ii )
856  {
857  TrackClip &trackClip = mClipMoveState.capturedClipArray[ii];
858  WaveClip* pWaveClip = trackClip.clip;
859  // Note that per AddClipsToCaptured(Track *t, double t0, double t1),
860  // in the non-WaveTrack case, the code adds a NULL clip to mCapturedClipArray,
861  // so we have to check for that any time we're going to deref it.
862  // Previous code did not check it here, and that caused bug 367 crash.
863  if (pWaveClip &&
864  trackClip.track != trackClip.origTrack)
865  {
866  // Now that user has dropped the clip into a different track,
867  // make sure the sample rate matches the destination track (mCapturedTrack).
868  // Assume the clip was dropped in a wave track
869  pWaveClip->Resample
870  (static_cast<WaveTrack*>(trackClip.track)->GetRate());
871  pWaveClip->MarkChanged();
872  }
873  }
874 
875  wxString msg;
876  bool consolidate;
877  if (mDidSlideVertically) {
878  msg = _("Moved clips to another track");
879  consolidate = false;
880  }
881  else {
882  msg.Printf(
883  ( mClipMoveState.hSlideAmount > 0
884  ? _("Time shifted tracks/clips right %.02f seconds")
885  : _("Time shifted tracks/clips left %.02f seconds") ),
886  fabs( mClipMoveState.hSlideAmount ) );
887  consolidate = true;
888  }
889  pProject->PushState(msg, _("Time-Shift"),
890  consolidate ? (UndoPush::CONSOLIDATE) : (UndoPush::AUTOSAVE));
891 
892  return result | FixScrollbars;
893 }
894 
896 {
897  pProject->RollbackState();
899 }
900 
903  wxDC * dc, const wxRegion &, const wxRect &)
904 {
905  if (pass == Panel) {
906  // Draw snap guidelines if we have any
907  if ( mSnapManager )
908  mSnapManager->Draw
909  ( dc, mClipMoveState.snapLeft, mClipMoveState.snapRight );
910  }
911 }
int GetKind() const override
Definition: WaveTrack.h:121
static void DoSlideHorizontal(ClipMoveState &state, TrackList &trackList, Track &capturedTrack)
A list of TrackListNode items.
Definition: Track.h:611
std::shared_ptr< WaveClip > holder
Definition: Snap.h:44
double t0() const
static HitTestPreview HitPreview(const AudacityProject *pProject, bool unsafe)
ViewInfo is used mainly to hold the zooming, selection and scroll information. It also has some statu...
Definition: ViewInfo.h:141
virtual ~TimeShiftHandle()
SelectedRegion selectedRegion
Definition: ViewInfo.h:160
double GetStartTime() const
Definition: Track.cpp:1404
bool GetSelected() const
Definition: Track.h:268
void Offset(double delta)
Definition: WaveClip.h:223
void MarkChanged()
Definition: WaveClip.h:255
virtual double GetEndTime() const =0
double GetEndTime() const
Definition: Track.cpp:1409
Track * Next(bool skiplinked=false) override
Definition: Track.cpp:705
void Offset(double t)
Definition: Track.h:281
void clear()=delete
double PositionToTime(wxInt64 position, wxInt64 origin=0, bool ignoreFisheye=false) const
Definition: ViewInfo.cpp:49
void Enter(bool forward) override
Definition: Snap.h:33
bool GetLinked() const
Definition: Track.h:271
void Resample(int rate, ProgressDialog *progress=NULL)
Definition: WaveClip.cpp:1898
bool capturedClipIsSelection
WaveClip * capturedClip
WaveClip * GetClipAtSample(sampleCount sample)
Definition: WaveTrack.cpp:2176
void move(double delta)
virtual double GetStartTime() const =0
double t1() const
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
HitTestPreview Preview(const TrackPanelMouseState &state, const AudacityProject *pProject) override
Result mChangeHighlight
Definition: UIHandle.h:150
ToolsToolBar * GetToolsToolBar()
Definition: Project.cpp:4866
AudacityProject provides the main window, with tools and tracks contained within it.
Definition: Project.h:158
static UIHandlePtr HitAnywhere(std::weak_ptr< TimeShiftHandle > &holder, const std::shared_ptr< Track > &pTrack, bool gripHit)
unsigned Result
Definition: UIHandle.h:37
WaveClip * GetClipAtX(int xcoord)
Definition: WaveTrack.cpp:2163
void RollbackState()
Definition: Project.cpp:4542
This allows multiple clips to be a part of one WaveTrack.
Definition: WaveClip.h:176
Track * track
Definition: Snap.h:40
TimeShiftHandle(const TimeShiftHandle &)=delete
WaveTrack * dstTrack
Definition: Snap.h:42
A Track that contains audio waveform data.
Definition: WaveTrack.h:60
bool CanOffsetClip(WaveClip *clip, double amount, double *allowedAmount=NULL)
Definition: WaveTrack.cpp:2297
Fundamental data object of Audacity, placed in the TrackPanel. Classes derived form it include the Wa...
Definition: Track.h:94
TrackClipArray capturedClipArray
Result Cancel(AudacityProject *pProject) override
Result Drag(const TrackPanelMouseEvent &event, AudacityProject *pProject) override
wxInt64 TimeToPosition(double time, wxInt64 origin=0, bool ignoreFisheye=false) const
STM: Converts a project time to screen x position.
Definition: ViewInfo.cpp:59
if(pTrack &&pTrack->GetDisplay()!=WaveTrack::Spectrum)
void DrawExtras(DrawingPass pass, wxDC *dc, const wxRegion &, const wxRect &panelRect) override
virtual Track * First(TrackList *val=nullptr)
Definition: Track.cpp:414
bool IsAudioActive() const
Definition: Project.cpp:1423
std::shared_ptr< UIHandle > UIHandlePtr
Definition: TrackPanel.h:59
Result Click(const TrackPanelMouseEvent &event, AudacityProject *pProject) override
An iterator for a TrackList.
Definition: Track.h:394
_("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 &)
static UIHandlePtr HitTest(std::weak_ptr< TimeShiftHandle > &holder, const wxMouseState &state, const wxRect &rect, const std::shared_ptr< Track > &pTrack)
void AddClip(std::shared_ptr< WaveClip > &&clip)
Definition: WaveTrack.cpp:1007
void PushState(const wxString &desc, const wxString &shortDesc)
Definition: Project.cpp:4502
Track * GetLink() const
Definition: Track.cpp:267
bool IsSyncLocked()
Definition: Project.cpp:5477
virtual Track * Next(bool skiplinked=false)
Definition: Track.cpp:456
static void CreateListOfCapturedClips(ClipMoveState &state, const ViewInfo &viewInfo, Track &capturedTrack, TrackList &trackList, bool syncLocked, double clickTime)
std::shared_ptr< TrackPanelCell > pCell
A kind of ToolBar with Tools on it.
Definition: ToolsToolBar.h:47
TrackArray trackExclusions
double GetRate() const
Definition: WaveTrack.cpp:424
TrackList * GetTracks()
Definition: Project.h:174
bool CanInsertClip(WaveClip *clip, double &slideBy, double &tolerance)
Definition: WaveTrack.cpp:2344
static std::shared_ptr< Subclass > Pointer(Track *t)
Definition: Track.h:129
Track * StartWith(Track *member) override
Definition: Track.cpp:658
const ViewInfo & GetViewInfo() const
Definition: Project.h:189
WaveClip * clip
Definition: Snap.h:43
std::shared_ptr< Subclass > AssignUIHandlePtr(std::weak_ptr< Subclass > &holder, const std::shared_ptr< Subclass > &pNew)
Definition: UIHandle.h:162
Track * origTrack
Definition: Snap.h:41
Result Release(const TrackPanelMouseEvent &event, AudacityProject *pProject, wxWindow *pParent) override
DrawingPass
Definition: UIHandle.h:43