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