16#include "../../HitTestResult.h"
19#include "../../ProjectSettings.h"
20#include "../../RefreshCode.h"
21#include "../../Snap.h"
22#include "../../SyncLock.h"
24#include "../../TrackArtist.h"
25#include "../../TrackPanelDrawingContext.h"
26#include "../../TrackPanelMouseEvent.h"
29#include "../../../images/Cursors.h"
32(
const std::shared_ptr<Track> &pTrack,
bool gripHit )
55#ifdef EXPERIMENTAL_TRACK_PANEL_HIGHLIGHTING
63 static auto disabledCursor =
64 ::MakeCursor(wxCURSOR_NO_ENTRY, DisabledCursorXpm, 16, 16);
65 static auto slideCursor =
66 MakeCursor(wxCURSOR_SIZEWE, TimeCursorXpm, 16, 16);
70 auto message =
XO(
"Click and drag to move a track in time");
81(std::weak_ptr<TimeShiftHandle> &holder,
82 const std::shared_ptr<Track> &pTrack,
bool gripHit)
84 auto result = std::make_shared<TimeShiftHandle>( pTrack, gripHit );
90(std::weak_ptr<TimeShiftHandle> &holder,
91 const wxMouseState &state,
const wxRect &rect,
92 const std::shared_ptr<Track> &pTrack)
102 const int adjustedDragHandleWidth = 14;
104 const int hotspotOffset = 5;
107 if (!(state.m_x + hotspotOffset < rect.x + adjustedDragHandleWidth ||
108 state.m_x + hotspotOffset >= rect.x + rect.width - adjustedDragHandleWidth))
122 pair.second->DoHorizontalOffset( offset );
126 channel->Offset( offset );
137 for (
auto iter =
mFixed.begin(); iter !=
mFixed.end(); ) {
138 if ( pred( *iter) ) {
139 mMoving.push_back( std::move( *iter ) );
140 iter =
mFixed.erase( iter );
163 return !(interval.
End() < myInterval.Start() ||
164 myInterval.End() < interval.
Start());
170 return desiredOffset;
175 return desiredOffset;
180 return desiredOffset;
193 auto pMyList = track.GetOwner().get();
194 auto pOtherList = otherTrack.
GetOwner().get();
195 if (pMyList && pOtherList) {
203 if (myChannels.size() == otherChannels.size()) {
207 return myChannels.size() == 1 ||
208 std::distance(myChannels.first, pMyList->Find(&track)) ==
209 std::distance(otherChannels.first, pOtherList->Find(&otherTrack));
258 : mpTrack{ track.SharedPointer() }
268 return HitTestResult::Track;
278 return std::make_unique<CoarseTrackShifter>(track);
284 Track &capturedTrack,
286 std::unique_ptr<TrackShifter> pHit,
298 switch (hitTestResult) {
309 state.movingSelection =
true;
318 state.shifters[&capturedTrack] = std::move( pHit );
321 for (
auto track : trackList.
Any() ) {
322 auto &pShifter = state.shifters[track];
327 if ( state.movingSelection ) {
333 for (
const auto &pair : state.shifters ) {
334 auto &shifter = *pair.second;
335 auto &track = shifter.GetTrack();
336 if (&track == &capturedTrack)
339 if ( track.IsSelected() )
340 shifter.SelectInterval( interval );
348 auto& shifter = *state.shifters[channel];
349 if (channel != &capturedTrack)
351 for (
auto& interval : intervals)
353 shifter.SelectInterval(interval);
362 selectIntervals(state.shifters[&capturedTrack]->MovingIntervals());
366 intervals.emplace_back(
TrackInterval { clickTime, clickTime });
369 selectIntervals(intervals);
381 for (
auto &pair : state.shifters ) {
382 auto &shifter = *pair.second.get();
383 if (!shifter.SyncLocks())
385 auto &track = shifter.GetTrack();
387 if ( group.size() <= 1 )
390 auto &intervals = shifter.MovingIntervals();
391 for (
auto &interval : intervals) {
395 for (
auto pTrack2 : group ) {
396 if (pTrack2 == &track)
399 auto &shifter2 = *
shifters[pTrack2];
400 auto size = shifter2.MovingIntervals().size();
401 shifter2.SelectInterval( interval );
403 (shifter2.SyncLocks() &&
404 size != shifter2.MovingIntervals().size());
420 auto iter =
shifters.find( pTrack );
422 auto &pShifter = iter->second;
424 auto &intervals = pShifter->MovingIntervals();
425 if ( !intervals.empty() )
426 return &intervals[0];
436 auto &capturedTrack = *state.mCapturedTrack;
441 if ( !state.shifters.empty() ) {
442 double initialAllowed = 0;
444 initialAllowed = desiredSlideAmount;
447 auto newAmount = pair.second->AdjustOffsetSmaller( desiredSlideAmount );
448 if ( desiredSlideAmount != newAmount ) {
449 if ( newAmount * desiredSlideAmount < 0 ||
450 fabs(newAmount) > fabs(desiredSlideAmount) ) {
454 desiredSlideAmount = newAmount;
455 state.snapLeft = state.snapRight = -1;
460 }
while ( desiredSlideAmount != initialAllowed );
465 if ( desiredSlideAmount != 0.0 )
466 state.DoHorizontalOffset( desiredSlideAmount );
471 return (state.hSlideAmount = desiredSlideAmount);
482 for (
const auto &pair : shifters ) {
483 auto &shifter = pair.second;
484 auto &track = shifter->GetTrack();
485 for (
const auto &interval : shifter->FixedIntervals() ) {
486 candidates.emplace_back( interval.Start(), &track );
487 if ( interval.Start() != interval.End() )
488 candidates.emplace_back( interval.End(), &track );
503 const wxMouseEvent &
event = evt.
event;
504 const wxRect &rect = evt.
rect;
507 const auto pView = std::static_pointer_cast<TrackView>(evt.
pCell);
508 const auto pTrack = pView ? pView->FindTrack().get() :
nullptr;
517 const bool multiToolModeActive =
520 const double clickTime =
521 viewInfo.PositionToTime(event.m_x, rect.x);
526 if (!event.ShiftDown()) {
528 rect,
event.m_x,
event.m_y
530 hitTestResult = pShifter->HitTest( clickTime, viewInfo, &
params );
531 switch( hitTestResult ) {
544 std::move( pShifter ),
554 std::make_shared<SnapManager>(*trackList.GetOwner(),
563 (fabs(clickTime - pInterval->End()) <
564 fabs(clickTime - pInterval->Start()));
571 const ViewInfo &viewInfo, wxCoord xx,
const wxMouseEvent &event,
573 bool slideUpDownOnly,
bool snapPreferRightEdge,
581 double desiredSlideAmount =
584 double clipLeft = 0, clipRight = 0;
588 state.
shifters[ &track ]->QuantizeOffset( desiredSlideAmount );
594 clipLeft = pInterval->
Start() + desiredSlideAmount;
595 clipRight = pInterval->End() + desiredSlideAmount;
598 clipLeft = capturedTrack.GetStartTime() + desiredSlideAmount;
599 clipRight = capturedTrack.GetEndTime() + desiredSlideAmount;
603 pSnapManager->
Snap(&capturedTrack, clipLeft,
false);
604 auto newClipLeft = results.
outTime;
606 pSnapManager->
Snap(&capturedTrack, clipRight,
false);
607 auto newClipRight = results.
outTime;
610 if (newClipLeft != clipLeft && newClipRight != clipRight) {
612 if (snapPreferRightEdge)
613 newClipLeft = clipLeft;
615 newClipRight = clipRight;
621 if (newClipLeft != clipLeft) {
622 const double difference = (newClipLeft - clipLeft);
623 desiredSlideAmount += difference;
627 else if (newClipRight != clipRight) {
628 const double difference = (newClipRight - clipRight);
629 desiredSlideAmount += difference;
634 return desiredSlideAmount;
653 auto sameType = [&](
auto pTrack ){
656 if (!sameType(&track))
660 auto range = trackList.
Any() + sameType;
663 const auto myPosition =
664 std::distance( range.first, trackList.
Find( &capturedTrack ) );
665 const auto otherPosition =
666 std::distance( range.first, trackList.
Find( &track ) );
667 auto diff = otherPosition - myPosition;
670 auto iter = range.first.advance( diff > 0 ? diff : 0 );
672 for (
auto pTrack : range) {
673 auto &pShifter = state.
shifters[pTrack];
674 if ( !pShifter->MovingIntervals().empty() ) {
678 if ( diff < 0 || !pOther )
682 if ( !pShifter->MayMigrateTo(*pOther) )
686 if ( correspondence.count(pTrack) )
690 newPairs[ pTrack ] = pOther;
700 if (correspondence.empty())
701 correspondence.swap(newPairs);
703 std::copy( newPairs.begin(), newPairs.end(),
704 std::inserter( correspondence, correspondence.end() ) );
709 std::unordered_map<Track*, TrackShifter::Intervals>;
714 double tolerance,
double &desiredSlideAmount )
717 double firstTolerance = tolerance;
720 for (
unsigned iPass = 0; iPass < 2 && ok; ++iPass ) {
721 for (
auto &pair : state.
shifters ) {
722 auto *pSrcTrack = pair.first;
723 auto iter = correspondence.find( pSrcTrack );
724 if ( iter != correspondence.end() )
725 if(
auto *pOtherTrack = iter->second )
726 if ( !(ok = pair.second->AdjustFit(
727 *pOtherTrack, intervals.at(pSrcTrack),
728 desiredSlideAmount , tolerance)) )
735 if (firstTolerance == 0)
748 XO(
"Could not shift between tracks")};
753 : state( clipMoveState )
756 for (
auto &pair : state.shifters)
757 detached[pair.first] = pair.second->Detach();
761 std::unordered_map< Track*, Track* > *pCorrespondence )
763 for (
auto &pair : detached) {
764 auto pTrack = pair.first;
765 if (pCorrespondence && pCorrespondence->count(pTrack))
766 pTrack = (*pCorrespondence)[pTrack];
767 auto &pShifter = state.shifters[pTrack];
768 if (!pShifter->Attach( std::move( pair.second ) ))
781 Track &dstTrack,
double &desiredSlideAmount )
788 correspondence, trackList, capturedTrack, dstTrack, state ))
792 auto tryExtend = [&](
bool forward){
794 auto pCaptured = trackList.
Find( &capturedTrack );
795 auto pDst = trackList.
Find( &dstTrack );
804 forward ? ++pCaptured : --pCaptured;
805 while ( pCaptured !=
end &&
806 ( correspondence.count(*pCaptured) || state.
shifters[*pCaptured]->MovingIntervals().empty() ) );
807 if ( pCaptured ==
end )
812 forward ? ++pDst : --pDst;
813 while ( pDst !=
end && correspondence.count(*pDst) );
819 correspondence, trackList, **pCaptured, **pDst, state ))
831 TemporaryClipRemover remover{ state };
834 double slide = desiredSlideAmount;
840 bool ok =
CheckFit( state, correspondence, remover.detached,
841 tolerance, desiredSlideAmount );
845 remover.Reinsert(
nullptr );
851 remover.Reinsert( &correspondence );
865 const wxMouseEvent &
event = evt.
event;
869 Track *track = trackView ? trackView->
FindTrack().get() :
nullptr;
879 if (event.m_x >=
mRect.GetX() &&
906 double desiredSlideAmount =
915 bool slidVertically = (
919 trackList, *pTrack, desiredSlideAmount ) );
926 if (desiredSlideAmount == 0.0)
938 if (slidVertically) {
962 return this->
Cancel(pProject);
976 if ( !pair.second->FinishMigration() )
982 msg =
XO(
"Moved clips to another track");
985 pair.first->LinkConsistencyFix();
989 ?
XO(
"Time shifted tracks/clips right %.02f seconds")
990 :
XO(
"Time shifted tracks/clips left %.02f seconds")
1013 const wxRect &rect,
unsigned iPass )
1016 auto &dc = context.
dc;
1027 const wxRect &rect,
const wxRect &panelRect,
unsigned iPass )
@ Internal
Indicates internal failure from Audacity.
std::shared_ptr< UIHandle > UIHandlePtr
EffectDistortionSettings params
const int kPixelTolerance
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...
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)
bool IsSyncLocked() const
A MessageBoxException that shows a given, unvarying string.
SnapResults Snap(Track *currentTrack, double t, bool rightEdge)
static TrackIterRange< Track > Group(Track *pTrack)
static bool DoSlideVertical(ViewInfo &viewInfo, wxCoord xx, ClipMoveState &state, TrackList &trackList, Track &dstTrack, double &desiredSlideAmount)
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
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
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()
virtual ConstIntervals GetIntervals() const
Report times on the track where important intervals begin and end, for UI to snap to.
LinkType GetLinkType() const noexcept
bool IsAlignedWithLeader() const
Returns true if the leader track has link type LinkType::Aligned.
A start and an end time, and mutative access to optional extra information.
A flat linked list of tracks supporting Add, Remove, Clear, and Contains, serialization of the list o...
auto Find(Track *pTrack) -> TrackIter< TrackType >
Turn a pointer into a TrackIter (constant time); get end iterator if this does not own the track.
auto Any() -> TrackIterRange< TrackType >
static TrackList & Get(AudacityProject &project)
static auto Channels(TrackType *pTrack) -> TrackIterRange< TrackType >
static wxRect MaximizeHeight(const wxRect &rect, const wxRect &panelRect)
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)
Test whether intervals can fit into another track, maybe adjusting the offset slightly.
std::vector< TrackInterval > Intervals
virtual bool Attach(Intervals intervals)
Put moving intervals into the track, which may have migrated from another.
virtual Intervals Detach()
Remove all moving intervals from the track, if possible.
virtual Track & GetTrack() const =0
There is always an associated track.
void CommonSelectInterval(const TrackInterval &interval)
void UnfixAll()
Change all intervals from fixed to moving.
virtual void DoHorizontalOffset(double offset)
void UnfixIntervals(std::function< bool(const TrackInterval &) > pred)
Change intervals satisfying a predicate from fixed to moving.
virtual double AdjustT0(double t0) const
void InitIntervals()
Derived class constructor can initialize all intervals reported by the track as fixed,...
virtual void SelectInterval(const TrackInterval &interval)
Notifies the shifter that a region is selected, so it may update its fixed and moving intervals.
virtual bool MayMigrateTo(Track &otherTrack)
Whether intervals may migrate to the other track, not yet checking all placement constraints */.
virtual double HintOffsetLarger(double desiredOffset)
Given amount to shift by horizontally, maybe adjust it from zero to suggest minimum distance.
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.
Holds a msgid for the translation catalog; may also bind format arguments.
NotifyingSelectedRegion selectedRegion
static ViewInfo & Get(AudacityProject &project)
double PositionToTime(wxInt64 position, wxInt64 origin=0, bool ignoreFisheye=false) const
wxInt64 TimeToPosition(double time, wxInt64 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?'.
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)
double FindDesiredSlideAmount(const ViewInfo &viewInfo, wxCoord xx, const wxMouseEvent &event, SnapManager *pSnapManager, bool slideUpDownOnly, bool snapPreferRightEdge, ClipMoveState &state, Track &track)
std::unordered_map< Track *, Track * > Correspondence
bool FindCorrespondence(Correspondence &correspondence, TrackList &trackList, Track &capturedTrack, Track &track, ClipMoveState &state)
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.
const TrackInterval * CapturedInterval() const
Return pointer to the first fixed interval of the captured track, if there is one.
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.
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
TemporaryClipRemover(ClipMoveState &clipMoveState)
void Reinsert(std::unordered_map< Track *, Track * > *pCorrespondence)