14#include "../../HitTestResult.h"
17#include "../../ProjectSettings.h"
18#include "../../RefreshCode.h"
22#include "../../TrackArt.h"
23#include "../../TrackArtist.h"
24#include "../../TrackPanelDrawingContext.h"
25#include "../../TrackPanelMouseEvent.h"
28#include "../../../images/Cursors.h"
37 if (
const auto pOwner = pTrack->GetOwner())
38 pTrack = (*pOwner->Find(pTrack.get()))->SharedPointer();
59#ifdef EXPERIMENTAL_TRACK_PANEL_HIGHLIGHTING
67 static auto disabledCursor =
68 ::MakeCursor(wxCURSOR_NO_ENTRY, DisabledCursorXpm, 16, 16);
69 static auto slideCursor =
70 MakeCursor(wxCURSOR_SIZEWE, TimeCursorXpm, 16, 16);
74 auto message =
XO(
"Click and drag to move a track in time");
85(std::weak_ptr<TimeShiftHandle> &holder,
86 const std::shared_ptr<Track> &pTrack,
bool gripHit)
88 auto result = std::make_shared<TimeShiftHandle>( pTrack, gripHit );
94(std::weak_ptr<TimeShiftHandle> &holder,
95 const wxMouseState &state,
const wxRect &rect,
96 const std::shared_ptr<Track> &pTrack)
106 const int adjustedDragHandleWidth = 14;
108 const int hotspotOffset = 5;
111 if (!(state.m_x + hotspotOffset < rect.x + adjustedDragHandleWidth ||
112 state.m_x + hotspotOffset >= rect.x + rect.width - adjustedDragHandleWidth))
124 return std::dynamic_pointer_cast<const Channel>(
GetTrack());
131 pair.second->DoHorizontalOffset(offset);
144 for (
auto iter =
mFixed.begin(); iter !=
mFixed.end();) {
146 mMoving.push_back(std::move(*iter));
147 iter =
mFixed.erase(iter);
170 return !(interval.
End() < myInterval.Start() ||
171 myInterval.End() < interval.
Start());
177 return desiredOffset;
182 return desiredOffset;
187 return desiredOffset;
200 auto pMyList = track.GetOwner().get();
201 auto pOtherList = otherTrack.
GetOwner().get();
202 if (pMyList && pOtherList) {
206 auto myChannels = track.Channels();
207 auto otherChannels = otherTrack.
Channels();
208 return (myChannels.size() == otherChannels.size());
249 assert(track.IsLeader());
251 auto range = track.Intervals();
256 : mpTrack{ track.SharedPointer() }
267 return HitTestResult::Track;
278 return std::make_unique<CoarseTrackShifter>(track);
284 Track &capturedTrack,
286 std::unique_ptr<TrackShifter> pHit,
299 switch (hitTestResult) {
310 state.movingSelection =
true;
319 state.shifters[&capturedTrack] = std::move( pHit );
322 for (
auto track : trackList) {
323 auto &pShifter = state.shifters[track];
328 if ( state.movingSelection ) {
334 for (
const auto &pair : state.shifters ) {
335 auto &shifter = *pair.second;
336 auto &track = shifter.GetTrack();
337 if (&track == &capturedTrack)
340 if ( track.IsSelected() )
341 shifter.SelectInterval( interval );
353 for (
auto &pair : state.shifters ) {
354 auto &shifter = *pair.second.get();
355 if (!shifter.SyncLocks())
357 auto &track = shifter.GetTrack();
359 if (group.size() <= 1)
362 auto &intervals = shifter.MovingIntervals();
363 for (
auto &interval : intervals) {
367 for (
auto pTrack2 : group) {
368 if (pTrack2 == &track)
371 auto &shifter2 = *
shifters[pTrack2];
372 auto size = shifter2.MovingIntervals().size();
373 shifter2.SelectInterval(*interval);
375 (shifter2.SyncLocks() &&
376 size != shifter2.MovingIntervals().size());
394 auto &pShifter = iter->second;
396 auto &intervals = pShifter->MovingIntervals();
397 if (!intervals.empty())
398 return intervals[0].get();
412 if ( !state.shifters.empty() ) {
413 double initialAllowed = 0;
415 initialAllowed = desiredSlideAmount;
418 auto newAmount = pair.second->AdjustOffsetSmaller( desiredSlideAmount );
419 if ( desiredSlideAmount != newAmount ) {
420 if ( newAmount * desiredSlideAmount < 0 ||
421 fabs(newAmount) > fabs(desiredSlideAmount) ) {
425 desiredSlideAmount = newAmount;
426 state.snapLeft = state.snapRight = -1;
431 }
while ( desiredSlideAmount != initialAllowed );
436 if ( desiredSlideAmount != 0.0 )
437 state.DoHorizontalOffset( desiredSlideAmount );
442 return (state.hSlideAmount = desiredSlideAmount);
453 for (
const auto &pair : shifters) {
454 auto &shifter = pair.second;
455 auto &track = shifter->GetTrack();
456 for (
const auto &interval : shifter->FixedIntervals()) {
457 candidates.emplace_back(interval->Start(), &track);
458 if (interval->Start() != interval->End())
459 candidates.emplace_back(interval->End(), &track);
474 const wxMouseEvent &
event = evt.
event;
475 const wxRect &rect = evt.
rect;
478 const auto pView = std::static_pointer_cast<ChannelView>(evt.
pCell);
479 const auto clickedTrack = pView ? pView->FindTrack().get() :
nullptr;
486 const auto pTrack = *trackList.Find(clickedTrack);
491 const bool multiToolModeActive =
494 const double clickTime =
495 viewInfo.PositionToTime(event.m_x, rect.x);
500 if (!event.ShiftDown()) {
502 rect,
event.m_x,
event.m_y
504 hitTestResult = pShifter->HitTest( clickTime, viewInfo, &
params );
505 switch( hitTestResult ) {
517 hitTestResult, move(pShifter), clickTime,
525 std::make_shared<SnapManager>(*trackList.GetOwner(),
532 (fabs(clickTime - pInterval->End()) <
533 fabs(clickTime - pInterval->Start()));
540 const ViewInfo &viewInfo, wxCoord xx,
542 bool snapPreferRightEdge,
544 double& desiredSlideAmount)
547 double clipLeft, clipRight;
553 clipLeft = pInterval->
Start() + desiredSlideAmount;
554 clipRight = pInterval->End() + desiredSlideAmount;
557 clipLeft = track->GetStartTime() + desiredSlideAmount;
558 clipRight = track->GetEndTime() + desiredSlideAmount;
562 pSnapManager->
Snap(track, clipLeft,
false);
563 auto newClipLeft = results.
outTime;
565 pSnapManager->
Snap(track, clipRight,
false);
566 auto newClipRight = results.
outTime;
569 if (newClipLeft != clipLeft && newClipRight != clipRight) {
571 if (snapPreferRightEdge)
572 newClipLeft = clipLeft;
574 newClipRight = clipRight;
580 if (newClipLeft != clipLeft) {
581 const double difference = (newClipLeft - clipLeft);
582 desiredSlideAmount += difference;
586 else if (newClipRight != clipRight) {
587 const double difference = (newClipRight - clipRight);
588 desiredSlideAmount += difference;
610 auto sameType = [&](
auto pTrack){
613 if (!sameType(&track))
617 auto range = trackList.
Any() + sameType;
620 const auto myPosition =
621 std::distance(range.first, trackList.
Find(&capturedTrack));
622 const auto otherPosition =
623 std::distance(range.first, trackList.
Find(&track));
624 auto diff = otherPosition - myPosition;
629 auto iter = range.first.advance(diff >= 0 ? diff : 0);
631 for (
auto pTrack : range) {
632 auto &pShifter = state.
shifters[pTrack];
633 if (!pShifter->MovingIntervals().empty()) {
637 if (diff < 0 || !pOther)
641 assert(pOther->IsLeader());
642 if (!pShifter->MayMigrateTo(*pOther))
646 if (correspondence.count(pTrack))
650 newPairs[pTrack] = pOther;
660 if (correspondence.empty())
661 correspondence.swap(newPairs);
663 copy(newPairs.begin(), newPairs.end(),
664 inserter(correspondence, correspondence.end()));
669 std::unordered_map<Track*, TrackShifter::Intervals>;
674 double tolerance,
double &desiredSlideAmount )
677 double firstTolerance = tolerance;
680 for (
unsigned iPass = 0; iPass < 2 && ok; ++iPass ) {
681 for (
auto &pair : state.
shifters ) {
682 auto *pSrcTrack = pair.first;
683 auto iter = correspondence.find( pSrcTrack );
684 if ( iter != correspondence.end() )
685 if(
auto *pOtherTrack = iter->second )
686 if ( !(ok = pair.second->AdjustFit(
687 *pOtherTrack, intervals.at(pSrcTrack),
688 desiredSlideAmount , tolerance)) )
695 if (firstTolerance == 0)
708 XO(
"Could not shift between tracks")};
713 : state( clipMoveState )
716 for (
auto &pair : state.shifters)
717 detached[pair.first] = pair.second->Detach();
721 std::unordered_map< Track*, Track* > *pCorrespondence,
double offset )
723 for (
auto &pair : detached) {
724 auto pTrack = pair.first;
725 if (pCorrespondence && pCorrespondence->count(pTrack))
726 pTrack = (*pCorrespondence)[pTrack];
727 auto &pShifter = state.shifters[pTrack];
728 if (!pShifter->Attach( std::move( pair.second ), offset ))
740 TrackList &trackList,
Track *dstTrack,
double& desiredSlideAmount)
745 dstTrack = *trackList.
Find(dstTrack);
750 correspondence, trackList, capturedTrack, *dstTrack,
mClipMoveState ))
754 auto tryExtend = [&](
bool forward) {
755 auto range = trackList.
Any();
756 auto begin = range.begin(),
end = range.end();
757 auto pCaptured = trackList.
Find(&capturedTrack);
758 auto pDst = trackList.
Find(dstTrack);
767 forward ? ++pCaptured : --pCaptured;
768 while (pCaptured !=
end &&
769 (correspondence.count(*pCaptured) ||
771 if (pCaptured ==
end)
777 while (pDst !=
end && correspondence.count(*pDst));
798 auto slideAmount = desiredSlideAmount;
805 tolerance, slideAmount );
809 remover.Reinsert(
nullptr, .0 );
813 remover.Reinsert( &correspondence, slideAmount );
823 desiredSlideAmount = .0;
836 const wxMouseEvent &
event = evt.
event;
842 *trackList.
Find(trackView ? trackView->
FindTrack().get() :
nullptr);
852 if (event.m_x >=
mRect.GetX() &&
876 double desiredSlideAmount = 0.0;
880 viewInfo.PositionToTime(event.m_x) -
897 viewInfo, event.m_x, trackList, pTrack.get(), desiredSlideAmount);
909 if (desiredSlideAmount == 0.0)
940 return this->
Cancel(pProject);
954 if (!pair.second->FinishMigration())
960 msg =
XO(
"Moved clips to another track");
963 pair.first->LinkConsistencyFix();
967 ?
XO(
"Time shifted tracks/clips right %.02f seconds")
968 :
XO(
"Time shifted tracks/clips left %.02f seconds")
991 const wxRect &rect,
unsigned iPass )
994 auto &dc = context.
dc;
1005 const wxRect &rect,
const wxRect &panelRect,
unsigned iPass )
@ Internal
Indicates internal failure from Audacity.
std::shared_ptr< UIHandle > UIHandlePtr
EffectDistortionSettings params
std::vector< SnapPoint > SnapPointArray
DEFINE_ATTACHED_VIRTUAL(MakeTrackShifter)
declares abstract base class Track, TrackList, and iterators over TrackList
std::unique_ptr< wxCursor > MakeCursor(int WXUNUSED(CursorId), const char *const pXpm[36], int HotX, int HotY)
std::shared_ptr< Subclass > AssignUIHandlePtr(std::weak_ptr< Subclass > &holder, const std::shared_ptr< Subclass > &pNew)
Class template generates single-dispatch, open method registry tables.
static Return Call(This &obj, Arguments ...arguments)
Invoke the method – but only after static initialization time.
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
IteratorRange< ChannelIterator< ChannelType > > Channels()
Get range of channels with mutative access.
void ShiftBy(double t)
Change start time by given duration.
A start and an end time, and whatever else subclasses associate with them.
Subclass * Find(const RegisteredFactory &key)
Get a (bare) pointer to an attachment, or null, down-cast it to Subclass *; will not create on demand...
CoarseTrackShifter(Track &track)
bool SyncLocks() override
Returns false.
~CoarseTrackShifter() override
HitTestResult HitTest(double, const ViewInfo &, HitTestParams *) override
Decide how shift behaves, based on the track that is clicked in.
std::shared_ptr< Track > FindTrack()
bool IsAudioActive() const
static ProjectAudioIO & Get(AudacityProject &project)
void PushState(const TranslatableString &desc, const TranslatableString &shortDesc)
static ProjectHistory & Get(AudacityProject &project)
static ProjectSettings & Get(AudacityProject &project)
A MessageBoxException that shows a given, unvarying string.
SnapResults Snap(Track *currentTrack, double t, bool rightEdge)
static TrackIterRange< Track > Group(Track *pTrack)
bool IsSyncLocked() const
static SyncLockState & Get(AudacityProject &project)
ClipMoveState mClipMoveState
static UIHandlePtr HitTest(std::weak_ptr< TimeShiftHandle > &holder, const wxMouseState &state, const wxRect &rect, const std::shared_ptr< Track > &pTrack)
std::shared_ptr< Track > GetTrack() const
std::shared_ptr< SnapManager > mSnapManager
TimeShiftHandle(const TimeShiftHandle &)=delete
Result Cancel(AudacityProject *pProject) override
wxRect DrawingArea(TrackPanelDrawingContext &, const wxRect &rect, const wxRect &panelRect, unsigned iPass) override
std::shared_ptr< const Channel > FindChannel() const override
static HitTestPreview HitPreview(const AudacityProject *pProject, bool unsafe)
Result Drag(const TrackPanelMouseEvent &event, AudacityProject *pProject) override
Result Release(const TrackPanelMouseEvent &event, AudacityProject *pProject, wxWindow *pParent) override
static UIHandlePtr HitAnywhere(std::weak_ptr< TimeShiftHandle > &holder, const std::shared_ptr< Track > &pTrack, bool gripHit)
HitTestPreview Preview(const TrackPanelMouseState &state, AudacityProject *pProject) override
virtual ~TimeShiftHandle()
bool mSnapPreferRightEdge
void Enter(bool forward, AudacityProject *) override
Result Click(const TrackPanelMouseEvent &event, AudacityProject *pProject) override
void Draw(TrackPanelDrawingContext &context, const wxRect &rect, unsigned iPass) override
void DoSlideVertical(ViewInfo &viewInfo, wxCoord xx, TrackList &trackList, Track *dstTrack, double &desiredSlideAmount)
Abstract base class for an object holding data associated with points on a time axis.
bool SameKindAs(const Track &track) const
std::shared_ptr< TrackList > GetOwner() const
std::shared_ptr< Subclass > SharedPointer()
bool IsLeader() const override
A flat linked list of tracks supporting Add, Remove, Clear, and Contains, serialization of the list o...
TrackIter< Track > Find(Track *pTrack)
auto Any() -> TrackIterRange< TrackType >
static TrackList & Get(AudacityProject &project)
static wxRect MaximizeHeight(const wxRect &rect, const wxRect &panelRect)
void CommonSelectInterval(const ChannelGroupInterval &interval)
virtual bool FinishMigration()
When dragging is done, do (once) the final steps of migration (which may be expensive)
virtual double AdjustOffsetSmaller(double desiredOffset)
Given amount to shift by horizontally, maybe adjust it toward zero to meet placement constraints.
virtual bool AdjustFit(const Track &otherTrack, const Intervals &intervals, double &desiredOffset, double tolerance)
virtual Intervals Detach()
Remove all moving intervals from the track, if possible.
virtual Track & GetTrack() const =0
There is always an associated track.
void UnfixAll()
Change all intervals from fixed to moving.
virtual void DoHorizontalOffset(double offset)
virtual double AdjustT0(double t0) const
void InitIntervals()
Derived class constructor can initialize all intervals reported by the track as fixed,...
virtual bool MayMigrateTo(Track &otherTrack)
virtual void SelectInterval(const ChannelGroupInterval &interval)
Notifies the shifter that a region is selected, so it may update its fixed and moving intervals.
virtual double HintOffsetLarger(double desiredOffset)
Given amount to shift by horizontally, maybe adjust it from zero to suggest minimum distance.
void UnfixIntervals(std::function< bool(const ChannelGroupInterval &)> pred)
Change intervals satisfying a predicate from fixed to moving.
virtual double QuantizeOffset(double desiredOffset)
Given amount to shift by horizontally, do any preferred rounding, before placement constraint checks.
bool CommonMayMigrateTo(Track &otherTrack)
virtual ~TrackShifter()=0
HitTestResult
Possibilities for HitTest on the clicked track.
@ Selection
Shift chosen intervals of this track; may shift other tracks' intervals.
@ Intervals
Shift intervals only of selected track and sister channels.
@ Track
Shift selected track and sister channels only, as a whole.
@ Miss
Don't shift anything.
virtual bool Attach(Intervals intervals, double offset)
Put moving intervals into the track, which may have migrated from another.
std::vector< std::shared_ptr< ChannelGroupInterval > > Intervals
Holds a msgid for the translation catalog; may also bind format arguments.
NotifyingSelectedRegion selectedRegion
static ViewInfo & Get(AudacityProject &project)
double PositionToTime(int64 position, int64 origin=0, bool ignoreFisheye=false) const
int64 TimeToPosition(double time, int64 origin=0, bool ignoreFisheye=false) const
STM: Converts a project time to screen x position.
auto end(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
auto begin(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Namespace containing an enum 'what to do on a refresh?'.
AUDACITY_DLL_API void DrawSnapLines(wxDC *dc, wxInt64 snap0, wxInt64 snap1)
bool CheckFit(ClipMoveState &state, const Correspondence &correspondence, const DetachedIntervals &intervals, double tolerance, double &desiredSlideAmount)
std::unordered_map< Track *, TrackShifter::Intervals > DetachedIntervals
SnapPointArray FindCandidates(const TrackList &tracks, const ClipMoveState::ShifterMap &shifters)
void AdjustToSnap(const ViewInfo &viewInfo, wxCoord xx, SnapManager *pSnapManager, bool snapPreferRightEdge, ClipMoveState &state, double &desiredSlideAmount)
std::unordered_map< Track *, Track * > Correspondence
bool FindCorrespondence(Correspondence &correspondence, TrackList &trackList, Track &capturedTrack, Track &track, ClipMoveState &state)
void copy(const T *src, T *dst, int32_t n)
void DoHorizontalOffset(double offset)
Offset tracks or intervals horizontally, without adjusting the offset.
double DoSlideHorizontal(double desiredSlideAmount)
Do sliding of tracks and intervals, maybe adjusting the offset.
void Init(AudacityProject &project, Track &capturedTrack, TrackShifter::HitTestResult hitTestResult, std::unique_ptr< TrackShifter > pHit, double clickTime, const ViewInfo &viewInfo, TrackList &trackList, bool syncLocked)
Will associate a TrackShifter with each track in the list.
const ChannelGroupInterval * CapturedInterval() const
Return pointer to the first fixed interval of the captured track, if there is one.
std::unordered_map< Track *, std::unique_ptr< TrackShifter > > ShifterMap
std::shared_ptr< Track > mCapturedTrack
std::shared_ptr< TrackPanelCell > pCell
Optional, more complete information for hit testing.
DetachedIntervals detached
void Reinsert(std::unordered_map< Track *, Track * > *pCorrespondence, double offset)
TemporaryClipRemover(ClipMoveState &clipMoveState)