Audacity 3.2.0
WaveClipAdjustBorderHandle.cpp
Go to the documentation of this file.
1/*!********************************************************************
2*
3 Audacity: A Digital Audio Editor
4
5 WaveClipAdjustBorderHandle.cpp
6
7 Vitaly Sverchinsky
8
9 **********************************************************************/
10
12#include "ProjectAudioIO.h"
13#include "RefreshCode.h"
14
15#include <wx/event.h>
16
17#include "../../../../TrackArt.h"
18#include "../../../../TrackArtist.h"
19#include "Snap.h"
20#include "../../../../TrackPanelDrawingContext.h"
21#include "../../../../../images/Cursors.h"
22#include "WaveClip.h"
23#include "WaveTrack.h"
24#include "WaveChannelView.h"
25#include "HitTestResult.h"
27#include "ViewInfo.h"
28#include "ProjectHistory.h"
29#include "UndoManager.h"
30#include "WaveClipUtilities.h"
31
32namespace {
33
34 void TrimLeftTo(WaveTrack::Interval& interval, double t)
35 {
36 interval.TrimLeftTo(t);
37 }
38
39 void TrimRightTo(WaveTrack::Interval& interval, double t)
40 {
41 interval.TrimRightTo(t);
42 }
43
44 void StretchLeftTo(WaveTrack::Interval& interval, double t)
45 {
46 interval.StretchLeftTo(t);
47 }
48
49 void StretchRightTo(WaveTrack::Interval& interval, double t)
50 {
51 interval.StretchRightTo(t);
52 }
53}
54
55//Different policies implement different adjustment scenarios
57 {
58 public:
59 virtual ~AdjustPolicy();
60
61 virtual bool Init(const TrackPanelMouseEvent& event) = 0;
63 virtual void Finish(AudacityProject& project) = 0;
64 virtual void Cancel() = 0;
65
66 virtual std::shared_ptr<Track> FindTrack() const = 0;
67
68 virtual void Draw(
70 const wxRect &rect,
71 unsigned iPass);
72
73 virtual wxRect DrawingArea(
75 const wxRect &rect,
76 const wxRect &panelRect,
77 unsigned iPass);
78 };
79
81
82namespace {
84 const WaveTrack& track,
85 bool adjustingLeftBorder,
86 bool isStretchMode)
87{
88 if (!adjustingLeftBorder)
89 return interval.Start() + 1.0 / track.GetRate();
90
91 const auto prevInterval = track.GetNextInterval(interval, PlaybackDirection::backward);
92 if(isStretchMode)
93 return prevInterval ? prevInterval->End() :
94 std::numeric_limits<double>::lowest();
95 if(prevInterval)
96 return std::max(interval.GetClip(0)->GetSequenceStartTime(),
97 prevInterval->End());
98 return interval.GetClip(0)->GetSequenceStartTime();
99}
100
102 const WaveTrack::Interval& interval, const WaveTrack& track, bool adjustingLeftBorder,
103 bool isStretchMode)
104{
105 if (adjustingLeftBorder)
106 return interval.End() - 1.0 / track.GetRate();
107
108 const auto nextInterval = track.GetNextInterval(interval, PlaybackDirection::forward);
109 if (isStretchMode)
110 return nextInterval ? nextInterval->Start() :
111 std::numeric_limits<double>::max();
112
113 if(nextInterval)
114 return std::min(interval.GetClip(0)->GetSequenceEndTime(),
115 nextInterval->Start());
116 return interval.GetClip(0)->GetSequenceEndTime();
117}
118} // namespace
119
121{
122public:
123 using AdjustHandler = std::function<void(WaveTrack::Interval&, double)>;
124
125private:
126 std::shared_ptr<WaveTrack> mTrack;
127 std::shared_ptr<WaveTrack::Interval> mInterval;
130 const bool mIsStretchMode;
133 const std::pair<double, double> mRange;
135
136 std::unique_ptr<SnapManager> mSnapManager;
138
139 void TrimTo(double t)
140 {
141 mBorderPosition = std::clamp(t, mRange.first, mRange.second);
143 }
144
145 //Search for a good snap points among all tracks except
146 //one to which moving interval belongs to
148 const WaveTrack* currentTrack,
149 const std::pair<double, double> range)
150 {
151 SnapPointArray result;
152
153 auto addSnapPoint = [&](double t, const Track* track)
154 {
155 if(t > range.second || t < range.first)
156 return;
157
158 for(const auto& snapPoint : result)
159 if(snapPoint.t == t)
160 return;
161 result.emplace_back(t, track);
162 };
163
164 if(const auto trackList = currentTrack->GetOwner())
165 {
166 for(const auto track : as_const(*trackList))
167 {
168 if(track == currentTrack)
169 {
170 //skip track that interval belongs to
171 continue;
172 }
173
174 for(const auto& interval : track->Intervals())
175 {
176 addSnapPoint(interval->Start(), track);
177 if(interval->Start() != interval->End())
178 addSnapPoint(interval->End(), track);
179 }
180 }
181 }
182 return result;
183 }
184
185public:
187 std::shared_ptr<WaveTrack> track,
188 std::shared_ptr<WaveTrack::Interval> interval,
189 bool adjustLeftBorder,
190 bool isStretchMode,
191 const ZoomInfo& zoomInfo)
192 : mTrack { std::move(track) }
193 , mInterval { std::move(interval) }
194 , mAdjustingLeftBorder { adjustLeftBorder }
195 , mIsStretchMode { isStretchMode }
196 , mInitialBorderPosition { adjustLeftBorder ? mInterval->Start() :
197 mInterval->End() }
199 , mRange { GetLeftAdjustLimit( *mInterval, *mTrack, adjustLeftBorder, isStretchMode),
200 GetRightAdjustLimit(*mInterval, *mTrack, adjustLeftBorder, isStretchMode) }
201 , mAdjustHandler { std::move(adjustHandler) }
202 {
203 assert(mRange.first <= mRange.second);
204 if(const auto trackList = mTrack->GetOwner())
205 {
206 mSnapManager = std::make_unique<SnapManager>(
207 *trackList->GetOwner(),
209 zoomInfo);
210 }
211 }
212
213 std::shared_ptr<Track> FindTrack() const override
214 {
215 return mTrack;
216 }
217
218 bool Init(const TrackPanelMouseEvent& event) override
219 {
220 if (event.event.LeftDown())
221 {
222 mDragStartX = event.event.GetX();
223 return true;
224 }
225 return false;
226 }
227
229 {
230 const auto eventX = event.event.GetX();
231 const auto dx = eventX - mDragStartX;
232
233 const auto& viewInfo = ViewInfo::Get(project);
234
235 const auto eventT = viewInfo.PositionToTime(
236 viewInfo.TimeToPosition(mInitialBorderPosition, event.rect.x) + dx,
237 event.rect.x
238 );
239
240 const auto offset = sampleCount(floor((eventT - mInitialBorderPosition) * mTrack->GetRate())).as_double()
241 / mTrack->GetRate();
242 const auto t = std::clamp(mInitialBorderPosition + offset, mRange.first, mRange.second);
243 const auto wasSnapped = mSnap.Snapped();
244 if(mSnapManager)
245 mSnap = mSnapManager->Snap(mTrack.get(), t, !mAdjustingLeftBorder);
246 if(mSnap.Snapped())
247 {
248 if (mSnap.outTime >= mRange.first && mSnap.outTime <= mRange.second)
249 {
250 //Make sure that outTime belongs to the adjustment range after snapping
253 }
254 mSnap = {};
255 }
256 TrimTo(t);
257 //If there was a snap line, make sure it is removed
258 //from the screen by redrawing whole TrackPanel
260 }
261
263 {
264 const auto dt = std::abs(mInitialBorderPosition - mBorderPosition);
265 if (dt != 0)
266 {
267 if (mIsStretchMode)
268 {
270 project, 100.0 / mInterval->GetStretchRatio());
271 }
272 else if (mAdjustingLeftBorder)
273 {
274 /*i18n-hint: This is about trimming a clip, a length in seconds like "2.4 seconds" is shown*/
275 ProjectHistory::Get(project).PushState(XO("Adjust left trim by %.02f seconds").Format(dt),
276 /*i18n-hint: This is about trimming a clip, a length in seconds like "2.4s" is shown*/
277 XO("Trim by %.02fs").Format(dt));
278 }
279 else
280 {
281 /*i18n-hint: This is about trimming a clip, a length in seconds like "2.4 seconds" is shown*/
282 ProjectHistory::Get(project).PushState(XO("Adjust right trim by %.02f seconds").Format(dt),
283 /*i18n-hint: This is about trimming a clip, a length in seconds like "2.4s" is shown*/
284 XO("Trim by %.02fs").Format(dt));
285 }
286 }
287 }
288
289 void Cancel() override
290 {
292 }
293
294 void Draw(TrackPanelDrawingContext& context, const wxRect& rect, unsigned iPass) override
295 {
296 if(iPass == TrackArtist::PassSnapping && mSnap.Snapped())
297 {
298 auto &dc = context.dc;
299 TrackArt::DrawSnapLines(&dc, rect.x + mSnap.outCoord, -1);
300 }
301 }
302
303 wxRect DrawingArea(TrackPanelDrawingContext&, const wxRect& rect, const wxRect& panelRect, unsigned iPass) override
304 {
305 if(iPass == TrackArtist::PassSnapping)
306 return TrackPanelDrawable::MaximizeHeight(rect, panelRect);
307 return rect;
308 }
309};
310
312{
313 static auto disabledCursor =
314 MakeCursor(wxCURSOR_NO_ENTRY, DisabledCursorXpm, 16, 16);
315 static auto trimCursorLeft =
316 MakeCursor(wxCURSOR_SIZEWE, ClipTrimLeftXpm , 16, 16);
317 static auto trimCursorRight =
318 MakeCursor(wxCURSOR_SIZEWE, ClipTrimRightXpm, 16, 16);
319 auto message = XO("Click and drag to move clip boundary in time");
320
321 return {
322 message,
323 (unsafe
324 ? &*disabledCursor
325 : &*(isLeftBorder ? trimCursorLeft : trimCursorRight))
326 };
327}
328
330{
331 static auto disabledCursor =
332 MakeCursor(wxCURSOR_NO_ENTRY, DisabledCursorXpm, 16, 16);
333 static auto stretchCursorLeft =
334 MakeCursor(wxCURSOR_SIZEWE, ClipStretchLeftXpm, 16, 16);
335 static auto stretchCursorRight =
336 MakeCursor(wxCURSOR_SIZEWE, ClipStretchRightXpm, 16, 16);
337 auto message = XO("Click and drag to stretch clip");
338
339 return {
340 message,
341 (unsafe
342 ? &*disabledCursor
343 : &*(isLeftBorder ? stretchCursorLeft : stretchCursorRight))
344 };
345}
346
348 std::unique_ptr<AdjustPolicy>& adjustPolicy,
349 std::shared_ptr<const WaveTrack> pTrack,
350 bool stretchMode,
351 bool leftBorder)
352 : mAdjustPolicy{ std::move(adjustPolicy) }
353 , mpTrack{ move(pTrack) }
354 , mIsStretchMode{stretchMode}
355 , mIsLeftBorder{leftBorder}
356{
357
358}
359
361
362std::shared_ptr<const Channel> WaveClipAdjustBorderHandle::FindChannel() const
363{
364 return mpTrack;
365}
366
368
370
371void WaveClipAdjustBorderHandle::AdjustPolicy::Draw(TrackPanelDrawingContext&, const wxRect&, unsigned) { }
372
373wxRect WaveClipAdjustBorderHandle::AdjustPolicy::DrawingArea(TrackPanelDrawingContext&, const wxRect& rect, const wxRect&, unsigned)
374{
375 return rect;
376}
377
379 std::weak_ptr<WaveClipAdjustBorderHandle>& holder,
380 const std::shared_ptr<WaveTrack>& waveTrack,
381 const AudacityProject* pProject,
382 const TrackPanelMouseState& state)
383{
384 assert(waveTrack->IsLeader());
385
386 const auto rect = state.rect;
387
388 const auto px = state.state.m_x;
389
390 auto& zoomInfo = ViewInfo::Get(*pProject);
391
392 std::shared_ptr<WaveTrack::Interval> leftInterval;
393 std::shared_ptr<WaveTrack::Interval> rightInterval;
394
395 //Test left and right boundaries of each clip
396 //to determine which kind of adjustment is
397 //more appropriate
398 for(const auto& interval : waveTrack->Intervals())
399 {
400 const auto& clip = *interval->GetClip(0);
401 if(!WaveChannelView::ClipDetailsVisible(clip, zoomInfo, rect))
402 continue;
403
404 auto clipRect = ClipParameters::GetClipRect(clip, zoomInfo, rect);
405 if(std::abs(px - clipRect.GetLeft()) <= BoundaryThreshold * 2)
406 rightInterval = interval;
407 else if (std::abs(px - clipRect.GetRight()) <= BoundaryThreshold * 2)
408 leftInterval = interval;
409 }
410
411 std::shared_ptr<WaveTrack::Interval> adjustedInterval;
412 bool adjustLeftBorder {false};
413 if (leftInterval && rightInterval)
414 {
415 //between adjacent clips
416 if(ClipParameters::GetClipRect(*leftInterval->GetClip(0), zoomInfo, rect).GetRight() > px)
417 {
418 adjustedInterval = leftInterval;
419 adjustLeftBorder = false;
420 }
421 else
422 {
423 adjustedInterval = rightInterval;
424 adjustLeftBorder = true;
425 }
426 }
427 else
428 {
429 adjustedInterval = leftInterval ? leftInterval : rightInterval;
430 if (adjustedInterval)
431 {
432 //single clip case, determine the border,
433 //hit testing area differs from one
434 //used for general case
435 const auto clipRect = ClipParameters::GetClipRect(*adjustedInterval->GetClip(0), zoomInfo, rect);
436 if (std::abs(px - clipRect.GetLeft()) <= BoundaryThreshold)
437 adjustLeftBorder = true;
438 else if (std::abs(px - clipRect.GetRight()) <= BoundaryThreshold)
439 adjustLeftBorder = false;
440 else
441 adjustedInterval.reset();
442 }
443 }
444
445 if(adjustedInterval)
446 {
447 const auto isStretchMode = state.state.AltDown();
448 AdjustClipBorder::AdjustHandler adjustHandler = isStretchMode
449 ? (adjustLeftBorder ? StretchLeftTo : StretchRightTo)
450 : (adjustLeftBorder ? TrimLeftTo : TrimRightTo);
451
452 std::unique_ptr<AdjustPolicy> policy =
453 std::make_unique<AdjustClipBorder>(
454 adjustHandler, waveTrack, adjustedInterval, adjustLeftBorder,
455 isStretchMode, zoomInfo);
456
457 return AssignUIHandlePtr(
458 holder,
459 std::make_shared<WaveClipAdjustBorderHandle>(policy, waveTrack,
460 isStretchMode,
461 adjustLeftBorder));
462 }
463 return { };
464}
465
466UIHandlePtr WaveClipAdjustBorderHandle::HitTest(std::weak_ptr<WaveClipAdjustBorderHandle>& holder,
467 WaveChannelView& view, const AudacityProject* pProject,
468 const TrackPanelMouseState& state)
469{
470 auto waveTrack = std::dynamic_pointer_cast<WaveTrack>(view.FindTrack());
471 //For multichannel tracks, show adjustment handle only for the leader track
472 if (!waveTrack->IsLeader())
473 return {};
474
475 std::vector<UIHandlePtr> results;
476
477 const auto rect = state.rect;
478
479 auto py = state.state.m_y;
480
481 if (py >= rect.GetTop() &&
482 py <= (rect.GetTop() + static_cast<int>(rect.GetHeight() * 0.3)))
483 {
484 return HitAnywhere(holder, waveTrack, pProject, state);
485 }
486 return {};
487}
488
489
490
492{
493 const bool unsafe = ProjectAudioIO::Get(*pProject).IsAudioActive();
494 return mIsStretchMode
495 ? HitPreviewStretch(pProject, unsafe, mIsLeftBorder)
496 : HitPreviewTrim(pProject, unsafe, mIsLeftBorder);
497}
498
500(const TrackPanelMouseEvent& event, AudacityProject* pProject)
501{
502 if (!ProjectAudioIO::Get(*pProject).IsAudioActive())
503 {
504 if (mAdjustPolicy->Init(event))
506 }
508}
509
512{
513 return mAdjustPolicy->Drag(event, *project);
514}
515
518 wxWindow* pParent)
519{
520 mAdjustPolicy->Finish(*project);
522}
523
525{
526 mAdjustPolicy->Cancel();
528}
529
530void WaveClipAdjustBorderHandle::Draw(TrackPanelDrawingContext& context, const wxRect& rect, unsigned iPass)
531{
532 mAdjustPolicy->Draw(context, rect, iPass);
533}
534
536 const wxRect& panelRect, unsigned iPass)
537{
538 return mAdjustPolicy->DrawingArea(context, rect, panelRect, iPass);
539}
std::shared_ptr< UIHandle > UIHandlePtr
Definition: CellularPanel.h:28
int min(int a, int b)
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:188
std::shared_ptr< Subclass > AssignUIHandlePtr(std::weak_ptr< Subclass > &holder, const std::shared_ptr< Subclass > &pNew)
Definition: UIHandle.h:159
void PushClipSpeedChangedUndoState(AudacityProject &project, double speedInPercent)
std::shared_ptr< WaveTrack::Interval > mInterval
void Finish(AudacityProject &project) override
void Draw(TrackPanelDrawingContext &context, const wxRect &rect, unsigned iPass) override
const std::pair< double, double > mRange
std::shared_ptr< WaveTrack > mTrack
static SnapPointArray FindSnapPoints(const WaveTrack *currentTrack, const std::pair< double, double > range)
std::unique_ptr< SnapManager > mSnapManager
std::function< void(WaveTrack::Interval &, double)> AdjustHandler
UIHandle::Result Drag(const TrackPanelMouseEvent &event, AudacityProject &project) override
wxRect DrawingArea(TrackPanelDrawingContext &, const wxRect &rect, const wxRect &panelRect, unsigned iPass) override
std::shared_ptr< Track > FindTrack() const override
AdjustClipBorder(AdjustHandler adjustHandler, std::shared_ptr< WaveTrack > track, std::shared_ptr< WaveTrack::Interval > interval, bool adjustLeftBorder, bool isStretchMode, const ZoomInfo &zoomInfo)
bool Init(const TrackPanelMouseEvent &event) override
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:90
double End() const
Definition: Channel.h:42
double Start() const
Definition: Channel.h:41
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)
Abstract base class for an object holding data associated with points on a time axis.
Definition: Track.h:122
std::shared_ptr< TrackList > GetOwner() const
Definition: Track.h:254
static wxRect MaximizeHeight(const wxRect &rect, const wxRect &panelRect)
unsigned Result
Definition: UIHandle.h:39
static ViewInfo & Get(AudacityProject &project)
Definition: ViewInfo.cpp:235
static bool ClipDetailsVisible(const ClipTimes &clip, const ZoomInfo &zoomInfo, const wxRect &viewRect)
virtual UIHandle::Result Drag(const TrackPanelMouseEvent &event, AudacityProject &project)=0
virtual bool Init(const TrackPanelMouseEvent &event)=0
virtual wxRect DrawingArea(TrackPanelDrawingContext &, const wxRect &rect, const wxRect &panelRect, unsigned iPass)
virtual void Finish(AudacityProject &project)=0
virtual void Draw(TrackPanelDrawingContext &context, const wxRect &rect, unsigned iPass)
virtual std::shared_ptr< Track > FindTrack() const =0
std::unique_ptr< AdjustPolicy > mAdjustPolicy
std::shared_ptr< const Channel > FindChannel() const override
Result Drag(const TrackPanelMouseEvent &event, AudacityProject *pProject) override
HitTestPreview Preview(const TrackPanelMouseState &mouseState, AudacityProject *pProject) override
~WaveClipAdjustBorderHandle() override
wxRect DrawingArea(TrackPanelDrawingContext &, const wxRect &rect, const wxRect &panelRect, unsigned iPass) override
void Draw(TrackPanelDrawingContext &context, const wxRect &rect, unsigned iPass) override
static HitTestPreview HitPreviewTrim(const AudacityProject *, bool unsafe, bool isLeftBorder)
Result Release(const TrackPanelMouseEvent &event, AudacityProject *pProject, wxWindow *pParent) override
Result Cancel(AudacityProject *pProject) override
WaveClipAdjustBorderHandle(std::unique_ptr< AdjustPolicy > &adjustPolicy, std::shared_ptr< const WaveTrack > pTrack, bool stretchMode, bool leftBorder)
Result Click(const TrackPanelMouseEvent &event, AudacityProject *pProject) override
static HitTestPreview HitPreviewStretch(const AudacityProject *, bool unsafe, bool isLeftBorder)
static UIHandlePtr HitAnywhere(std::weak_ptr< WaveClipAdjustBorderHandle > &holder, const std::shared_ptr< WaveTrack > &waveTrack, const AudacityProject *pProject, const TrackPanelMouseState &state)
std::shared_ptr< const WaveTrack > mpTrack
static UIHandlePtr HitTest(std::weak_ptr< WaveClipAdjustBorderHandle > &holder, WaveChannelView &view, const AudacityProject *pProject, const TrackPanelMouseState &state)
std::shared_ptr< const WaveClip > GetClip(size_t iChannel) const
Definition: WaveTrack.h:994
void TrimRightTo(double t)
Definition: WaveTrack.cpp:223
void TrimLeftTo(double t)
Definition: WaveTrack.cpp:217
void StretchLeftTo(double t)
Definition: WaveTrack.cpp:253
void StretchRightTo(double t)
Definition: WaveTrack.cpp:259
A Track that contains audio waveform data.
Definition: WaveTrack.h:222
IntervalConstHolder GetNextInterval(const Interval &interval, PlaybackDirection searchDirection) const
Definition: WaveTrack.cpp:499
double GetRate() const override
Definition: WaveTrack.cpp:1085
Positions or offsets within audio files need a wide type.
Definition: SampleCount.h:19
double as_double() const
Definition: SampleCount.h:46
AUDACITY_DLL_API void DrawSnapLines(wxDC *dc, wxInt64 snap0, wxInt64 snap1)
Definition: TrackArt.cpp:821
void StretchRightTo(WaveTrack::Interval &interval, double t)
double GetRightAdjustLimit(const WaveTrack::Interval &interval, const WaveTrack &track, bool adjustingLeftBorder, bool isStretchMode)
void StretchLeftTo(WaveTrack::Interval &interval, double t)
double GetLeftAdjustLimit(const WaveTrack::Interval &interval, const WaveTrack &track, bool adjustingLeftBorder, bool isStretchMode)
void TrimLeftTo(WaveTrack::Interval &interval, double t)
void TrimRightTo(WaveTrack::Interval &interval, double t)
STL namespace.
static wxRect GetClipRect(const ClipTimes &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