Audacity 3.2.0
WaveClipTrimHandle.cpp
Go to the documentation of this file.
1/*!********************************************************************
2*
3 Audacity: A Digital Audio Editor
4
5 WaveClipTrimHandle.cpp
6
7 Vitaly Sverchinsky
8
9 **********************************************************************/
10
11#include "WaveClipTrimHandle.h"
12#include "ProjectAudioIO.h"
13#include "RefreshCode.h"
14
15#include <wx/event.h>
16
17#include "../../../../TrackArtist.h"
18#include "../../../../Snap.h"
19#include "../../../../TrackPanelDrawingContext.h"
20#include "../../../../../images/Cursors.h"
21#include "WaveClip.h"
22#include "WaveTrack.h"
23#include "WaveTrackView.h"
24#include "HitTestResult.h"
26#include "ViewInfo.h"
27#include "ProjectHistory.h"
28#include "UndoManager.h"
29
30namespace {
31
32 std::vector<std::shared_ptr<WaveClip>> FindClipsInChannels(double start, double end, WaveTrack* track) {
33 std::vector<std::shared_ptr<WaveClip>> result;
34 for (auto channel : TrackList::Channels(track))
35 {
36 for (auto& clip : channel->GetClips())
37 {
38 if (clip->GetPlayStartTime() == start && clip->GetPlayEndTime() == end)
39 result.push_back(clip);
40 }
41 }
42 return result;
43 }
44}
45
47
49{
50 std::shared_ptr<WaveTrack> mTrack;
51 std::vector<std::shared_ptr<WaveClip>> mClips;
54 std::pair<double, double> mRange;
56
57 std::unique_ptr<SnapManager> mSnapManager;
59
60 void TrimTo(double t)
61 {
62 t = std::clamp(t, mRange.first, mRange.second);
64 {
65 for (auto& clip : mClips)
66 clip->TrimLeftTo(t);
67 }
68 else
69 {
70 for (auto& clip : mClips)
71 clip->TrimRightTo(t);
72 }
73 }
74
75 //Search for a good snap points among all tracks, including
76 //the one to which adjusted clip belongs to, but not counting
77 //the borders of adjusted clip
79 const WaveTrack* currentTrack,
80 WaveClip* adjustedClip,
81 const std::pair<double, double> range)
82 {
83 SnapPointArray result;
84
85 auto addSnapPoint = [&](double t, const Track* track)
86 {
87 if(t > range.second || t < range.first)
88 return;
89
90 for(const auto& snapPoint : result)
91 if(snapPoint.t == t)
92 return;
93 result.emplace_back(t, track);
94 };
95
96 if(const auto trackList = currentTrack->GetOwner())
97 {
98 for(const auto track : trackList->Any())
99 {
100 const auto isSameTrack = (track == currentTrack) ||
101 (track->GetLinkType() == Track::LinkType::Aligned && *trackList->FindLeader(currentTrack) == track) ||
102 (currentTrack->GetLinkType() == Track::LinkType::Aligned && *trackList->FindLeader(track) == currentTrack);
103 for(const auto& interval : track->GetIntervals())
104 {
105 if(isSameTrack)
106 {
107 auto waveTrackIntervalData = dynamic_cast<WaveTrack::IntervalData*>(interval.Extra());
108 if(waveTrackIntervalData->GetClip().get() == adjustedClip)
109 //exclude boundaries of the adjusted clip
110 continue;
111 }
112 addSnapPoint(interval.Start(), track);
113 if(interval.Start() != interval.End())
114 addSnapPoint(interval.End(), track);
115 }
116 }
117 }
118 return result;
119 }
120
121public:
123 const std::shared_ptr<WaveTrack>& track,
124 const std::shared_ptr<WaveClip>& clip,
125 bool leftBorder,
126 const ZoomInfo& zoomInfo)
127 : mTrack(track),
128 mAdjustingLeftBorder(leftBorder)
129 {
130 auto clips = track->GetClips();
131
132 wxASSERT(std::find(clips.begin(), clips.end(), clip) != clips.end());
133
134 if (track->IsAlignedWithLeader() || track->GetLinkType() == Track::LinkType::Aligned)
135 //find clips in other channels which are also should be trimmed
136 mClips = FindClipsInChannels(clip->GetPlayStartTime(), clip->GetPlayEndTime(), track.get());
137 else
138 mClips.push_back(clip);
139
141 {
142 auto left = clip->GetSequenceStartTime();
143 for (const auto& other : clips)
144 if (other->GetPlayStartTime() < clip->GetPlayStartTime() && other->GetPlayEndTime() > left)
145 left = other->GetPlayEndTime();
146 //not less than 1 sample length
147 mRange = std::make_pair(left, clip->GetPlayEndTime() - 1.0 / clip->GetRate());
148
149 mInitialBorderPosition = mClips[0]->GetPlayStartTime();
150 }
151 else
152 {
153 auto right = clip->GetSequenceEndTime();
154 for (const auto& other : clips)
155 if (other->GetPlayStartTime() > clip->GetPlayStartTime() && other->GetPlayStartTime() < right)
156 right = other->GetPlayStartTime();
157 //not less than 1 sample length
158 mRange = std::make_pair(clip->GetPlayStartTime() + 1.0 / clip->GetRate(), right);
159
160 mInitialBorderPosition = mClips[0]->GetPlayEndTime();
161 }
162
163 if(const auto trackList = track->GetOwner())
164 {
165 mSnapManager = std::make_unique<SnapManager>(
166 *trackList->GetOwner(),
167 FindSnapPoints(track.get(), clip.get(), mRange),
168 zoomInfo);
169 }
170 }
171
172 bool Init(const TrackPanelMouseEvent& event) override
173 {
174 if (event.event.LeftDown())
175 {
176 mDragStartX = event.event.GetX();
177 return true;
178 }
179 return false;
180 }
181
183 {
184 const auto eventX = event.event.GetX();
185 const auto dx = eventX - mDragStartX;
186
187 const auto& viewInfo = ViewInfo::Get(project);
188
189 const auto eventT = viewInfo.PositionToTime(viewInfo.TimeToPosition(mInitialBorderPosition, event.rect.x) + dx, event.rect.x);
190
191 const auto offset = sampleCount(floor((eventT - mInitialBorderPosition) * mClips[0]->GetRate())).as_double() / mClips[0]->GetRate();
192 const auto t = std::clamp(mInitialBorderPosition + offset, mRange.first, mRange.second);
193 const auto wasSnapped = mSnap.Snapped();
194 if(mSnapManager)
195 mSnap = mSnapManager->Snap(mTrack.get(), t, !mAdjustingLeftBorder);
196 if(mSnap.Snapped())
197 {
198 if(mSnap.outTime >= mRange.first && mSnap.outTime <= mRange.second)
199 {
200 //Make sure that outTime belongs to the adjustment range after snapping
203 }
204 else
205 {
206 //Otherwise snapping cannot be performed
207 mSnap = {};
208 TrimTo(t);
209 }
210 }
211 else
212 TrimTo(t);
213 //If there was a snap line, make sure it is removed
214 //from the screen by redrawing whole TrackPanel
216 }
217
218 void Finish(AudacityProject& project) override
219 {
220 if (mClips[0]->GetPlayStartTime() != mInitialBorderPosition)
221 {
223 {
224 auto dt = std::abs(mClips[0]->GetPlayStartTime() - mInitialBorderPosition);
225 ProjectHistory::Get(project).PushState(XO("Clip-Trim-Left"),
226 XO("Moved by %.02f").Format(dt), UndoPush::CONSOLIDATE);
227 }
228 else
229 {
230 auto dt = std::abs(mInitialBorderPosition - mClips[0]->GetPlayEndTime());
231 ProjectHistory::Get(project).PushState(XO("Clip-Trim-Right"),
232 XO("Moved by %.02f").Format(dt), UndoPush::CONSOLIDATE);
233 }
234 }
235 }
236
237 void Cancel() override
238 {
240 }
241
242 void Draw(TrackPanelDrawingContext& context, const wxRect& rect, unsigned iPass) override
243 {
244 if(iPass == TrackArtist::PassSnapping && mSnap.Snapped())
245 {
246 auto &dc = context.dc;
247 SnapManager::Draw(&dc, rect.x + mSnap.outCoord, -1);
248 }
249 }
250
251 wxRect DrawingArea(TrackPanelDrawingContext&, const wxRect& rect, const wxRect& panelRect, unsigned iPass) override
252 {
253 if(iPass == TrackArtist::PassSnapping)
254 return MaximizeHeight(rect, panelRect);
255 return rect;
256 }
257};
258
260{
261 std::pair<double, double> mRange;
262 std::vector<std::shared_ptr<WaveClip>> mLeftClips;
263 std::vector<std::shared_ptr<WaveClip>> mRightClips;
266
267 void TrimTo(double t)
268 {
269 t = std::clamp(t, mRange.first, mRange.second);
270
271 for (auto& clip : mLeftClips)
272 clip->TrimRightTo(t);
273 for (auto& clip : mRightClips)
274 clip->TrimLeftTo(t);
275 }
276
277public:
279 WaveTrack* track,
280 std::shared_ptr<WaveClip>& leftClip,
281 std::shared_ptr<WaveClip>& rightClip)
282 {
283 auto clips = track->GetClips();
284
285 wxASSERT(std::find(clips.begin(), clips.end(), leftClip) != clips.end());
286 wxASSERT(std::find(clips.begin(), clips.end(), rightClip) != clips.end());
287
288 if (track->IsAlignedWithLeader() || track->GetLinkType() == Track::LinkType::Aligned)
289 {
290 //find clips in other channels which are also should be trimmed
291 mLeftClips = FindClipsInChannels(leftClip->GetPlayStartTime(), leftClip->GetPlayEndTime(), track);
292 mRightClips = FindClipsInChannels(rightClip->GetPlayStartTime(), rightClip->GetPlayEndTime(), track);
293 }
294 else
295 {
296 mLeftClips.push_back(leftClip);
297 mRightClips.push_back(rightClip);
298 }
299
300 mRange = std::make_pair(
301 //not less than 1 sample length
302 mLeftClips[0]->GetPlayStartTime() + 1.0 / mLeftClips[0]->GetRate(),
303 mRightClips[0]->GetPlayEndTime() - 1.0 / mRightClips[0]->GetRate()
304 );
305 mInitialBorderPosition = mRightClips[0]->GetPlayStartTime();
306 }
307
308 bool Init(const TrackPanelMouseEvent& event) override
309 {
310 if (event.event.LeftDown())
311 {
312 mDragStartX = event.event.GetX();
313 return true;
314 }
315 return false;
316 }
317
319 {
320 const auto newX = event.event.GetX();
321 const auto dx = newX - mDragStartX;
322
323 auto& viewInfo = ViewInfo::Get(project);
324
325 auto eventT = viewInfo.PositionToTime(viewInfo.TimeToPosition(mInitialBorderPosition, event.rect.x) + dx, event.rect.x);
326 auto offset = sampleCount(
327 floor(
328 (eventT - mInitialBorderPosition) * mLeftClips[0]->GetRate()
329 )
330 ).as_double() / mLeftClips[0]->GetRate();
331
333
335 }
336
337 void Finish(AudacityProject& project) override
338 {
339 if (mRightClips[0]->GetPlayStartTime() != mInitialBorderPosition)
340 {
341 auto dt = std::abs(mRightClips[0]->GetPlayStartTime() - mInitialBorderPosition);
342 ProjectHistory::Get(project).PushState(XO("Clip-Trim-Between"),
343 XO("Moved by %.02f").Format(dt), UndoPush::CONSOLIDATE);
344 }
345 }
346
347 void Cancel() override
348 {
350 }
351};
352
353
355{
356 static auto disabledCursor =
357 ::MakeCursor(wxCURSOR_NO_ENTRY, DisabledCursorXpm, 16, 16);
358 static auto slideCursor =
359 MakeCursor(wxCURSOR_SIZEWE, TimeCursorXpm, 16, 16);
360 auto message = XO("Click and drag to move clip boundary in time");
361
362 return {
363 message,
364 (unsafe
365 ? &*disabledCursor
366 : &*slideCursor)
367 };
368}
369
370
371WaveClipTrimHandle::WaveClipTrimHandle(std::unique_ptr<ClipTrimPolicy>& clipTrimPolicy)
372 : mClipTrimPolicy{ std::move(clipTrimPolicy) }
373{
374
375}
376
378
379wxRect WaveClipTrimHandle::ClipTrimPolicy::DrawingArea(TrackPanelDrawingContext&, const wxRect& rect, const wxRect&, unsigned)
380{
381 return rect;
382}
383
385 std::weak_ptr<WaveClipTrimHandle>& holder,
386 const std::shared_ptr<WaveTrack>& waveTrack,
387 const AudacityProject* pProject,
388 const TrackPanelMouseState& state)
389{
390 const auto rect = state.rect;
391
392 auto px = state.state.m_x;
393
394 auto& zoomInfo = ViewInfo::Get(*pProject);
395
396 std::shared_ptr<WaveClip> leftClip;
397 std::shared_ptr<WaveClip> rightClip;
398
399 //Test left and right boundaries of each clip
400 //to determine which type of trimming should be applied
401 //and input for the policy
402 for (auto& clip : waveTrack->GetClips())
403 {
404 if (!WaveTrackView::ClipDetailsVisible(*clip, zoomInfo, rect))
405 continue;
406
407 auto clipRect = ClipParameters::GetClipRect(*clip.get(), zoomInfo, rect);
408
409 //double the hit testing area in case if clip are close to each other
410 if (std::abs(px - clipRect.GetLeft()) <= BoundaryThreshold * 2)
411 rightClip = clip;
412 else if (std::abs(px - clipRect.GetRight()) <= BoundaryThreshold * 2)
413 leftClip = clip;
414 }
415
416 std::unique_ptr<ClipTrimPolicy> clipTrimPolicy;
417 if (leftClip && rightClip)
418 {
419 //between adjacent clips
420 if(ClipParameters::GetClipRect(*leftClip, zoomInfo, rect).GetRight() > px)
421 clipTrimPolicy = std::make_unique<AdjustBorder>(waveTrack, leftClip, false, zoomInfo);//right border
422 else
423 clipTrimPolicy = std::make_unique<AdjustBorder>(waveTrack, rightClip, true, zoomInfo);//left border
424 }
425 else
426 {
427 auto clip = leftClip ? leftClip : rightClip;
428 if (clip)
429 {
430 //single clip case, determine the border,
431 //hit testing area differs from one
432 //used for general case
433 auto clipRect = ClipParameters::GetClipRect(*clip.get(), zoomInfo, rect);
434 if (std::abs(px - clipRect.GetLeft()) <= BoundaryThreshold)
435 clipTrimPolicy = std::make_unique<AdjustBorder>(waveTrack, clip, true, zoomInfo);
436 else if (std::abs(px - clipRect.GetRight()) <= BoundaryThreshold)
437 clipTrimPolicy = std::make_unique<AdjustBorder>(waveTrack, clip, false, zoomInfo);
438 }
439 }
440
441 if(clipTrimPolicy)
442 return AssignUIHandlePtr(
443 holder,
444 std::make_shared<WaveClipTrimHandle>(clipTrimPolicy)
445 );
446 return { };
447}
448
449UIHandlePtr WaveClipTrimHandle::HitTest(std::weak_ptr<WaveClipTrimHandle>& holder,
450 WaveTrackView& view, const AudacityProject* pProject,
451 const TrackPanelMouseState& state)
452{
453 auto waveTrack = std::dynamic_pointer_cast<WaveTrack>(view.FindTrack());
454 //For multichannel tracks, show trim handle only for the leader track
455 if (!waveTrack->IsLeader() && waveTrack->IsAlignedWithLeader())
456 return {};
457
458 std::vector<UIHandlePtr> results;
459
460 const auto rect = state.rect;
461
462 auto px = state.state.m_x;
463 auto py = state.state.m_y;
464
465 if (py >= rect.GetTop() &&
466 py <= (rect.GetTop() + static_cast<int>(rect.GetHeight() * 0.3)))
467 {
468 return HitAnywhere(holder, waveTrack, pProject, state);
469 }
470 return {};
471}
472
473
474
476{
477 const bool unsafe = ProjectAudioIO::Get(*pProject).IsAudioActive();
478 return HitPreview(pProject, unsafe);
479}
480
482(const TrackPanelMouseEvent& event, AudacityProject* pProject)
483{
484 if (!ProjectAudioIO::Get(*pProject).IsAudioActive())
485 {
486 if (mClipTrimPolicy->Init(event))
488 }
490}
491
493(const TrackPanelMouseEvent& event, AudacityProject* project)
494{
495 return mClipTrimPolicy->Trim(event, *project);
496}
497
499(const TrackPanelMouseEvent& event, AudacityProject* project,
500 wxWindow* pParent)
501{
502 mClipTrimPolicy->Finish(*project);
504}
505
507{
508 mClipTrimPolicy->Cancel();
510}
511
512void WaveClipTrimHandle::Draw(TrackPanelDrawingContext& context, const wxRect& rect, unsigned iPass)
513{
514 mClipTrimPolicy->Draw(context, rect, iPass);
515}
516
518 const wxRect& panelRect, unsigned iPass)
519{
520 return mClipTrimPolicy->DrawingArea(context, rect, panelRect, iPass);
521}
std::shared_ptr< UIHandle > UIHandlePtr
Definition: CellularPanel.h:28
XO("Cut/Copy/Paste")
std::vector< SnapPoint > SnapPointArray
Definition: Snap.h:43
std::unique_ptr< wxCursor > MakeCursor(int WXUNUSED(CursorId), const char *const pXpm[36], int HotX, int HotY)
Definition: TrackPanel.cpp:186
std::shared_ptr< Subclass > AssignUIHandlePtr(std::weak_ptr< Subclass > &holder, const std::shared_ptr< Subclass > &pNew)
Definition: UIHandle.h:151
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:90
std::shared_ptr< Track > FindTrack()
Abstract base class used in importing a file.
bool IsAudioActive() const
static ProjectAudioIO & Get(AudacityProject &project)
void PushState(const TranslatableString &desc, const TranslatableString &shortDesc)
static ProjectHistory & Get(AudacityProject &project)
static void Draw(wxDC *dc, wxInt64 snap0, wxInt64 snap1)
Definition: Snap.cpp:306
Abstract base class for an object holding data associated with points on a time axis.
Definition: Track.h:162
std::shared_ptr< TrackList > GetOwner() const
Definition: Track.h:344
LinkType GetLinkType() const noexcept
Definition: Track.cpp:1245
bool IsAlignedWithLeader() const
Returns true if the leader track has link type LinkType::Aligned.
Definition: Track.cpp:1250
static auto Channels(TrackType *pTrack) -> TrackIterRange< TrackType >
Definition: Track.h:1417
static wxRect MaximizeHeight(const wxRect &rect, const wxRect &panelRect)
unsigned Result
Definition: UIHandle.h:38
static ViewInfo & Get(AudacityProject &project)
Definition: ViewInfo.cpp:235
This allows multiple clips to be a part of one WaveTrack.
Definition: WaveClip.h:101
AdjustBetweenBorders(WaveTrack *track, std::shared_ptr< WaveClip > &leftClip, std::shared_ptr< WaveClip > &rightClip)
std::vector< std::shared_ptr< WaveClip > > mLeftClips
bool Init(const TrackPanelMouseEvent &event) override
UIHandle::Result Trim(const TrackPanelMouseEvent &event, AudacityProject &project) override
void Finish(AudacityProject &project) override
std::vector< std::shared_ptr< WaveClip > > mRightClips
UIHandle::Result Trim(const TrackPanelMouseEvent &event, AudacityProject &project) override
std::vector< std::shared_ptr< WaveClip > > mClips
void Finish(AudacityProject &project) override
std::unique_ptr< SnapManager > mSnapManager
AdjustBorder(const std::shared_ptr< WaveTrack > &track, const std::shared_ptr< WaveClip > &clip, bool leftBorder, const ZoomInfo &zoomInfo)
wxRect DrawingArea(TrackPanelDrawingContext &, const wxRect &rect, const wxRect &panelRect, unsigned iPass) override
static SnapPointArray FindSnapPoints(const WaveTrack *currentTrack, WaveClip *adjustedClip, const std::pair< double, double > range)
bool Init(const TrackPanelMouseEvent &event) override
std::pair< double, double > mRange
std::shared_ptr< WaveTrack > mTrack
void Draw(TrackPanelDrawingContext &context, const wxRect &rect, unsigned iPass) override
virtual wxRect DrawingArea(TrackPanelDrawingContext &, const wxRect &rect, const wxRect &panelRect, unsigned iPass)
virtual void Draw(TrackPanelDrawingContext &context, const wxRect &rect, unsigned iPass)
Result Release(const TrackPanelMouseEvent &event, AudacityProject *pProject, wxWindow *pParent) override
Result Cancel(AudacityProject *pProject) override
static HitTestPreview HitPreview(const AudacityProject *, bool unsafe)
Result Drag(const TrackPanelMouseEvent &event, AudacityProject *pProject) override
WaveClipTrimHandle(std::unique_ptr< ClipTrimPolicy > &clipTrimPolicy)
Result Click(const TrackPanelMouseEvent &event, AudacityProject *pProject) override
HitTestPreview Preview(const TrackPanelMouseState &mouseState, AudacityProject *pProject) override
wxRect DrawingArea(TrackPanelDrawingContext &, const wxRect &rect, const wxRect &panelRect, unsigned iPass) override
std::unique_ptr< ClipTrimPolicy > mClipTrimPolicy
static UIHandlePtr HitTest(std::weak_ptr< WaveClipTrimHandle > &holder, WaveTrackView &view, const AudacityProject *pProject, const TrackPanelMouseState &state)
static UIHandlePtr HitAnywhere(std::weak_ptr< WaveClipTrimHandle > &holder, const std::shared_ptr< WaveTrack > &waveTrack, const AudacityProject *pProject, const TrackPanelMouseState &state)
static constexpr int BoundaryThreshold
void Draw(TrackPanelDrawingContext &context, const wxRect &rect, unsigned iPass) override
A Track that contains audio waveform data.
Definition: WaveTrack.h:51
WaveClipHolders & GetClips()
Definition: WaveTrack.h:322
static bool ClipDetailsVisible(const WaveClip &clip, const ZoomInfo &zoomInfo, const wxRect &viewRect)
Positions or offsets within audio files need a wide type.
Definition: SampleCount.h:19
double as_double() const
Definition: SampleCount.h:46
auto end(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:159
double GetRate(const Track &track)
Definition: TimeTrack.cpp:179
std::vector< std::shared_ptr< WaveClip > > FindClipsInChannels(double start, double end, WaveTrack *track)
STL namespace.
static wxRect GetClipRect(const WaveClip &clip, const ZoomInfo &zoomInfo, const wxRect &viewRect, bool *outShowSamples=nullptr)
double outTime
Definition: Snap.h:47
wxInt64 outCoord
Definition: Snap.h:48
bool Snapped() const
Definition: Snap.h:52