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"
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))
127 pair.second->DoHorizontalOffset(offset);
140 for (
auto iter =
mFixed.begin(); iter !=
mFixed.end();) {
142 mMoving.push_back(std::move(*iter));
143 iter =
mFixed.erase(iter);
166 return !(interval.
End() < myInterval.Start() ||
167 myInterval.End() < interval.
Start());
173 return desiredOffset;
178 return desiredOffset;
183 return desiredOffset;
195 auto pMyList = track.GetOwner().get();
196 auto pOtherList = otherTrack.
GetOwner().get();
197 if (pMyList && pOtherList) {
201 auto myChannels = track.Channels();
202 auto otherChannels = otherTrack.
Channels();
203 return (myChannels.size() == otherChannels.size());
245 const auto &range = track.Intervals();
250 : mpTrack{ track.SharedPointer() }
260 return HitTestResult::Track;
270 return std::make_unique<CoarseTrackShifter>(track);
276 Track &capturedTrack,
278 std::unique_ptr<TrackShifter> pHit,
290 switch (hitTestResult) {
301 state.movingSelection =
true;
310 state.shifters[&capturedTrack] = std::move( pHit );
313 for (
auto track : trackList) {
314 auto &pShifter = state.shifters[track];
319 if ( state.movingSelection ) {
325 for (
const auto &pair : state.shifters ) {
326 auto &shifter = *pair.second;
327 auto &track = shifter.GetTrack();
328 if (&track == &capturedTrack)
331 if ( track.IsSelected() )
332 shifter.SelectInterval( interval );
344 for (
auto &pair : state.shifters ) {
345 auto &shifter = *pair.second.get();
346 if (!shifter.SyncLocks())
348 auto &track = shifter.GetTrack();
350 if (group.size() <= 1)
353 auto &intervals = shifter.MovingIntervals();
354 for (
auto &interval : intervals) {
358 for (
auto pTrack2 : group) {
359 if (pTrack2 == &track)
361 auto &shifter2 = *
shifters[pTrack2];
362 auto size = shifter2.MovingIntervals().size();
363 shifter2.SelectInterval({
364 interval->Start(), interval->End() });
366 (shifter2.SyncLocks() &&
367 size != shifter2.MovingIntervals().size());
385 auto &pShifter = iter->second;
387 auto &intervals = pShifter->MovingIntervals();
388 if (!intervals.empty())
389 return intervals[0].get();
403 if ( !state.shifters.empty() ) {
404 double initialAllowed = 0;
406 initialAllowed = desiredSlideAmount;
409 auto newAmount = pair.second->AdjustOffsetSmaller( desiredSlideAmount );
410 if ( desiredSlideAmount != newAmount ) {
411 if ( newAmount * desiredSlideAmount < 0 ||
412 fabs(newAmount) > fabs(desiredSlideAmount) ) {
416 desiredSlideAmount = newAmount;
417 state.snapLeft = state.snapRight = -1;
422 }
while ( desiredSlideAmount != initialAllowed );
427 if ( desiredSlideAmount != 0.0 )
428 state.DoHorizontalOffset( desiredSlideAmount );
433 return (state.hSlideAmount = desiredSlideAmount);
444 for (
const auto &pair : shifters) {
445 auto &shifter = pair.second;
446 auto &track = shifter->GetTrack();
447 for (
const auto &interval : shifter->FixedIntervals()) {
448 candidates.emplace_back(interval->Start(), &track);
449 if (interval->Start() != interval->End())
450 candidates.emplace_back(interval->End(), &track);
465 const wxMouseEvent &
event = evt.
event;
466 const wxRect &rect = evt.
rect;
470 std::dynamic_pointer_cast<CommonTrackPanelCell>(evt.
pCell);
471 const auto pTrack = pView ? pView->FindTrack() :
nullptr;
478 const bool multiToolModeActive =
481 const double clickTime =
482 viewInfo.PositionToTime(event.m_x, rect.x);
487 if (!event.ShiftDown()) {
489 rect,
event.m_x,
event.m_y
491 hitTestResult = pShifter->HitTest( clickTime, viewInfo, &
params );
492 switch( hitTestResult ) {
505 hitTestResult, move(pShifter), clickTime,
513 std::make_shared<SnapManager>(*trackList.GetOwner(),
520 (fabs(clickTime - pInterval->End()) <
521 fabs(clickTime - pInterval->Start()));
528 const ViewInfo &viewInfo, wxCoord xx,
530 bool snapPreferRightEdge,
532 double& desiredSlideAmount)
535 double clipLeft, clipRight;
541 clipLeft = pInterval->
Start() + desiredSlideAmount;
542 clipRight = pInterval->End() + desiredSlideAmount;
545 clipLeft = track->GetStartTime() + desiredSlideAmount;
546 clipRight = track->GetEndTime() + desiredSlideAmount;
550 pSnapManager->
Snap(track, clipLeft,
false);
551 auto newClipLeft = results.
outTime;
553 pSnapManager->
Snap(track, clipRight,
false);
554 auto newClipRight = results.
outTime;
557 if (newClipLeft != clipLeft && newClipRight != clipRight) {
559 if (snapPreferRightEdge)
560 newClipLeft = clipLeft;
562 newClipRight = clipRight;
568 if (newClipLeft != clipLeft) {
569 const double difference = (newClipLeft - clipLeft);
570 desiredSlideAmount += difference;
574 else if (newClipRight != clipRight) {
575 const double difference = (newClipRight - clipRight);
576 desiredSlideAmount += difference;
598 auto sameType = [&](
auto pTrack){
601 if (!sameType(&track))
605 auto range = trackList.
Any() + sameType;
608 const auto myPosition =
609 std::distance(range.first, trackList.
Find(&capturedTrack));
610 const auto otherPosition =
611 std::distance(range.first, trackList.
Find(&track));
612 auto diff = otherPosition - myPosition;
617 auto iter = range.first.advance(diff >= 0 ? diff : 0);
619 for (
auto pTrack : range) {
620 auto &pShifter = state.
shifters[pTrack];
621 if (!pShifter->MovingIntervals().empty()) {
625 if (diff < 0 || !pOther)
629 if (!pShifter->MayMigrateTo(*pOther))
633 if (correspondence.count(pTrack))
637 newPairs[pTrack] = pOther;
647 if (correspondence.empty())
648 correspondence.swap(newPairs);
650 copy(newPairs.begin(), newPairs.end(),
651 inserter(correspondence, correspondence.end()));
656 std::unordered_map<Track*, TrackShifter::Intervals>;
661 double tolerance,
double &desiredSlideAmount )
664 double firstTolerance = tolerance;
667 for (
unsigned iPass = 0; iPass < 2 && ok; ++iPass ) {
668 for (
auto &pair : state.
shifters ) {
669 auto *pSrcTrack = pair.first;
670 auto iter = correspondence.find( pSrcTrack );
671 if ( iter != correspondence.end() )
672 if(
auto *pOtherTrack = iter->second )
673 if ( !(ok = pair.second->AdjustFit(
674 *pOtherTrack, intervals.at(pSrcTrack),
675 desiredSlideAmount , tolerance)) )
682 if (firstTolerance == 0)
695 XO(
"Could not shift between tracks")};
700 : state( clipMoveState )
703 for (
auto &pair : state.shifters)
704 detached[pair.first] = pair.second->Detach();
708 std::unordered_map< Track*, Track* > *pCorrespondence,
double offset )
710 for (
auto &pair : detached) {
711 auto pTrack = pair.first;
712 if (pCorrespondence && pCorrespondence->count(pTrack))
713 pTrack = (*pCorrespondence)[pTrack];
714 auto &pShifter = state.shifters[pTrack];
715 if (!pShifter->Attach( std::move( pair.second ), offset ))
727 TrackList &trackList,
Track *dstTrack,
double& desiredSlideAmount)
734 correspondence, trackList, capturedTrack, *dstTrack,
mClipMoveState ))
738 auto tryExtend = [&](
bool forward) {
739 auto range = trackList.
Any();
740 auto begin = range.begin(),
end = range.end();
741 auto pCaptured = trackList.
Find(&capturedTrack);
742 auto pDst = trackList.
Find(dstTrack);
751 forward ? ++pCaptured : --pCaptured;
752 while (pCaptured !=
end &&
753 (correspondence.count(*pCaptured) ||
755 if (pCaptured ==
end)
761 while (pDst !=
end && correspondence.count(*pDst));
782 auto slideAmount = desiredSlideAmount;
789 tolerance, slideAmount );
793 remover.Reinsert(
nullptr, .0 );
797 remover.Reinsert( &correspondence, slideAmount );
806 desiredSlideAmount = .0;
819 const wxMouseEvent &
event = evt.
event;
824 const auto pChannel = trackView ? trackView->
FindChannel() :
nullptr;
825 auto track = pChannel
826 ?
dynamic_cast<Track *
>(&pChannel->GetChannelGroup())
837 if (event.m_x >=
mRect.GetX() &&
861 double desiredSlideAmount = 0.0;
865 viewInfo.PositionToTime(event.m_x) -
882 viewInfo, event.m_x, trackList, pTrack.get(), desiredSlideAmount);
894 if (desiredSlideAmount == 0.0)
925 return this->
Cancel(pProject);
939 if (!pair.second->FinishMigration())
945 msg =
XO(
"Moved clips to another track");
950 ?
XO(
"Time shifted tracks/clips right %.02f seconds")
951 :
XO(
"Time shifted tracks/clips left %.02f seconds")
974 const wxRect &rect,
unsigned iPass )
977 auto &dc = context.
dc;
988 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.
virtual double Start() const =0
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.
auto FindChannel() -> std::shared_ptr< Subtype >
May return null.
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 &track)
bool IsSyncLocked() const
static SyncLockState & Get(AudacityProject &project)
ClipMoveState mClipMoveState
std::shared_ptr< const Track > FindTrack() const override
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, 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()
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)
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)
void CommonSelectInterval(TimeInterval interval)
virtual void SelectInterval(TimeInterval interval)
Notifies the shifter that a region is selected, so it may update its fixed and moving intervals.
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 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.
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)
const char * end(const char *str) noexcept
const char * begin(const char *str) noexcept
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)