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