16#include "../../HitTestResult.h"
19#include "../../ProjectSettings.h"
20#include "../../RefreshCode.h"
21#include "../../Snap.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(),
561 (fabs(clickTime - pInterval->End()) <
562 fabs(clickTime - pInterval->Start()));
569 const ViewInfo &viewInfo, wxCoord xx,
571 bool snapPreferRightEdge,
573 double& desiredSlideAmount)
576 double clipLeft, clipRight;
582 clipLeft = pInterval->
Start() + desiredSlideAmount;
583 clipRight = pInterval->End() + desiredSlideAmount;
586 clipLeft = track->GetStartTime() + desiredSlideAmount;
587 clipRight = track->GetEndTime() + desiredSlideAmount;
591 pSnapManager->
Snap(track, clipLeft,
false);
592 auto newClipLeft = results.
outTime;
594 pSnapManager->
Snap(track, clipRight,
false);
595 auto newClipRight = results.
outTime;
598 if (newClipLeft != clipLeft && newClipRight != clipRight) {
600 if (snapPreferRightEdge)
601 newClipLeft = clipLeft;
603 newClipRight = clipRight;
609 if (newClipLeft != clipLeft) {
610 const double difference = (newClipLeft - clipLeft);
611 desiredSlideAmount += difference;
615 else if (newClipRight != clipRight) {
616 const double difference = (newClipRight - clipRight);
617 desiredSlideAmount += difference;
639 auto sameType = [&](
auto pTrack ){
642 if (!sameType(&track))
646 auto range = trackList.
Any() + sameType;
649 const auto myPosition =
650 std::distance( range.first, trackList.
Find( &capturedTrack ) );
651 const auto otherPosition =
652 std::distance( range.first, trackList.
Find( &track ) );
653 auto diff = otherPosition - myPosition;
656 auto iter = range.first.advance( diff > 0 ? diff : 0 );
658 for (
auto pTrack : range) {
659 auto &pShifter = state.
shifters[pTrack];
660 if ( !pShifter->MovingIntervals().empty() ) {
664 if ( diff < 0 || !pOther )
668 if ( !pShifter->MayMigrateTo(*pOther) )
672 if ( correspondence.count(pTrack) )
676 newPairs[ pTrack ] = pOther;
686 if (correspondence.empty())
687 correspondence.swap(newPairs);
689 std::copy( newPairs.begin(), newPairs.end(),
690 std::inserter( correspondence, correspondence.end() ) );
695 std::unordered_map<Track*, TrackShifter::Intervals>;
700 double tolerance,
double &desiredSlideAmount )
703 double firstTolerance = tolerance;
706 for (
unsigned iPass = 0; iPass < 2 && ok; ++iPass ) {
707 for (
auto &pair : state.
shifters ) {
708 auto *pSrcTrack = pair.first;
709 auto iter = correspondence.find( pSrcTrack );
710 if ( iter != correspondence.end() )
711 if(
auto *pOtherTrack = iter->second )
712 if ( !(ok = pair.second->AdjustFit(
713 *pOtherTrack, intervals.at(pSrcTrack),
714 desiredSlideAmount , tolerance)) )
721 if (firstTolerance == 0)
734 XO(
"Could not shift between tracks")};
739 : state( clipMoveState )
742 for (
auto &pair : state.shifters)
743 detached[pair.first] = pair.second->Detach();
747 std::unordered_map< Track*, Track* > *pCorrespondence,
double offset )
749 for (
auto &pair : detached) {
750 auto pTrack = pair.first;
751 if (pCorrespondence && pCorrespondence->count(pTrack))
752 pTrack = (*pCorrespondence)[pTrack];
753 auto &pShifter = state.shifters[pTrack];
754 if (!pShifter->Attach( std::move( pair.second ), offset ))
767 const std::shared_ptr<Track>& dstTrack,
double& desiredSlideAmount )
774 correspondence, trackList, capturedTrack, *dstTrack,
mClipMoveState ))
778 auto tryExtend = [&](
bool forward){
780 auto pCaptured = trackList.
Find( &capturedTrack );
781 auto pDst = trackList.
Find( dstTrack.get() );
790 forward ? ++pCaptured : --pCaptured;
791 while ( pCaptured !=
end &&
793 if ( pCaptured ==
end )
798 forward ? ++pDst : --pDst;
799 while ( pDst !=
end && correspondence.count(*pDst) );
820 auto slideAmount = desiredSlideAmount;
827 tolerance, slideAmount );
831 remover.Reinsert(
nullptr, .0 );
835 remover.Reinsert( &correspondence, slideAmount );
844 desiredSlideAmount = .0;
857 const wxMouseEvent &
event = evt.
event;
861 Track *track = trackView ? trackView->
FindTrack().get() :
nullptr;
871 if (event.m_x >=
mRect.GetX() &&
898 double desiredSlideAmount = 0.0;
902 viewInfo.PositionToTime(event.m_x) -
918 DoSlideVertical(viewInfo, event.m_x, trackList, pTrack, desiredSlideAmount);
930 if (desiredSlideAmount == 0.0)
961 return this->
Cancel(pProject);
975 if ( !pair.second->FinishMigration() )
981 msg =
XO(
"Moved clips to another track");
984 pair.first->LinkConsistencyFix();
988 ?
XO(
"Time shifted tracks/clips right %.02f seconds")
989 :
XO(
"Time shifted tracks/clips left %.02f seconds")
1012 const wxRect &rect,
unsigned iPass )
1015 auto &dc = context.
dc;
1026 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...
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
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, const std::shared_ptr< 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()
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 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.
virtual bool Attach(Intervals intervals, double offset)
Put moving intervals into the track, which may have migrated from another.
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?'.
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.
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
void Reinsert(std::unordered_map< Track *, Track * > *pCorrespondence, double offset)
TemporaryClipRemover(ClipMoveState &clipMoveState)