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 "WaveChannelView.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 the adjusted clip belongs, 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 : as_const(*trackList).Leaders())
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->Intervals())
104 {
105 if(isSameTrack)
106 {
107 auto waveTrackIntervalData =
108 std::dynamic_pointer_cast<const WaveTrack::Interval>(
109 interval);
110 if(waveTrackIntervalData->GetClip(0).get() == adjustedClip)
111 //exclude boundaries of the adjusted clip
112 continue;
113 }
114 addSnapPoint(interval->Start(), track);
115 if(interval->Start() != interval->End())
116 addSnapPoint(interval->End(), track);
117 }
118 }
119 }
120 return result;
121 }
122
123public:
125 const std::shared_ptr<WaveTrack>& track,
126 const std::shared_ptr<WaveClip>& clip,
127 bool leftBorder,
128 const ZoomInfo& zoomInfo)
129 : mTrack(track),
130 mAdjustingLeftBorder(leftBorder)
131 {
132 auto clips = track->GetClips();
133
134 wxASSERT(std::find(clips.begin(), clips.end(), clip) != clips.end());
135
136 if (track->IsAlignedWithLeader() || track->GetLinkType() == Track::LinkType::Aligned)
137 //find clips in other channels which are also should be trimmed
138 mClips = FindClipsInChannels(clip->GetPlayStartTime(), clip->GetPlayEndTime(), track.get());
139 else
140 mClips.push_back(clip);
141
143 {
144 auto left = clip->GetSequenceStartTime();
145 for (const auto& other : clips)
146 if (other->GetPlayStartTime() < clip->GetPlayStartTime() && other->GetPlayEndTime() > left)
147 left = other->GetPlayEndTime();
148 //not less than 1 sample length
149 mRange = std::make_pair(left, clip->GetPlayEndTime() - 1.0 / clip->GetRate());
150
151 mInitialBorderPosition = mClips[0]->GetPlayStartTime();
152 }
153 else
154 {
155 auto right = clip->GetSequenceEndTime();
156 for (const auto& other : clips)
157 if (other->GetPlayStartTime() > clip->GetPlayStartTime() && other->GetPlayStartTime() < right)
158 right = other->GetPlayStartTime();
159 //not less than 1 sample length
160 mRange = std::make_pair(clip->GetPlayStartTime() + 1.0 / clip->GetRate(), right);
161
162 mInitialBorderPosition = mClips[0]->GetPlayEndTime();
163 }
164
165 if(const auto trackList = track->GetOwner())
166 {
167 mSnapManager = std::make_unique<SnapManager>(
168 *trackList->GetOwner(),
169 FindSnapPoints(track.get(), clip.get(), mRange),
170 zoomInfo);
171 }
172 }
173
174 bool Init(const TrackPanelMouseEvent& event) override
175 {
176 if (event.event.LeftDown())
177 {
178 mDragStartX = event.event.GetX();
179 return true;
180 }
181 return false;
182 }
183
185 {
186 const auto eventX = event.event.GetX();
187 const auto dx = eventX - mDragStartX;
188
189 const auto& viewInfo = ViewInfo::Get(project);
190
191 const auto eventT = viewInfo.PositionToTime(viewInfo.TimeToPosition(mInitialBorderPosition, event.rect.x) + dx, event.rect.x);
192
193 const auto offset = sampleCount(floor((eventT - mInitialBorderPosition) * mClips[0]->GetRate())).as_double() / mClips[0]->GetRate();
194 const auto t = std::clamp(mInitialBorderPosition + offset, mRange.first, mRange.second);
195 const auto wasSnapped = mSnap.Snapped();
196 if(mSnapManager)
197 mSnap = mSnapManager->Snap(mTrack.get(), t, !mAdjustingLeftBorder);
198 if(mSnap.Snapped())
199 {
200 if(mSnap.outTime >= mRange.first && mSnap.outTime <= mRange.second)
201 {
202 //Make sure that outTime belongs to the adjustment range after snapping
205 }
206 else
207 {
208 //Otherwise snapping cannot be performed
209 mSnap = {};
210 TrimTo(t);
211 }
212 }
213 else
214 TrimTo(t);
215 //If there was a snap line, make sure it is removed
216 //from the screen by redrawing whole TrackPanel
218 }
219
221 {
222 if (mClips[0]->GetPlayStartTime() != mInitialBorderPosition)
223 {
225 {
226 auto dt = std::abs(mClips[0]->GetPlayStartTime() - mInitialBorderPosition);
227 ProjectHistory::Get(project).PushState(XO("Clip-Trim-Left"),
228 XO("Moved by %.02f").Format(dt));
229 }
230 else
231 {
232 auto dt = std::abs(mInitialBorderPosition - mClips[0]->GetPlayEndTime());
233 ProjectHistory::Get(project).PushState(XO("Clip-Trim-Right"),
234 XO("Moved by %.02f").Format(dt));
235 }
236 }
237 }
238
239 void Cancel() override
240 {
242 }
243
244 void Draw(TrackPanelDrawingContext& context, const wxRect& rect, unsigned iPass) override
245 {
246 if(iPass == TrackArtist::PassSnapping && mSnap.Snapped())
247 {
248 auto &dc = context.dc;
249 SnapManager::Draw(&dc, rect.x + mSnap.outCoord, -1);
250 }
251 }
252
253 wxRect DrawingArea(TrackPanelDrawingContext&, const wxRect& rect, const wxRect& panelRect, unsigned iPass) override
254 {
255 if(iPass == TrackArtist::PassSnapping)
256 return MaximizeHeight(rect, panelRect);
257 return rect;
258 }
259};
260
262{
263 std::pair<double, double> mRange;
264 std::vector<std::shared_ptr<WaveClip>> mLeftClips;
265 std::vector<std::shared_ptr<WaveClip>> mRightClips;
268
269 void TrimTo(double t)
270 {
271 t = std::clamp(t, mRange.first, mRange.second);
272
273 for (auto& clip : mLeftClips)
274 clip->TrimRightTo(t);
275 for (auto& clip : mRightClips)
276 clip->TrimLeftTo(t);
277 }
278
279public:
281 WaveTrack* track,
282 std::shared_ptr<WaveClip>& leftClip,
283 std::shared_ptr<WaveClip>& rightClip)
284 {
285 auto clips = track->GetClips();
286
287 wxASSERT(std::find(clips.begin(), clips.end(), leftClip) != clips.end());
288 wxASSERT(std::find(clips.begin(), clips.end(), rightClip) != clips.end());
289
290 if (track->IsAlignedWithLeader() || track->GetLinkType() == Track::LinkType::Aligned)
291 {
292 //find clips in other channels which are also should be trimmed
293 mLeftClips = FindClipsInChannels(leftClip->GetPlayStartTime(), leftClip->GetPlayEndTime(), track);
294 mRightClips = FindClipsInChannels(rightClip->GetPlayStartTime(), rightClip->GetPlayEndTime(), track);
295 }
296 else
297 {
298 mLeftClips.push_back(leftClip);
299 mRightClips.push_back(rightClip);
300 }
301
302 mRange = std::make_pair(
303 //not less than 1 sample length
304 mLeftClips[0]->GetPlayStartTime() + 1.0 / mLeftClips[0]->GetRate(),
305 mRightClips[0]->GetPlayEndTime() - 1.0 / mRightClips[0]->GetRate()
306 );
307 mInitialBorderPosition = mRightClips[0]->GetPlayStartTime();
308 }
309
310 bool Init(const TrackPanelMouseEvent& event) override
311 {
312 if (event.event.LeftDown())
313 {
314 mDragStartX = event.event.GetX();
315 return true;
316 }
317 return false;
318 }
319
321 {
322 const auto newX = event.event.GetX();
323 const auto dx = newX - mDragStartX;
324
325 auto& viewInfo = ViewInfo::Get(project);
326
327 auto eventT = viewInfo.PositionToTime(viewInfo.TimeToPosition(mInitialBorderPosition, event.rect.x) + dx, event.rect.x);
328 auto offset = sampleCount(
329 floor(
330 (eventT - mInitialBorderPosition) * mLeftClips[0]->GetRate()
331 )
332 ).as_double() / mLeftClips[0]->GetRate();
333
335
337 }
338
340 {
341 if (mRightClips[0]->GetPlayStartTime() != mInitialBorderPosition)
342 {
343 auto dt = std::abs(mRightClips[0]->GetPlayStartTime() - mInitialBorderPosition);
344 ProjectHistory::Get(project).PushState(XO("Clip-Trim-Between"),
345 XO("Moved by %.02f").Format(dt));
346 }
347 }
348
349 void Cancel() override
350 {
352 }
353};
354
355
357{
358 static auto disabledCursor =
359 ::MakeCursor(wxCURSOR_NO_ENTRY, DisabledCursorXpm, 16, 16);
360 static auto slideCursor =
361 MakeCursor(wxCURSOR_SIZEWE, TimeCursorXpm, 16, 16);
362 auto message = XO("Click and drag to move clip boundary in time");
363
364 return {
365 message,
366 (unsafe
367 ? &*disabledCursor
368 : &*slideCursor)
369 };
370}
371
372
373WaveClipTrimHandle::WaveClipTrimHandle(std::unique_ptr<ClipTrimPolicy>& clipTrimPolicy)
374 : mClipTrimPolicy{ std::move(clipTrimPolicy) }
375{
376
377}
378
380
381wxRect WaveClipTrimHandle::ClipTrimPolicy::DrawingArea(TrackPanelDrawingContext&, const wxRect& rect, const wxRect&, unsigned)
382{
383 return rect;
384}
385
387 std::weak_ptr<WaveClipTrimHandle>& holder,
388 const std::shared_ptr<WaveTrack>& waveTrack,
389 const AudacityProject* pProject,
390 const TrackPanelMouseState& state)
391{
392 const auto rect = state.rect;
393
394 auto px = state.state.m_x;
395
396 auto& zoomInfo = ViewInfo::Get(*pProject);
397
398 std::shared_ptr<WaveClip> leftClip;
399 std::shared_ptr<WaveClip> rightClip;
400
401 //Test left and right boundaries of each clip
402 //to determine which type of trimming should be applied
403 //and input for the policy
404 for (auto& clip : waveTrack->GetClips())
405 {
406 if (!WaveChannelView::ClipDetailsVisible(*clip, zoomInfo, rect))
407 continue;
408
409 auto clipRect = ClipParameters::GetClipRect(*clip.get(), zoomInfo, rect);
410
411 //double the hit testing area in case if clip are close to each other
412 if (std::abs(px - clipRect.GetLeft()) <= BoundaryThreshold * 2)
413 rightClip = clip;
414 else if (std::abs(px - clipRect.GetRight()) <= BoundaryThreshold * 2)
415 leftClip = clip;
416 }
417
418 std::unique_ptr<ClipTrimPolicy> clipTrimPolicy;
419 if (leftClip && rightClip)
420 {
421 //between adjacent clips
422 if(ClipParameters::GetClipRect(*leftClip, zoomInfo, rect).GetRight() > px)
423 clipTrimPolicy = std::make_unique<AdjustBorder>(waveTrack, leftClip, false, zoomInfo);//right border
424 else
425 clipTrimPolicy = std::make_unique<AdjustBorder>(waveTrack, rightClip, true, zoomInfo);//left border
426 }
427 else
428 {
429 auto clip = leftClip ? leftClip : rightClip;
430 if (clip)
431 {
432 //single clip case, determine the border,
433 //hit testing area differs from one
434 //used for general case
435 auto clipRect = ClipParameters::GetClipRect(*clip.get(), zoomInfo, rect);
436 if (std::abs(px - clipRect.GetLeft()) <= BoundaryThreshold)
437 clipTrimPolicy = std::make_unique<AdjustBorder>(waveTrack, clip, true, zoomInfo);
438 else if (std::abs(px - clipRect.GetRight()) <= BoundaryThreshold)
439 clipTrimPolicy = std::make_unique<AdjustBorder>(waveTrack, clip, false, zoomInfo);
440 }
441 }
442
443 if(clipTrimPolicy)
444 return AssignUIHandlePtr(
445 holder,
446 std::make_shared<WaveClipTrimHandle>(clipTrimPolicy)
447 );
448 return { };
449}
450
451UIHandlePtr WaveClipTrimHandle::HitTest(std::weak_ptr<WaveClipTrimHandle>& holder,
452 WaveChannelView& view, const AudacityProject* pProject,
453 const TrackPanelMouseState& state)
454{
455 auto waveTrack = std::dynamic_pointer_cast<WaveTrack>(view.FindTrack());
456 //For multichannel tracks, show trim handle only for the leader track
457 if (!waveTrack->IsLeader() && waveTrack->IsAlignedWithLeader())
458 return {};
459
460 std::vector<UIHandlePtr> results;
461
462 const auto rect = state.rect;
463
464 auto px = state.state.m_x;
465 auto py = state.state.m_y;
466
467 if (py >= rect.GetTop() &&
468 py <= (rect.GetTop() + static_cast<int>(rect.GetHeight() * 0.3)))
469 {
470 return HitAnywhere(holder, waveTrack, pProject, state);
471 }
472 return {};
473}
474
475
476
478{
479 const bool unsafe = ProjectAudioIO::Get(*pProject).IsAudioActive();
480 return HitPreview(pProject, unsafe);
481}
482
484(const TrackPanelMouseEvent& event, AudacityProject* pProject)
485{
486 if (!ProjectAudioIO::Get(*pProject).IsAudioActive())
487 {
488 if (mClipTrimPolicy->Init(event))
490 }
492}
493
496{
497 return mClipTrimPolicy->Trim(event, *project);
498}
499
502 wxWindow* pParent)
503{
504 mClipTrimPolicy->Finish(*project);
506}
507
509{
510 mClipTrimPolicy->Cancel();
512}
513
514void WaveClipTrimHandle::Draw(TrackPanelDrawingContext& context, const wxRect& rect, unsigned iPass)
515{
516 mClipTrimPolicy->Draw(context, rect, iPass);
517}
518
520 const wxRect& panelRect, unsigned iPass)
521{
522 return mClipTrimPolicy->DrawingArea(context, rect, panelRect, iPass);
523}
std::shared_ptr< UIHandle > UIHandlePtr
Definition: CellularPanel.h:28
XO("Cut/Copy/Paste")
std::vector< SnapPoint > SnapPointArray
Definition: Snap.h:43
const auto project
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:304
Abstract base class for an object holding data associated with points on a time axis.
Definition: Track.h:120
std::shared_ptr< TrackList > GetOwner() const
Definition: Track.h:252
LinkType GetLinkType() const noexcept
Definition: Track.cpp:1252
bool IsAlignedWithLeader() const
Returns true if the leader track has link type LinkType::Aligned.
Definition: Track.cpp:1257
static auto Channels(TrackType *pTrack) -> TrackIterRange< TrackType >
Definition: Track.h:1217
static wxRect MaximizeHeight(const wxRect &rect, const wxRect &panelRect)
unsigned Result
Definition: UIHandle.h:38
static ViewInfo & Get(AudacityProject &project)
Definition: ViewInfo.cpp:235
static bool ClipDetailsVisible(const WaveClip &clip, const ZoomInfo &zoomInfo, const wxRect &viewRect)
This allows multiple clips to be a part of one WaveTrack.
Definition: WaveClip.h:102
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)
static UIHandlePtr HitTest(std::weak_ptr< WaveClipTrimHandle > &holder, WaveChannelView &view, const AudacityProject *pProject, const TrackPanelMouseState &state)
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 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:59
WaveClipHolders & GetClips()
Definition: WaveTrack.h:425
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:196
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