Audacity 3.2.0
WaveTrackView.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3Audacity: A Digital Audio Editor
4
5WaveTrackView.cpp
6
7Paul Licameli split from TrackPanel.cpp
8
9**********************************************************************/
10
11#include "WaveTrackView.h"
12
13#include <unordered_set>
14
15#include "CutlineHandle.h"
16
17#include <numeric>
18#include <wx/dc.h>
19#include <wx/graphics.h>
20
21#include "AColor.h"
22#include "../../../../WaveClip.h"
23#include "../../../../WaveTrack.h"
24
25#include "../../../../../images/Cursors.h"
26#include "AllThemeResources.h"
27
28#include "../../../../commands/CommandContext.h"
29#include "../../../../HitTestResult.h"
30#include "ProjectHistory.h"
31#include "../../../../RefreshCode.h"
32#include "../../../../SyncLock.h"
33#include "../../../../TrackArtist.h"
34#include "../../../../TrackPanel.h"
35#include "../../../../TrackPanelAx.h"
36#include "../../../../TrackPanelDrawingContext.h"
37#include "../../../../TrackPanelMouseEvent.h"
38#include "../../../../TrackPanelResizeHandle.h"
39#include "ViewInfo.h"
40#include "../../../../prefs/TracksPrefs.h"
41
42#include "../../../ui/TimeShiftHandle.h"
43#include "../../../ui/ButtonHandle.h"
44#include "../../../../TrackInfo.h"
45
46#include "../WaveTrackUtils.h"
50#include "WaveClipTrimHandle.h"
51
53
54using WaveTrackSubViewPtrs = std::vector< std::shared_ptr< WaveTrackSubView > >;
55
56// Structure that collects and modifies information on sub-view positions
57// Written with great generality, allowing any number of sub-views
59{
60 enum { HotZoneSize = 5 }; // so many pixels at top and bottom of each subview
61
63 : mwView{
64 std::static_pointer_cast<WaveTrackView>( view.shared_from_this() ) }
65 {
69 }
70
72 {
73 // Find a certain sort of the sub-views
74 auto size = mOrigPlacements.size();
75 wxASSERT( mSubViews.size() == size );
76 mPermutation.resize( size );
77 const auto begin = mPermutation.begin(), end = mPermutation.end();
78 std::iota( begin, end, 0 );
79 static auto invisible = []( const WaveTrackSubViewPlacement &placement ){
80 return placement.index < 0 || placement.fraction <= 0;
81 };
82 const auto comp = [this]( size_t ii, size_t jj ){
83 auto &pi = mOrigPlacements[ii];
84 bool iInvisible = invisible( pi );
85
86 auto &pj = mOrigPlacements[jj];
87 bool jInvisible = invisible( pj );
88
89 // Sort the invisibles to the front, rest by index
90 if ( iInvisible != jInvisible )
91 return iInvisible;
92 else if ( !iInvisible )
93 return pi.index < pj.index;
94 else
95 // Minor sort among the invisible views by their type
96 return mSubViews[ii]->SubViewType() < mSubViews[jj]->SubViewType();
97 };
98 std::sort( begin, end, comp );
99 // Find the start of visible sub-views
100 auto first = std::find_if( begin, end, [this](size_t ii){
101 return !invisible( mOrigPlacements[ii] );
102 } );
103 mFirstSubView = first - begin;
104 }
105
106 size_t NVisible() const
107 { return mPermutation.size() - mFirstSubView; }
108
109 bool ModifyPermutation( bool top )
110 {
111 bool rotated = false;
112 const auto pBegin = mPermutation.begin(), pEnd = mPermutation.end();
113 auto pFirst = pBegin + mFirstSubView;
114 if ( mFirstSubView > 0 ) {
115 // In case of dragging the top edge of the topmost view, or the
116 // bottom edge of the bottommost, decide which of the invisible
117 // views can become visible, and reassign the sequence.
118 // For definiteness, that choice depends on the subview type numbers;
119 // see the sorting criteria above.
121 --pFirst;
122 if ( top ) {
123 // If you drag down the top, the greatest-numbered invisible
124 // subview type will appear there.
125 mNewPlacements[ *pFirst ].fraction = 0;
126 }
127 else {
128 // If you drag up the bottom, let the least-numbered invisible
129 // subview type appear there.
130 mNewPlacements[ *pBegin ].fraction = 0;
131 std::rotate( pBegin, pBegin + 1, pEnd );
132 rotated = true;
133 }
134 }
135 // Reassign index numbers to all sub-views and 0 fraction to invisibles
136 for ( auto pIter = pBegin; pIter != pFirst; ++pIter ) {
137 auto &placement = mNewPlacements[ *pIter ];
138 placement.index = -1;
139 placement.fraction = 0;
140 }
141 size_t index = 0;
142 for ( auto pIter = pFirst; pIter != pEnd; ++pIter )
143 mNewPlacements[ *pIter ].index = index++;
144 return rotated;
145 }
146
147 size_t FindIndex( WaveTrackSubView &subView ) const
148 {
149 const auto begin = mPermutation.begin(), end = mPermutation.end();
150 auto iter = std::find_if( begin, end, [&](size_t ii){
151 return mSubViews[ ii ].get() == &subView;
152 } );
153 return iter - begin;
154 }
155
156 std::pair< size_t, bool >
158 wxCoord yy, wxCoord top, wxCoord height )
159 {
160 const auto index = FindIndex( subView );
161 auto size = mPermutation.size();
162 if ( index < (int)size ) {
163 yy -= top;
164 if ( yy >= 0 && yy < HotZoneSize && index > 0 )
165 return { index, true }; // top hit
166 if ( yy < height && yy >= height - HotZoneSize &&
167 // Have not yet called ModifyPermutation; dragging bottom of
168 // bottommost view allowed only if at least one view is invisible
169 ( index < (int)size - 1 || mFirstSubView > 0 ) )
170 return { index, false }; // bottom hit
171 }
172 return { size, false }; // not hit
173 }
174
175 std::vector<wxCoord> ComputeHeights( wxCoord totalHeight )
176 {
177 // Compute integer-valued heights
178 float total = 0;
179 for (const auto index : mPermutation ) {
180 const auto &placement = mOrigPlacements[ index ];
181 total += std::max( 0.f, placement.fraction );
182 }
183 float partial = 0;
184 wxCoord lastCoord = 0;
185 std::vector<wxCoord> result;
186 for (const auto index : mPermutation ) {
187 const auto &placement = mOrigPlacements[ index ];
188 auto fraction = std::max( 0.f, placement.fraction );
189 wxCoord coord = ( (partial + fraction ) / total ) * totalHeight;
190 auto height = coord - lastCoord;
191 result.emplace_back( height );
192 mNewPlacements[ index ].fraction = height;
193 lastCoord = coord;
194 partial += fraction;
195 }
196 return result;
197 }
198
199 void UpdateViews( bool rollback )
200 {
201 auto pView = mwView.lock();
202 if ( pView ) {
203 auto pTrack = static_cast< WaveTrack* >( pView->FindTrack().get() );
204 for ( auto pChannel : TrackList::Channels<WaveTrack>( pTrack ) )
206 rollback ? mOrigPlacements : mNewPlacements );
207 }
208 }
209
210 std::weak_ptr< WaveTrackView > mwView;
213 // Array mapping ordinal into the placement and subview arrays
214 std::vector< size_t > mPermutation;
215 // index into mPermutation
217};
218
220{
221public:
223
224 static UIHandlePtr HitTest( std::weak_ptr<SubViewAdjustHandle> &holder,
225 WaveTrackView &view,
226 WaveTrackSubView &subView,
227 const TrackPanelMouseState &state )
228 {
229 if ( !view.GetMultiView() )
230 return {};
231
232 SubViewAdjuster adjuster{ view };
233 auto hit = adjuster.HitTest( subView,
234 state.state.GetY(), state.rect.GetTop(), state.rect.GetHeight() );
235 auto index = hit.first;
236
237 if ( index < adjuster.mPermutation.size() ) {
238 auto result = std::make_shared< SubViewAdjustHandle >(
239 std::move( adjuster ), index, view.GetLastHeight(), hit.second
240 );
241 result = AssignUIHandlePtr( holder, result );
242 return result;
243 }
244 else
245 return {};
246 }
247
249 SubViewAdjuster &&adjuster, size_t subViewIndex,
250 wxCoord viewHeight, bool top )
251 : mAdjuster{ std::move( adjuster ) }
252 , mMySubView{ subViewIndex }
253 , mViewHeight{ viewHeight }
254 , mTop{ top }
255 {
256 if ( mAdjuster.ModifyPermutation( top ) )
257 --mMySubView;
258 }
259
261 const TrackPanelMouseEvent &event, AudacityProject *pProject ) override
262 {
263 using namespace RefreshCode;
264 const auto &permutation = mAdjuster.mPermutation;
265 const auto size = permutation.size();
266 if ( mMySubView >= size )
267 return Cancelled;
268
269 if (event.event.LeftDClick()) {
270 for ( auto &placement : mAdjuster.mNewPlacements ) {
271 if ( placement.index >= 0 )
272 placement.fraction = 1.0f;
273 else
274 placement.fraction = 0.0f;
275 }
276 mAdjuster.UpdateViews( false );
277 ProjectHistory::Get( *pProject ).ModifyState( false );
278
279 // Do not start a drag
280 return Cancelled | RefreshAll;
281 }
282
283 const auto &rect = event.rect;
284 const auto height = rect.GetHeight();
285 mOrigHeight = height;
286
288
289 // Find the total height of the sub-views that may resize
290 mTotalHeight = 0;
291 auto index = ( mTop ? mAdjuster.mFirstSubView : mMySubView );
292 const auto end = ( mTop ? mMySubView + 1 : permutation.size() );
293 for (; index != end; ++index)
294 mTotalHeight += mOrigHeights[ index ];
295
296 wxASSERT( height == mOrigHeights[ mMySubView ] );
297
298 // Compute the maximum and minimum Y coordinates for drag effect
299 if ( mTop ) {
300 mOrigY = rect.GetTop();
301 mYMax = rect.GetBottom();
302 mYMin = mYMax - mTotalHeight + 1;
303 }
304 else {
305 mOrigY = rect.GetBottom();
306 mYMin = rect.GetTop();
307 mYMax = mYMin + mTotalHeight - 1;
308 }
309
310 return RefreshNone;
311 }
312
314 {
315 using namespace RefreshCode;
316 auto pView = mAdjuster.mwView.lock();
317 if ( !pView )
318 return Cancelled;
319
320 // Find new height for the dragged sub-view
321 auto newY = std::max( mYMin, std::min( mYMax, event.event.GetY() ) );
322 const auto delta = newY - mOrigY;
323 wxCoord newHeight = mTop
324 ? mOrigHeight - delta
325 : mOrigHeight + delta
326 ;
327 wxASSERT( newHeight >= 0 && newHeight <= mTotalHeight );
328 if ( newHeight < MinHeight )
329 // Snap the dragged sub-view to nothing
330 newHeight = 0;
331
332 // Reassign height for the dragged sub-view
333 auto &myPlacement =
335 myPlacement.fraction = newHeight;
336
337 // Grow or shrink other sub-views
338 auto excess = newHeight - mOrigHeight; // maybe negative
339 const auto adjustHeight = [&](size_t ii) {
340 if (excess == 0)
341 return true;
342
343 const auto oldFraction = mOrigHeights[ ii ];
344
345 auto index = mAdjuster.mPermutation[ ii ];
346 auto &placement = mAdjuster.mNewPlacements[ index ];
347 auto &fraction = placement.fraction;
348
349 if (excess > oldFraction) {
350 excess -= oldFraction, fraction = 0;
351 return false;
352 }
353 else {
354 auto newFraction = oldFraction - excess;
355 if ( newFraction < MinHeight ) {
356 // This snaps very short sub-views to nothing
357 myPlacement.fraction += newFraction;
358 fraction = 0;
359 }
360 else
361 fraction = newFraction;
362 return true;
363 }
364 };
365 if ( mTop ) {
366 for ( size_t ii = mMySubView; ii > 0; ) {
367 --ii;
368 if ( adjustHeight( ii ) )
369 break;
370 }
371 }
372 else {
373 for ( size_t ii = mMySubView + 1, size = mAdjuster.mPermutation.size();
374 ii < size; ++ii
375 ) {
376 if ( adjustHeight( ii ) )
377 break;
378 }
379 }
380
381 // Save adjustment to the track and request a redraw
382 mAdjuster.UpdateViews( false );
383 return RefreshAll;
384 }
385
387 const TrackPanelMouseState &state, AudacityProject * ) override
388 {
389 static auto resizeCursor =
390 ::MakeCursor(wxCURSOR_ARROW, SubViewsCursorXpm, 16, 16);
391 return {
392 XO(
393"Click and drag to adjust sizes of sub-views, double-click to split evenly"),
394 &*resizeCursor
395 };
396 }
397
399 const TrackPanelMouseEvent &event, AudacityProject *pProject,
400 wxWindow *pParent) override
401 {
402 ProjectHistory::Get( *pProject ).ModifyState( false );
404 }
405
407 {
408 mAdjuster.UpdateViews( true );
410 }
411
412private:
413
415 std::vector<wxCoord> mOrigHeights;
416
417 // An index into mAdjuster.mPermutation
418 size_t mMySubView{};
419
420 wxCoord mYMin{}, mYMax{};
421 wxCoord mViewHeight{}; // Total height of all sub-views
422 wxCoord mTotalHeight{}; // Total height of adjusting sub-views only
423 wxCoord mOrigHeight{};
424 wxCoord mOrigY{};
425
426 // Whether we drag the top or the bottom of the sub-view
427 bool mTop{};
428};
429
431{
432public:
433 // Make it somewhat wider than the close button
434 enum { HotZoneWidth = 3 * kTrackInfoBtnSize / 2 };
435
436 static UIHandlePtr HitTest( std::weak_ptr<SubViewRearrangeHandle> &holder,
437 WaveTrackView &view, WaveTrackSubView &subView,
438 const TrackPanelMouseState &state )
439 {
440 if ( !view.GetMultiView() )
441 return {};
442
443 SubViewAdjuster adjuster{ view };
444 if ( adjuster.NVisible() < 2 )
445 return {};
446
447 auto relX = state.state.GetX() - state.rect.GetLeft();
448 if ( relX >= HotZoneWidth )
449 return {};
450
451 auto index = adjuster.FindIndex( subView );
452
453 // Hit on the rearrange cursor only in the top and bottom thirds of
454 // sub-view height, leaving the rest free to hit the selection cursor
455 // first.
456 // And also exclude the top third of the topmost sub-view and bottom
457 // third of bottommost.
458 auto relY = state.state.GetY() - state.rect.GetTop();
459 auto height = state.rect.GetHeight();
460 bool hit =
461 ( ( 3 * relY < height ) && index > 0 ) // top hit
462 ||
463 ( ( 3 * relY > 2 * height ) &&
464 index < adjuster.mPermutation.size() - 1 ) // bottom
465 ;
466 if ( ! hit )
467 return {};
468
469 auto result = std::make_shared< SubViewRearrangeHandle >(
470 std::move( adjuster ),
471 index, view.GetLastHeight()
472 );
473 result = AssignUIHandlePtr( holder, result );
474 return result;
475 }
476
478 SubViewAdjuster &&adjuster, size_t subViewIndex,
479 wxCoord viewHeight )
480 : mAdjuster{ std::move( adjuster ) }
481 , mMySubView{ subViewIndex }
482 , mViewHeight{ viewHeight }
483 {
484 }
485
487 const TrackPanelMouseEvent &event, AudacityProject *pProject ) override
488 {
489 using namespace RefreshCode;
490 const auto &permutation = mAdjuster.mPermutation;
491 const auto size = permutation.size();
492 if ( mMySubView >= size )
493 return Cancelled;
494
496
497 // Find y coordinate of first sub-view
498 wxCoord heightAbove = 0;
499 for (auto index = mAdjuster.mFirstSubView;
500 index != mMySubView; ++index)
501 heightAbove += mHeights[ index ];
502 mTopY = event.rect.GetTop() - heightAbove;
503
504 return RefreshNone;
505 }
506
507 bool Clicked() const { return !mHeights.empty(); }
508
510
512 {
513 // Disregard x coordinate -- so the mouse need not be in any sub-view,
514 // just in the correct range of y coordinates
515 auto yy = event.event.GetY();
516 auto coord = mTopY;
517 size_t ii = mAdjuster.mFirstSubView;
518 if ( yy < mTopY )
519 return ( mMySubView == ii ) ? Neutral : Upward;
520
521 for ( auto nn = mHeights.size(); ii < nn; ++ii ) {
522 const auto height = mHeights[ ii ];
523 coord += height;
524 if ( yy < coord )
525 break;
526 }
527
528 if ( ii < mMySubView ) {
529 if ( yy < coord - mHeights[ ii ] + mHeights[ mMySubView ] )
530 return Upward;
531 }
532
533 if ( ii > mMySubView ) {
534 if( mMySubView < mHeights.size() - 1 &&
535 yy >= coord - mHeights[ mMySubView ] )
536 return Downward;
537 }
538
539 return Neutral;
540 }
541
543 {
544 using namespace RefreshCode;
545 auto pView = mAdjuster.mwView.lock();
546 if ( !pView )
547 return Cancelled;
548
549 switch( DragChoice( event ) ) {
550 case Upward:
551 {
553 std::swap(
556 );
557 --mMySubView;
558 break;
559 }
560 case Downward:
561 {
563 std::swap(
566 );
567 ++mMySubView;
568 break;
569 }
570 default:
571 return RefreshNone;
572 }
573
574 // Save adjustment to the track and request a redraw
575 mAdjuster.UpdateViews( false );
576 return RefreshAll;
577 }
578
580 const TrackPanelMouseState &state, AudacityProject * ) override
581 {
582 static auto hoverCursor =
583 ::MakeCursor(wxCURSOR_HAND, RearrangeCursorXpm, 16, 16);
584 static auto clickedCursor =
585 ::MakeCursor(wxCURSOR_HAND, RearrangingCursorXpm, 16, 16);
586 return {
587 XO("Click and drag to rearrange sub-views"),
588 Clicked() ? &*clickedCursor : &*hoverCursor,
589 XO("Rearrange sub-views")
590 };
591 }
592
594 const TrackPanelMouseEvent &event, AudacityProject *pProject,
595 wxWindow *pParent) override
596 {
597 ProjectHistory::Get( *pProject ).ModifyState( false );
599 }
600
602 {
603 mAdjuster.UpdateViews( true );
605 }
606
607private:
608
610 std::vector<wxCoord> mHeights;
611 wxCoord mTopY;
612
613 // An index into mAdjuster.mPermutation
614 size_t mMySubView{};
615
616 wxCoord mViewHeight{}; // Total height of all sub-views
617};
618
620{
621 static wxRect GetButtonRect( const wxRect &rect )
622 {
623 return {
624 rect.GetLeft(),
625 rect.GetTop(),
628 };
629 }
630
631public:
632 static UIHandlePtr HitTest( std::weak_ptr<SubViewCloseHandle> &holder,
633 WaveTrackView &view, WaveTrackSubView &subView,
634 const TrackPanelMouseState &state )
635 {
636 SubViewAdjuster adjuster{ view };
637 if ( adjuster.NVisible() < 2 )
638 return {};
639
640 const auto rect = GetButtonRect( state.rect );
641 if ( !rect.Contains( state.state.GetPosition() ) )
642 return {};
643 auto index = adjuster.FindIndex( subView );
644 auto result = std::make_shared<SubViewCloseHandle>(
645 std::move( adjuster ), index, view.FindTrack(), rect );
646 result = AssignUIHandlePtr( holder, result );
647 return result;
648 }
649
651 SubViewAdjuster &&adjuster, size_t index,
652 const std::shared_ptr<Track> &pTrack, const wxRect &rect )
653 : ButtonHandle{ pTrack, rect }
654 , mAdjuster{ std::move( adjuster ) }
655 , mMySubView{ index }
656 {
657 }
658
660 const wxMouseEvent &event, AudacityProject *pProject, wxWindow *pParent)
661 override
662 {
663 ProjectHistory::Get( *pProject ).ModifyState( false );
664 auto &myPlacement =
666 myPlacement.fraction = 0;
667 mAdjuster.UpdateViews( false );
669 }
670
672 const wxMouseState &state, AudacityProject &project) const override
673 {
674 return XO("Close sub-view");
675 }
676
677 // TrackPanelDrawable implementation
678 void Draw(
679 TrackPanelDrawingContext &context, const wxRect &rect, unsigned iPass )
680 override
681 {
682 if ( iPass == TrackArtist::PassMargins ) { // after PassTracks
684 context, GetButtonRect(rect), GetTrack().get(), this );
685 }
686 }
687
688private:
690 size_t mMySubView{};
691};
692
693
694std::pair<
695 bool, // if true, hit-testing is finished
696 std::vector<UIHandlePtr>
698 const TrackPanelMouseState &state,
699 const AudacityProject *pProject, int currentTool, bool bMultiTool,
700 const std::shared_ptr<WaveTrack> &wt)
701{
703 state, pProject, currentTool, bMultiTool, wt, *this );
704 if ( results.first )
705 return results;
706
707 auto pWaveTrackView = mwWaveTrackView.lock();
708 if ( pWaveTrackView && !state.state.HasModifiers() ) {
709 if ( auto pHandle = SubViewCloseHandle::HitTest(
711 *pWaveTrackView, *this, state ) )
712 results.second.push_back( pHandle );
713
714 auto channels = TrackList::Channels(wt.get());
715 if(channels.size() > 1) {
716 // Only one cell is tested and we need to know
717 // which one and it's relative location to the border.
718 auto subviews = pWaveTrackView->GetSubViews();
719 auto currentSubview = std::find_if(subviews.begin(), subviews.end(),
720 [self = shared_from_this()](const auto& p){
721 return self == p.second;
722 });
723 if (currentSubview != subviews.end())
724 {
725 auto currentSubviewIndex = std::distance(subviews.begin(), currentSubview);
726
727 const auto py = state.state.GetY();
728 const auto topBorderHit = std::abs(py - state.rect.GetTop())
730 const auto bottomBorderHit = std::abs(py - state.rect.GetBottom())
732
733 auto currentChannel = channels.find(wt.get());
734 auto currentChannelIndex = std::distance(channels.begin(), currentChannel);
735
736 if (//for not-last-view check the bottom border hit
737 ((currentChannelIndex != channels.size() - 1)
738 && (currentSubviewIndex == static_cast<int>(subviews.size()) - 1)
739 && bottomBorderHit)
740 ||
741 //or for not-first-view check the top border hit
742 ((currentChannelIndex != 0) && currentSubviewIndex == 0 && topBorderHit))
743 {
744 //depending on which border hit test succeeded on we
745 //need to choose a proper target for resizing
746 auto it = bottomBorderHit ? currentChannel : currentChannel.advance(-1);
747 auto result = std::make_shared<TrackPanelResizeHandle>((*it)->shared_from_this(), py);
748 result = AssignUIHandlePtr(mResizeHandle, result);
749 results.second.push_back(result);
750 }
751 }
752 }
753
754 if ( auto pHandle = SubViewAdjustHandle::HitTest(
756 *pWaveTrackView, *this, state ) )
757 results.second.push_back( pHandle );
758 if ( auto pHandle = SubViewRearrangeHandle::HitTest(
760 *pWaveTrackView, *this, state ) )
761 results.second.push_back( pHandle );
762 if (auto pHandle = WaveClipTrimHandle::HitTest(
764 *pWaveTrackView, pProject, state))
765 results.second.push_back(pHandle);
766 }
767 if (auto result = CutlineHandle::HitTest(
768 mCutlineHandle, state.state, state.rect,
769 pProject, wt ))
770 // This overriding test applies in all tools
771 results.second.push_back(result);
772
773 return results;
774}
775
776
778 TrackPanelDrawingContext &context, const WaveTrack *track,
779 const wxRect &rect )
780{
781 auto &dc = context.dc;
782 const auto artist = TrackArtist::Get( context );
783
784 const auto &zoomInfo = *artist->pZoomInfo;
785
786#ifdef EXPERIMENTAL_TRACK_PANEL_HIGHLIGHTING
787 auto target2 = dynamic_cast<CutlineHandle*>(context.target.get());
788#endif
789 for (const auto loc : track->GetCachedLocations()) {
790 bool highlightLoc = false;
791#ifdef EXPERIMENTAL_TRACK_PANEL_HIGHLIGHTING
792 highlightLoc =
793 target2 && target2->GetTrack().get() == track &&
794 target2->GetLocation() == loc;
795#endif
796 const int xx = zoomInfo.TimeToPosition(loc.pos);
797 if (xx >= 0 && xx < rect.width) {
798 dc.SetPen( highlightLoc ? AColor::uglyPen : *wxGREY_PEN );
799 AColor::Line(dc, (int) (rect.x + xx - 1), rect.y, (int) (rect.x + xx - 1), rect.y + rect.height);
800 if (loc.typ == WaveTrackLocation::locationCutLine) {
801 dc.SetPen( highlightLoc ? AColor::uglyPen : *wxRED_PEN );
802 }
803 else {
804#ifdef EXPERIMENTAL_DA
805 // JKC Black does not show up enough.
806 dc.SetPen(highlightLoc ? AColor::uglyPen : *wxWHITE_PEN);
807#else
808 dc.SetPen(highlightLoc ? AColor::uglyPen : *wxBLACK_PEN);
809#endif
810 }
811 AColor::Line(dc, (int) (rect.x + xx), rect.y, (int) (rect.x + xx), rect.y + rect.height);
812 dc.SetPen( highlightLoc ? AColor::uglyPen : *wxGREY_PEN );
813 AColor::Line(dc, (int) (rect.x + xx + 1), rect.y, (int) (rect.x + xx + 1), rect.y + rect.height);
814 }
815 }
816}
817
818std::weak_ptr<WaveTrackView> WaveTrackSubView::GetWaveTrackView() const
819{
820 return mwWaveTrackView;
821}
822
824 const wxRect &rect, const wxPoint *pPosition, AudacityProject *pProject )
825 -> std::vector<MenuItem>
826{
827 const WaveClip *pClip = nullptr;
828 auto pTrack = static_cast<WaveTrack*>( FindTrack().get() );
829 double time = 0.0;
830 if ( pTrack && pPosition ) {
831 auto &viewInfo = ViewInfo::Get(*pProject);
832 time = viewInfo.PositionToTime( pPosition->x, rect.x );
833 pClip = pTrack->GetClipAtTime( time );
834 }
835
836 if (pClip)
837 return {
838 { L"Cut", XO("Cut") },
839 { L"Copy", XO("Copy") },
840 { L"Paste", XO("Paste") },
841 {},
842 { L"Split", XO("Split Clip") },
843 { L"TrackMute", XO("Mute/Unmute Track") },
844 {},
845 { L"RenameClip", XO("Rename clip...") },
846 };
847 else
848 return {
849 { L"Paste", XO("Paste") },
850 {},
851 { L"TrackMute", XO("Mute/Unmute Track") },
852 };
853}
854
856{
857 return static_cast< WaveTrackView& >( TrackView::Get( track ) );
858}
859
861{
862 return Get( const_cast<WaveTrack&>( track ) );
863}
864
866{
867 return static_cast< WaveTrackView* >( TrackView::Find( pTrack ) );
868}
869
871{
872 return Find( const_cast<WaveTrack*>( pTrack ) );
873}
874
875WaveTrackView::WaveTrackView( const std::shared_ptr<Track> &pTrack )
876 : CommonTrackView{ pTrack }
877{
878}
879
881 : CommonTrackView( waveTrackView.FindTrack() )
882{
883 mwWaveTrackView = std::static_pointer_cast<WaveTrackView>(
884 waveTrackView.shared_from_this() );
885}
886
888
889}
890
892{
893}
894
895void WaveTrackView::CopyTo( Track &track ) const
896{
897 TrackView::CopyTo( track );
898 auto &other = TrackView::Get( track );
899
900 if ( const auto pOther = dynamic_cast< WaveTrackView* >( &other ) ) {
901 // only these fields are important to preserve in undo/redo history
902 pOther->RestorePlacements( SavePlacements() );
903 pOther->mMultiView = mMultiView;
904
905 auto srcSubViewsPtrs = const_cast<WaveTrackView*>( this )->GetAllSubViews();
906 auto destSubViewsPtrs = const_cast<WaveTrackView*>( pOther )->GetAllSubViews();
907 wxASSERT(srcSubViewsPtrs.size() == destSubViewsPtrs.size());
908
909 for(auto i = 0; i != srcSubViewsPtrs.size(); i++){
910 srcSubViewsPtrs[i]->CopyToSubView(destSubViewsPtrs[i].get());
911 }
912 }
913}
914
915std::vector<UIHandlePtr> WaveTrackView::DetailedHitTest
916(const TrackPanelMouseState &st,
917 const AudacityProject *pProject, int currentTool, bool bMultiTool)
918{
919 // should not come here any more, delegation to sub-view instead
920 wxASSERT( false );
921 return {};
922}
923
924std::pair< bool, std::vector<UIHandlePtr> >
926(const TrackPanelMouseState &st,
927 const AudacityProject *pProject, int currentTool, bool bMultiTool,
928 const std::shared_ptr<WaveTrack> &pTrack,
929 CommonTrackView &view)
930{
931 // common hit-testing for different sub-view types, to help implement their
932 // DetailedHitTest()
933
934 // This is the only override of Track::DetailedHitTest that still
935 // depends on the state of the Tools toolbar.
936 // If that toolbar were eliminated, this could simplify to a sequence of
937 // hit test routines describable by a table.
938
939 std::vector<UIHandlePtr> results;
940
941 const auto& viewInfo = ViewInfo::Get(*pProject);
942
943 for (auto& clip : pTrack->GetClips())
944 {
945 if (!WaveTrackView::ClipDetailsVisible(*clip, viewInfo, st.rect)
946 && HitTest(*clip, viewInfo, st.rect, st.state.GetPosition()))
947 {
948 auto &waveTrackView = WaveTrackView::Get(*pTrack);
949 results.push_back(
951 waveTrackView.mAffordanceHandle,
952 std::make_shared<WaveTrackAffordanceHandle>(pTrack, clip)
953 )
954 );
955 }
956 }
957
958 if (bMultiTool && st.state.CmdDown()) {
959 // Ctrl modifier key in multi-tool overrides everything else
960 // (But this does not do the time shift constrained to the vertical only,
961 // which is what happens when you hold Ctrl in the Time Shift tool mode)
962 auto result = TimeShiftHandle::HitAnywhere(
963 view.mTimeShiftHandle, pTrack, false);
964 if (result)
965 results.push_back(result);
966 return { true, results };
967 }
968
969 return { false, results };
970}
971
973 -> std::vector< WaveTrackSubView::Type >
974{
976
977 // Collect the display types of visible views and sort them by position
978 using Pair = std::pair< int, WaveTrackSubView::Type >;
979 std::vector< Pair > pairs;
980 size_t ii = 0;
981 WaveTrackSubViews::ForEach( [&]( const WaveTrackSubView &subView ){
982 auto &placement = mPlacements[ii];
983 if ( placement.fraction > 0 )
984 pairs.emplace_back( placement.index, subView.SubViewType() );
985 ++ii;
986 } );
987 std::sort( pairs.begin(), pairs.end() );
988 std::vector< WaveTrackSubView::Type > results;
989 for ( const auto &pair : pairs )
990 results.push_back( pair.second );
991 return results;
992}
993
994void WaveTrackView::SetDisplay(Display display, bool exclusive)
995{
997 DoSetDisplay( display, exclusive );
998}
999
1001{
1002 size_t ii = 0;
1003 size_t found = 0;
1004 if ( WaveTrackSubViews::FindIf( [&]( const WaveTrackSubView &subView ) {
1005 if ( subView.SubViewType().id == display ) {
1006 found = ii;
1007 return true;
1008 }
1009 ++ii;
1010 return false;
1011 } ) ) {
1012 auto &foundPlacement = mPlacements[found];
1013 if ( foundPlacement.fraction > 0.0 ) {
1014 // Toggle off
1015
1016 if (GetDisplays().size() < 2)
1017 // refuse to do it
1018 return false;
1019
1020 auto index = foundPlacement.index;
1021 foundPlacement = { -1, 0.0 };
1022 if (index >= 0) {
1023 for ( auto &placement : mPlacements ) {
1024 if ( placement.index > index )
1025 --placement.index;
1026 }
1027 }
1028
1029 return true;
1030 }
1031 else {
1032 // Toggle on
1033 float total = 0;
1034 int greatest = -1;
1035 unsigned nn = 0;
1036 for ( const auto &placement : mPlacements ) {
1037 if ( placement.fraction > 0.0 && placement.index >= 0 ) {
1038 total += placement.fraction;
1039 greatest = std::max( greatest, placement.index );
1040 ++nn;
1041 }
1042 }
1043 // Turn on the sub-view, putting it lowest, and with average of the
1044 // heights of the other sub-views
1045 foundPlacement = { greatest + 1, total / nn };
1046
1047 return true;
1048 }
1049 }
1050 else
1051 // unknown sub-view
1052 return false;
1053}
1054
1055// If exclusive, make the chosen view take up all the height. Else,
1056// partition equally, putting the specified view on top.
1057// Be sure the sequence in which the other views appear is determinate.
1058void WaveTrackView::DoSetDisplay(Display display, bool exclusive)
1059{
1060 // Some generality here anticipating more than two views.
1061 // The order of sub-views in the array is not specified, so make it definite
1062 // by sorting by the view type constants.
1063 size_t ii = 0;
1064 std::vector< std::pair< WaveTrackViewConstants::Display, size_t > > pairs;
1065 WaveTrackSubViews::ForEach( [&pairs, &ii]( WaveTrackSubView &subView ){
1066 pairs.push_back( { subView.SubViewType().id, ii++ } );
1067 } );
1068 std::sort( pairs.begin(), pairs.end() );
1069
1070 int jj = 1;
1071 for ( const auto &pair : pairs ) {
1072 auto &placement = mPlacements[ pair.second ];
1073 if (pair.first == display) {
1074 // 0 for first view
1075 placement = { 0, 1.0 };
1076 }
1077 else if( exclusive )
1078 // -1 for not displayed
1079 placement = { -1, 0.0 };
1080 else
1081 // positions other than the first.
1082 // (Note that the fractions in the placement don't need to be
1083 // denominated to 1. Just make them all equal to get an equal
1084 // partitioning of the sub-views.)
1085 placement = { jj++, 1.0 };
1086 }
1087}
1088
1089namespace {
1090 template<typename Iter, typename Comp>
1091 const WaveClip* NextClipLooped(ViewInfo& viewInfo, Iter begin, Iter end, Comp comp)
1092 {
1093 auto it = WaveTrackUtils::SelectedClip(viewInfo, begin, end);
1094 if (it == end)
1095 it = std::find_if(begin, end, comp);
1096 else
1097 it = std::next(it);
1098
1099 if (it == end)
1100 return *begin;
1101 return *it;
1102 }
1103}
1104
1105bool WaveTrackView::SelectNextClip(ViewInfo& viewInfo, AudacityProject* project, bool forward)
1106{
1107 //Iterates through clips in a looped manner
1108 auto waveTrack = std::dynamic_pointer_cast<WaveTrack>(FindTrack());
1109 if (!waveTrack)
1110 return false;
1111 auto clips = waveTrack->SortedClipArray();
1112 if (clips.empty())
1113 return false;
1114
1115 const WaveClip* clip{ };
1116 if (forward)
1117 {
1118 clip = NextClipLooped(viewInfo, clips.begin(), clips.end(), [&](const WaveClip* other) {
1119 return other->GetPlayStartTime() >= viewInfo.selectedRegion.t1();
1120 });
1121 }
1122 else
1123 {
1124 clip = NextClipLooped(viewInfo, clips.rbegin(), clips.rend(), [&](const WaveClip* other) {
1125 return other->GetPlayStartTime() <= viewInfo.selectedRegion.t0();
1126 });
1127 }
1128
1129 viewInfo.selectedRegion.setTimes(clip->GetPlayStartTime(), clip->GetPlayEndTime());
1130 ProjectHistory::Get(*project).ModifyState(false);
1131
1132 // create and send message to screen reader
1133 auto it = std::find(clips.begin(), clips.end(), clip);
1134 auto index = std::distance(clips.begin(), it);
1135
1136 auto message = XP(
1137 /* i18n-hint:
1138 string is the name of a clip
1139 first number is the position of that clip in a sequence of clips,
1140 second number counts the clips */
1141 "%s, %d of %d clip",
1142 "%s, %d of %d clips",
1143 2
1144 )(
1145 clip->GetName(),
1146 static_cast<int>(index + 1),
1147 static_cast<int>(clips.size())
1148 );
1149
1150 TrackFocus::Get(*project).MessageForScreenReader(message);
1151 return true;
1152}
1153
1154auto WaveTrackView::GetSubViews( const wxRect &rect ) -> Refinement
1155{
1156 return GetSubViews(&rect);
1157}
1158
1159auto WaveTrackView::GetSubViews(const wxRect* rect) -> Refinement
1160{
1161 BuildSubViews();
1162
1163 // Collect the visible views in the right sequence
1164 struct Item {
1165 int index; float fraction; std::shared_ptr< TrackView > pView;
1166 };
1167 std::vector< Item > items;
1168 size_t ii = 0;
1169 float total = 0;
1171 auto& placement = mPlacements[ii];
1172 auto index = placement.index;
1173 auto fraction = placement.fraction;
1174 if (index >= 0 && fraction > 0.0)
1175 total += fraction,
1176 items.push_back({ index, fraction, subView.shared_from_this() });
1177 ++ii;
1178 });
1179 std::sort(items.begin(), items.end(), [](const Item& a, const Item& b) {
1180 return a.index < b.index;
1181 });
1182
1183 // Remove views we don't need
1184 auto begin = items.begin(), end = items.end(),
1185 newEnd = std::remove_if(begin, end,
1186 [](const Item& item) { return !item.pView; });
1187 items.erase(newEnd, end);
1188
1189 Refinement results;
1190
1191 if (rect != nullptr)
1192 {
1193 // Assign coordinates, redenominating to the total height,
1194 // storing integer values
1195 results.reserve(items.size());
1196 const auto top = rect->GetTop();
1197 const auto height = rect->GetHeight();
1198 float partial = 0;
1199 wxCoord lastCoord = 0;
1200 for (const auto& item : items) {
1201 wxCoord newCoord = top + (partial / total) * height;
1202 results.emplace_back(newCoord, item.pView);
1203 partial += item.fraction;
1204 }
1205
1206 // Cache for the use of sub-view dragging
1207 mLastHeight = height;
1208 }
1209 else
1210 {
1211 std::transform(items.begin(), items.end(), std::back_inserter(results), [](const auto& item) {
1212 return std::make_pair(0, item.pView);
1213 });
1214 }
1215
1216 return results;
1217}
1218
1219/*
1220 Note that the WaveTrackView isn't in the TrackPanel subdivision, but it is
1221 set sometimes as the focused cell, and therefore the following functions can
1222 be visited. To visit their overrides in the sub-views and affordances,
1223 which are never focused, we must forward to them. To do that properly, if
1224 any cell declines to handle the event by setting it as skipped, it must be
1225 set again to not-skipped before attempting the next call-through.
1226 */
1227unsigned WaveTrackView::CaptureKey(wxKeyEvent& event, ViewInfo& viewInfo, wxWindow* pParent, AudacityProject* project)
1228{
1229 unsigned result{ RefreshCode::RefreshNone };
1230 auto pTrack = static_cast<WaveTrack*>(FindTrack().get());
1231 for (auto pChannel : TrackList::Channels(pTrack)) {
1232 event.Skip(false);
1233 auto &waveTrackView = WaveTrackView::Get(*pChannel);
1234 // Give sub-views first chance to handle the event
1235 for (auto &subView : waveTrackView.GetSubViews()) {
1236 // Event defaults in skipped state which must be turned off explicitly
1237 wxASSERT(!event.GetSkipped());
1238 result |= subView.second->CaptureKey(event, viewInfo, pParent, project);
1239 if (!event.GetSkipped()) {
1240 // sub view wants it
1241 mKeyEventDelegate = subView.second;
1242 return result;
1243 }
1244 else
1245 event.Skip(false);
1246 }
1247
1248 if (auto affordance = waveTrackView.GetAffordanceControls()) {
1249 result |= affordance->CaptureKey(event, viewInfo, pParent, project);
1250 if (!event.GetSkipped()) {
1251 mKeyEventDelegate = affordance;
1252 return result;
1253 }
1254 }
1255
1256 event.Skip(false);
1257 }
1258 switch (event.GetKeyCode())
1259 {
1260 case WXK_TAB:
1261 break;
1262 default:
1264 event, viewInfo, pParent, project);
1265 break;
1266 };
1267 if (!event.GetSkipped()) {
1268 mKeyEventDelegate = shared_from_this();
1269 }
1270
1271 return result;
1272}
1273
1274unsigned WaveTrackView::KeyDown(wxKeyEvent& event, ViewInfo& viewInfo, wxWindow* pParent, AudacityProject* project)
1275{
1276 unsigned result{ RefreshCode::RefreshNone };
1277 if (auto delegate = mKeyEventDelegate.lock()) {
1278 if (auto pWaveTrackView = dynamic_cast<WaveTrackView*>(delegate.get()))
1279 {
1280 if (event.GetKeyCode() == WXK_TAB)
1281 {
1282 SelectNextClip(viewInfo, project, event.GetModifiers() != wxMOD_SHIFT);
1283 result |= RefreshCode::RefreshCell;
1284 }
1285 else
1286 result |= pWaveTrackView->CommonTrackView::KeyDown(
1287 event, viewInfo, pParent, project);
1288 }
1289 else
1290 result |= delegate->KeyDown(event, viewInfo, pParent, project);
1291 }
1292 else
1293 event.Skip();
1294
1295 return result;
1296}
1297
1298unsigned WaveTrackView::Char(wxKeyEvent& event, ViewInfo& viewInfo, wxWindow* pParent, AudacityProject* project)
1299{
1300 unsigned result{ RefreshCode::RefreshNone };
1301 if (auto delegate = mKeyEventDelegate.lock()) {
1302 if (auto pWaveTrackView = dynamic_cast<WaveTrackView*>(delegate.get()))
1303 result |= pWaveTrackView->CommonTrackView::Char(
1304 event, viewInfo, pParent, project);
1305 else
1306 result |= delegate->Char(event, viewInfo, pParent, project);
1307 }
1308 else
1309 event.Skip();
1310
1311 return result;
1312}
1313
1315{
1316 unsigned result = RefreshCode::RefreshNone;
1317 if (auto delegate = mKeyEventDelegate.lock()) {
1318 if (auto waveTrackView = dynamic_cast<WaveTrackView*>(delegate.get()))
1319 result = waveTrackView->CommonTrackView::LoseFocus(project);
1320 else
1321 result = delegate->LoseFocus(project);
1322 mKeyEventDelegate.reset();
1323 }
1324 return result;
1325}
1326
1328{
1329 for (auto channel : TrackList::Channels(FindTrack().get()))
1330 {
1331 auto& view = TrackView::Get(*channel);
1332 if (auto affordance
1333 = std::dynamic_pointer_cast<WaveTrackAffordanceControls>(view.GetAffordanceControls()))
1334 {
1335 if (affordance->OnTextCut(project))
1336 return true;
1337 }
1338 }
1339 return false;
1340}
1341
1343{
1344 for (auto channel : TrackList::Channels(FindTrack().get()))
1345 {
1346 auto& view = TrackView::Get(*channel);
1347 if (auto affordance
1348 = std::dynamic_pointer_cast<WaveTrackAffordanceControls>(view.GetAffordanceControls()))
1349 {
1350 if (affordance->OnTextCopy(project))
1351 return true;
1352 }
1353 }
1354 return false;
1355}
1356
1357bool WaveTrackView::ClipDetailsVisible(const WaveClip& clip, const ZoomInfo& zoomInfo, const wxRect& viewRect)
1358{
1359 //Do not fold clips to line at sample zoom level, as
1360 //it may become impossible to 'unfold' it when clip is trimmed
1361 //to a single sample
1362 bool showSamples{ false };
1363 auto clipRect = ClipParameters::GetClipRect(clip, zoomInfo, viewRect, &showSamples);
1364 return showSamples || clipRect.width >= kClipDetailedViewMinimumWidth;
1365}
1366
1367wxRect WaveTrackView::ClipHitTestArea(const WaveClip& clip, const ZoomInfo& zoomInfo, const wxRect& viewRect)
1368{
1369 bool showSamples{ false };
1370 auto clipRect = ClipParameters::GetClipRect(clip, zoomInfo, viewRect, &showSamples);
1371 if (showSamples || clipRect.width >= kClipDetailedViewMinimumWidth)
1372 return clipRect;
1373
1374 return clipRect.Inflate(2, 0);
1375}
1376
1377bool WaveTrackView::HitTest(const WaveClip& clip, const ZoomInfo& viewInfo, const wxRect& viewRect, const wxPoint& pos)
1378{
1379 return ClipHitTestArea(clip, viewInfo, viewRect).Contains(pos);
1380}
1381
1383{
1384 for (auto channel : TrackList::Channels(FindTrack().get()))
1385 {
1386 auto& view = TrackView::Get(*channel);
1387 if (auto affordance
1388 = std::dynamic_pointer_cast<WaveTrackAffordanceControls>(view.GetAffordanceControls()))
1389 {
1390 if (affordance->OnTextPaste(project))
1391 return true;
1392 }
1393 }
1394 return false;
1395}
1396
1398{
1399 for (auto channel : TrackList::Channels(FindTrack().get()))
1400 {
1401 auto& view = TrackView::Get(*channel);
1402 if (auto affordance
1403 = std::dynamic_pointer_cast<WaveTrackAffordanceControls>(view.GetAffordanceControls()))
1404 {
1405 if (affordance->OnTextSelect(project))
1406 return true;
1407 }
1408 }
1409 return false;
1410}
1411
1412std::vector< std::shared_ptr< WaveTrackSubView > >
1414{
1415 BuildSubViews();
1416
1417 std::vector< std::shared_ptr< WaveTrackSubView > > results;
1419 results.push_back( std::static_pointer_cast<WaveTrackSubView>(
1420 subView.shared_from_this() ) );
1421 } );
1422 return results;
1423}
1424
1425std::shared_ptr<CommonTrackCell> WaveTrackView::GetAffordanceControls()
1426{
1427 auto track = FindTrack();
1428 if (!track->IsAlignedWithLeader())
1429 {
1430 return DoGetAffordance(track);
1431 }
1432 return {};
1433}
1434
1435void WaveTrackView::DoSetMinimized( bool minimized )
1436{
1437 BuildSubViews();
1438
1439 // May come here. Invoke also on sub-views.
1440 TrackView::DoSetMinimized( minimized );
1441 WaveTrackSubViews::ForEach( [minimized](WaveTrackSubView &subView){
1442 subView.DoSetMinimized( minimized );
1443 } );
1444}
1445
1446std::shared_ptr<CommonTrackCell> WaveTrackView::DoGetAffordance(const std::shared_ptr<Track>& track)
1447{
1448 if (mpAffordanceCellControl == nullptr)
1449 mpAffordanceCellControl = std::make_shared<WaveTrackAffordanceControls>(track);
1451}
1452
1455 return [](WaveTrack &track) {
1456 return std::make_shared<WaveTrackView>( track.SharedPointer() );
1457 };
1458}
1459
1460std::shared_ptr<TrackVRulerControls> WaveTrackView::DoGetVRulerControls()
1461{
1462 // This should never be called because of delegation to the spectrum or
1463 // waveform sub-view
1464 wxASSERT( false );
1465 return {};
1466}
1467
1468namespace
1469{
1470 // Returns an offset in seconds to be applied to the right clip
1471 // boundary so that it does not overlap the last sample
1473 const wxRect& viewRect,
1474 const ZoomInfo& zoomInfo,
1475 int rate,
1476 double& outAveragePPS,
1477 //Is zoom level sufficient to show individual samples?
1478 bool& outShowSamples)
1479 {
1480 static constexpr double pixelsOffset{ 2 };//The desired offset in pixels
1481
1482 auto h = zoomInfo.PositionToTime(0, 0, true);
1483 auto h1 = zoomInfo.PositionToTime(viewRect.width, 0, true);
1484
1485 // Determine whether we should show individual samples
1486 // or draw circular points as well
1487 outAveragePPS = viewRect.width / (rate * (h1 - h));// pixels per sample
1488 outShowSamples = outAveragePPS > 0.5;
1489
1490 if(outShowSamples)
1491 // adjustment so that the last circular point doesn't appear
1492 // to be hanging off the end
1493 return pixelsOffset / (outAveragePPS * rate); // pixels / ( pixels / second ) = seconds
1494 return .0;
1495 }
1496}
1497
1499 (bool spectrum, const SampleTrack *track, const WaveClip *clip, const wxRect &rect,
1500 const SelectedRegion &selectedRegion, const ZoomInfo &zoomInfo)
1501{
1502 tOffset = clip->GetPlayStartTime();
1503 rate = clip->GetRate();
1504
1505 h = zoomInfo.PositionToTime(0, 0
1506 , true
1507 );
1508 h1 = zoomInfo.PositionToTime(rect.width, 0
1509 , true
1510 );
1511
1512 double sel0 = selectedRegion.t0(); //left selection bound
1513 double sel1 = selectedRegion.t1(); //right selection bound
1514
1515 //If the track isn't selected, make the selection empty
1516 if (!track->GetSelected() &&
1517 (spectrum ||
1518 !SyncLock::IsSyncLockSelected(track))) { // PRL: why was there a difference for spectrum?
1519 sel0 = sel1 = 0.0;
1520 }
1521
1522 const double trackLen = clip->GetPlayEndTime() - clip->GetPlayStartTime();
1523
1524 tpre = h - tOffset; // offset corrected time of
1525 // left edge of display
1526 tpost = h1 - tOffset; // offset corrected time of
1527 // right edge of display
1528
1529 const double sps = 1. / rate; //seconds-per-sample
1530
1531 // Calculate actual selection bounds so that t0 > 0 and t1 < the
1532 // end of the track
1533 t0 = std::max(tpre, .0);
1534 t1 = std::min(tpost, trackLen - sps * .99)
1536
1537 // Make sure t1 (the right bound) is greater than 0
1538 if (t1 < 0.0) {
1539 t1 = 0.0;
1540 }
1541
1542 // Make sure t1 is greater than t0
1543 if (t0 > t1) {
1544 t0 = t1;
1545 }
1546
1547 // Use the WaveTrack method to show what is selected and 'should' be copied, pasted etc.
1548 ssel0 = std::max(sampleCount(0), spectrum
1549 ? sampleCount((sel0 - tOffset) * rate + .99) // PRL: why?
1550 : track->TimeToLongSamples(sel0 - tOffset)
1551 );
1552 ssel1 = std::max(sampleCount(0), spectrum
1553 ? sampleCount((sel1 - tOffset) * rate + .99) // PRL: why?
1554 : track->TimeToLongSamples(sel1 - tOffset)
1555 );
1556
1557 //trim selection so that it only contains the actual samples
1558 if (ssel0 != ssel1 && ssel1 > (sampleCount)(0.5 + trackLen * rate)) {
1559 ssel1 = sampleCount( 0.5 + trackLen * rate );
1560 }
1561
1562 // The variable "hiddenMid" will be the rectangle containing the
1563 // actual waveform, as opposed to any blank area before
1564 // or after the track, as it would appear without the fisheye.
1565 hiddenMid = rect;
1566
1567 // If the left edge of the track is to the right of the left
1568 // edge of the display, then there's some unused area to the
1569 // left of the track. Reduce the "hiddenMid"
1570 hiddenLeftOffset = 0;
1571 if (tpre < 0) {
1572 // Fix Bug #1296 caused by premature conversion to (int).
1573 wxInt64 time64 = zoomInfo.TimeToPosition(tOffset, 0 , true);
1574 if( time64 < 0 )
1575 time64 = 0;
1576 hiddenLeftOffset = (time64 < rect.width) ? (int)time64 : rect.width;
1577
1579 hiddenMid.width -= hiddenLeftOffset;
1580 }
1581
1582 // If the right edge of the track is to the left of the right
1583 // edge of the display, then there's some unused area to the right
1584 // of the track. Reduce the "hiddenMid" rect by the
1585 // size of the blank area.
1586 if (tpost > t1) {
1587 wxInt64 time64 = zoomInfo.TimeToPosition(tOffset+t1, 0 , true);
1588 if( time64 < 0 )
1589 time64 = 0;
1590 const int hiddenRightOffset = (time64 < rect.width) ? (int)time64 : rect.width;
1591
1592 hiddenMid.width = std::max(0, hiddenRightOffset - hiddenLeftOffset);
1593 }
1594 // The variable "mid" will be the rectangle containing the
1595 // actual waveform, as distorted by the fisheye,
1596 // as opposed to any blank area before or after the track.
1597 mid = rect;
1598
1599 // If the left edge of the track is to the right of the left
1600 // edge of the display, then there's some unused area to the
1601 // left of the track. Reduce the "mid"
1602 leftOffset = 0;
1603 if (tpre < 0) {
1604 wxInt64 time64 = zoomInfo.TimeToPosition(tOffset, 0 , false);
1605 if( time64 < 0 )
1606 time64 = 0;
1607 leftOffset = (time64 < rect.width) ? (int)time64 : rect.width;
1608
1609 mid.x += leftOffset;
1610 mid.width -= leftOffset;
1611 }
1612
1613 // If the right edge of the track is to the left of the right
1614 // edge of the display, then there's some unused area to the right
1615 // of the track. Reduce the "mid" rect by the
1616 // size of the blank area.
1617 if (tpost > t1) {
1618 wxInt64 time64 = zoomInfo.TimeToPosition(tOffset+t1, 0 , false);
1619 if( time64 < 0 )
1620 time64 = 0;
1621 const int distortedRightOffset = (time64 < rect.width) ? (int)time64 : rect.width;
1622
1623 mid.width = std::max(0, distortedRightOffset - leftOffset);
1624 }
1625}
1626
1627wxRect ClipParameters::GetClipRect(const WaveClip& clip, const ZoomInfo& zoomInfo, const wxRect& viewRect, bool* outShowSamples)
1628{
1629 auto srs = 1. / static_cast<double>(clip.GetRate());
1630 double averagePixelsPerSample{};
1631 bool showIndividualSamples{};
1632 auto clipEndingAdjustemt
1634 if (outShowSamples != nullptr)
1635 *outShowSamples = showIndividualSamples;
1636 constexpr auto edgeLeft = static_cast<wxInt64>(std::numeric_limits<int>::min());
1637 constexpr auto edgeRight = static_cast<wxInt64>(std::numeric_limits<int>::max());
1638 auto left = std::clamp(
1639 zoomInfo.TimeToPosition(
1640 clip.GetPlayStartTime(), viewRect.x, true
1641 ), edgeLeft, edgeRight
1642 );
1643 auto right = std::clamp(
1644 zoomInfo.TimeToPosition(
1645 clip.GetPlayEndTime() - .99 * srs + clipEndingAdjustemt, viewRect.x, true
1646 ), edgeLeft, edgeRight
1647 );
1648 if (right >= left)
1649 {
1650 //after clamping we can expect that left and right
1651 //are small enough to be put into int
1652 return wxRect(
1653 static_cast<int>(left),
1654 viewRect.y,
1655 std::max(1, static_cast<int>(right - left)),
1656 viewRect.height
1657 );
1658 }
1659 return wxRect();
1660}
1661
1662void WaveTrackView::Reparent( const std::shared_ptr<Track> &parent )
1663{
1664 // BuildSubViews(); // not really needed
1665 CommonTrackView::Reparent( parent );
1666 WaveTrackSubViews::ForEach( [&parent](WaveTrackSubView &subView){
1667 subView.Reparent( parent );
1668 } );
1670 mpAffordanceCellControl->Reparent(parent);
1671}
1672
1673std::weak_ptr<WaveClip> WaveTrackView::GetSelectedClip()
1674{
1675 if (auto affordance = std::dynamic_pointer_cast<WaveTrackAffordanceControls>(GetAffordanceControls()))
1676 {
1677 return affordance->GetSelectedClip();
1678 }
1679 return {};
1680}
1681
1683{
1684 if ( WaveTrackSubViews::size() == 0) {
1685 // On-demand steps that can't happen in the constructor
1686 auto pThis = const_cast<WaveTrackView*>( this );
1687 pThis->BuildAll();
1688 bool minimized = GetMinimized();
1689 pThis->WaveTrackSubViews::ForEach( [&]( WaveTrackSubView &subView ){
1690 subView.DoSetMinimized( minimized );
1691 } );
1692
1693 if ( pThis->mPlacements.empty() ) {
1694 pThis->mPlacements.resize( WaveTrackSubViews::size() );
1695
1696 auto pTrack = pThis->FindTrack();
1697 auto display = TracksPrefs::ViewModeChoice();
1698 bool multi = (display == WaveTrackViewConstants::MultiView);
1699 if ( multi ) {
1700 pThis->SetMultiView( true );
1702 }
1703
1704 pThis->DoSetDisplay( display, !multi );
1705 }
1706 }
1707}
1708
1710 TrackPanelDrawingContext &context,
1711 const wxRect &rect, unsigned iPass )
1712{
1713 // Should not come here, drawing is now delegated to sub-views
1714 wxASSERT( false );
1715
1716 CommonTrackView::Draw( context, rect, iPass );
1717}
1718
1722 return [](auto &) { return SyncLockPolicy::Grouped; };
1723}
std::shared_ptr< UIHandle > UIHandlePtr
Definition: CellularPanel.h:28
int min(int a, int b)
#define XO(s)
Definition: Internat.h:31
#define XP(sing, plur, n)
Definition: Internat.h:96
@ Grouped
Can be part of a group.
std::unique_ptr< wxCursor > MakeCursor(int WXUNUSED(CursorId), const char *const pXpm[36], int HotX, int HotY)
Definition: TrackPanel.cpp:185
std::shared_ptr< Subclass > AssignUIHandlePtr(std::weak_ptr< Subclass > &holder, const std::shared_ptr< Subclass > &pNew)
Definition: UIHandle.h:151
@ kTrackInfoBtnSize
Definition: ViewInfo.h:95
DEFINE_ATTACHED_VIRTUAL_OVERRIDE(DoGetWaveTrackView)
std::vector< std::shared_ptr< WaveTrackSubView > > WaveTrackSubViewPtrs
constexpr int kClipDetailedViewMinimumWidth
std::vector< WaveTrackSubViewPlacement > WaveTrackSubViewPlacements
Definition: WaveTrackView.h:88
static void Line(wxDC &dc, wxCoord x1, wxCoord y1, wxCoord x2, wxCoord y2)
Definition: AColor.cpp:187
static wxPen uglyPen
Definition: AColor.h:141
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:89
A UIHandle for a TrackPanel button, such as the Mute and Solo buttons.
Definition: ButtonHandle.h:26
std::shared_ptr< Track > GetTrack() const
Definition: ButtonHandle.h:30
size_t size() const
How many attachment pointers are in the Site.
Definition: ClientData.h:251
ClientData * FindIf(const Function &function)
Return pointer to first attachment in this that is not null and satisfies a predicate,...
Definition: ClientData.h:412
void ForEach(const Function &function)
Invoke function on each ClientData object that has been created in this.
Definition: ClientData.h:380
void BuildAll()
For each RegisteredFactory, if the corresponding attachment is absent in this, build and store it.
Definition: ClientData.h:441
void Reparent(const std::shared_ptr< Track > &parent) override
Object may be shared among tracks but hold a special back-pointer to one of them; reassign it.
std::shared_ptr< Track > FindTrack()
std::weak_ptr< TimeShiftHandle > mTimeShiftHandle
std::shared_ptr< WaveTrack > GetTrack()
Definition: CutlineHandle.h:44
static UIHandlePtr HitTest(std::weak_ptr< CutlineHandle > &holder, const wxMouseState &state, const wxRect &rect, const AudacityProject *pProject, const std::shared_ptr< WaveTrack > &pTrack)
bool setTimes(double t0, double t1)
Definition: ViewInfo.cpp:51
void ModifyState(bool bWantsAutoSave)
static ProjectHistory & Get(AudacityProject &project)
sampleCount TimeToLongSamples(double t0) const
Convert correctly between an (absolute) time in seconds and a number of samples.
Definition: SampleTrack.cpp:42
Defines a selected portion of a project.
double t1() const
double t0() const
SubViewAdjuster mAdjuster
static UIHandlePtr HitTest(std::weak_ptr< SubViewAdjustHandle > &holder, WaveTrackView &view, WaveTrackSubView &subView, const TrackPanelMouseState &state)
HitTestPreview Preview(const TrackPanelMouseState &state, AudacityProject *) override
Result Release(const TrackPanelMouseEvent &event, AudacityProject *pProject, wxWindow *pParent) override
Result Drag(const TrackPanelMouseEvent &event, AudacityProject *) override
SubViewAdjustHandle(SubViewAdjuster &&adjuster, size_t subViewIndex, wxCoord viewHeight, bool top)
Result Click(const TrackPanelMouseEvent &event, AudacityProject *pProject) override
std::vector< wxCoord > mOrigHeights
Result Cancel(AudacityProject *) override
SubViewAdjuster mAdjuster
TranslatableString Tip(const wxMouseState &state, AudacityProject &project) const override
static UIHandlePtr HitTest(std::weak_ptr< SubViewCloseHandle > &holder, WaveTrackView &view, WaveTrackSubView &subView, const TrackPanelMouseState &state)
static wxRect GetButtonRect(const wxRect &rect)
SubViewCloseHandle(SubViewAdjuster &&adjuster, size_t index, const std::shared_ptr< Track > &pTrack, const wxRect &rect)
void Draw(TrackPanelDrawingContext &context, const wxRect &rect, unsigned iPass) override
Result CommitChanges(const wxMouseEvent &event, AudacityProject *pProject, wxWindow *pParent) override
SubViewRearrangeHandle(SubViewAdjuster &&adjuster, size_t subViewIndex, wxCoord viewHeight)
DragChoice_t DragChoice(const TrackPanelMouseEvent &event) const
HitTestPreview Preview(const TrackPanelMouseState &state, AudacityProject *) override
Result Release(const TrackPanelMouseEvent &event, AudacityProject *pProject, wxWindow *pParent) override
Result Click(const TrackPanelMouseEvent &event, AudacityProject *pProject) override
static UIHandlePtr HitTest(std::weak_ptr< SubViewRearrangeHandle > &holder, WaveTrackView &view, WaveTrackSubView &subView, const TrackPanelMouseState &state)
std::vector< wxCoord > mHeights
SubViewAdjuster mAdjuster
Result Drag(const TrackPanelMouseEvent &event, AudacityProject *) override
Result Cancel(AudacityProject *) override
static bool IsSyncLockSelected(const Track *pTrack)
Definition: SyncLock.cpp:43
static UIHandlePtr HitAnywhere(std::weak_ptr< TimeShiftHandle > &holder, const std::shared_ptr< Track > &pTrack, bool gripHit)
static TrackArtist * Get(TrackPanelDrawingContext &)
Definition: TrackArtist.cpp:69
Track * Get()
Abstract base class for an object holding data associated with points on a time axis.
Definition: Track.h:225
bool GetSelected() const
Definition: Track.h:469
static auto Channels(TrackType *pTrack) -> TrackIterRange< TrackType >
Definition: Track.h:1541
virtual unsigned CaptureKey(wxKeyEvent &event, ViewInfo &viewInfo, wxWindow *pParent, AudacityProject *project)
virtual void Draw(TrackPanelDrawingContext &context, const wxRect &rect, unsigned iPass)
virtual void DoSetMinimized(bool isMinimized)
Definition: TrackView.cpp:141
bool GetMinimized() const
Definition: TrackView.h:52
static TrackView * Find(Track *)
Definition: TrackView.cpp:79
void CopyTo(Track &track) const override
Copy state, for undo/redo purposes.
Definition: TrackView.cpp:52
static TrackView & Get(Track &)
Definition: TrackView.cpp:69
std::vector< std::pair< wxCoord, std::shared_ptr< TrackView > > > Refinement
Definition: TrackView.h:104
static WaveTrackViewConstants::Display ViewModeChoice()
Holds a msgid for the translation catalog; may also bind format arguments.
Short-lived drawing and event-handling object associated with a TrackPanelCell.
Definition: UIHandle.h:35
unsigned Result
Definition: UIHandle.h:38
NotifyingSelectedRegion selectedRegion
Definition: ViewInfo.h:216
static ViewInfo & Get(AudacityProject &project)
Definition: ViewInfo.cpp:235
This allows multiple clips to be a part of one WaveTrack.
Definition: WaveClip.h:101
double GetPlayStartTime() const noexcept
Definition: WaveClip.cpp:900
double GetPlayEndTime() const
Definition: WaveClip.cpp:910
int GetRate() const
Definition: WaveClip.h:139
static UIHandlePtr HitTest(std::weak_ptr< WaveClipTrimHandle > &holder, WaveTrackView &view, const AudacityProject *pProject, const TrackPanelMouseState &state)
A Track that contains audio waveform data.
Definition: WaveTrack.h:57
const std::vector< Location > & GetCachedLocations() const
Definition: WaveTrack.h:489
std::weak_ptr< TrackPanelResizeHandle > mResizeHandle
Definition: WaveTrackView.h:76
static void DrawBoldBoundaries(TrackPanelDrawingContext &context, const WaveTrack *track, const wxRect &rect)
WaveTrackSubView(WaveTrackView &waveTrackView)
std::weak_ptr< SubViewAdjustHandle > mAdjustHandle
Definition: WaveTrackView.h:77
std::weak_ptr< SubViewRearrangeHandle > mRearrangeHandle
Definition: WaveTrackView.h:78
virtual const Type & SubViewType() const =0
std::weak_ptr< WaveTrackView > mwWaveTrackView
Definition: WaveTrackView.h:81
std::weak_ptr< WaveClipTrimHandle > mClipTrimHandle
Definition: WaveTrackView.h:79
std::weak_ptr< CutlineHandle > mCutlineHandle
Definition: WaveTrackView.h:80
std::vector< MenuItem > GetMenuItems(const wxRect &rect, const wxPoint *pPosition, AudacityProject *pProject) override
Return a list of items for DoContextMenu() (empties for separators)
std::pair< bool, std::vector< UIHandlePtr > > DoDetailedHitTest(const TrackPanelMouseState &state, const AudacityProject *pProject, int currentTool, bool bMultiTool, const std::shared_ptr< WaveTrack > &wt)
virtual void CopyToSubView(WaveTrackSubView *destSubView) const
std::weak_ptr< SubViewCloseHandle > mCloseHandle
Definition: WaveTrackView.h:75
std::weak_ptr< WaveTrackView > GetWaveTrackView() const
std::vector< UIHandlePtr > DetailedHitTest(const TrackPanelMouseState &state, const AudacityProject *pProject, int currentTool, bool bMultiTool) override
void DoSetDisplay(Display display, bool exclusive=true)
std::weak_ptr< TrackPanelCell > mKeyEventDelegate
WaveTrackView(const WaveTrackView &)=delete
void SetDisplay(Display display, bool exclusive=true)
std::vector< std::shared_ptr< WaveTrackSubView > > GetAllSubViews()
bool CutSelectedText(AudacityProject &project)
wxCoord GetLastHeight() const
void RestorePlacements(const WaveTrackSubViewPlacements &placements)
bool GetMultiView() const
void DoSetMinimized(bool minimized) override
std::shared_ptr< TrackVRulerControls > DoGetVRulerControls() override
static bool HitTest(const WaveClip &clip, const ZoomInfo &zoomInfo, const wxRect &rect, const wxPoint &pos)
unsigned KeyDown(wxKeyEvent &event, ViewInfo &viewInfo, wxWindow *pParent, AudacityProject *project) override
void CopyTo(Track &track) const override
Copy state, for undo/redo purposes.
std::shared_ptr< CommonTrackCell > DoGetAffordance(const std::shared_ptr< Track > &track)
~WaveTrackView() override
const WaveTrackSubViewPlacements & SavePlacements() const
unsigned CaptureKey(wxKeyEvent &event, ViewInfo &viewInfo, wxWindow *pParent, AudacityProject *project) override
static wxRect ClipHitTestArea(const WaveClip &clip, const ZoomInfo &zoomInfo, const wxRect &viewRect)
unsigned Char(wxKeyEvent &event, ViewInfo &viewInfo, wxWindow *pParent, AudacityProject *project) override
WaveTrackSubViewPlacements mPlacements
bool SelectNextClip(ViewInfo &viewInfo, AudacityProject *project, bool forward)
static std::pair< bool, std::vector< UIHandlePtr > > DoDetailedHitTest(const TrackPanelMouseState &state, const AudacityProject *pProject, int currentTool, bool bMultiTool, const std::shared_ptr< WaveTrack > &wt, CommonTrackView &view)
void BuildSubViews() const
static WaveTrackView & Get(WaveTrack &track)
void Reparent(const std::shared_ptr< Track > &parent) override
Object may be shared among tracks but hold a special back-pointer to one of them; reassign it.
bool PasteText(AudacityProject &project)
bool ToggleSubView(Display id)
void Draw(TrackPanelDrawingContext &context, const wxRect &rect, unsigned iPass) override
static WaveTrackView * Find(WaveTrack *pTrack)
std::vector< WaveTrackSubView::Type > GetDisplays() const
std::shared_ptr< CommonTrackCell > GetAffordanceControls() override
unsigned LoseFocus(AudacityProject *project) override
bool CopySelectedText(AudacityProject &project)
std::shared_ptr< CommonTrackCell > mpAffordanceCellControl
Refinement GetSubViews(const wxRect *rect=nullptr)
std::weak_ptr< WaveClip > GetSelectedClip()
static bool ClipDetailsVisible(const WaveClip &clip, const ZoomInfo &zoomInfo, const wxRect &viewRect)
static constexpr int kChannelSeparatorThickness
bool SelectAllText(AudacityProject &project)
double PositionToTime(wxInt64 position, wxInt64 origin=0, bool ignoreFisheye=false) const
Definition: ZoomInfo.cpp:41
wxInt64 TimeToPosition(double time, wxInt64 origin=0, bool ignoreFisheye=false) const
STM: Converts a project time to screen x position.
Definition: ZoomInfo.cpp:51
Positions or offsets within audio files need a wide type.
Definition: SampleCount.h:19
auto end(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:159
auto begin(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:150
Namespace containing an enum 'what to do on a refresh?'.
Definition: RefreshCode.h:16
AUDACITY_DLL_API void DrawCloseButton(TrackPanelDrawingContext &context, const wxRect &bev, const Track *pTrack, ButtonHandle *target)
Definition: TrackInfo.cpp:267
Iter SelectedClip(const ViewInfo &viewInfo, Iter begin, Iter end)
@ MultiView
"Multi" is special, not really a view type on par with the others.
void swap(std::unique_ptr< Alg_seq > &a, std::unique_ptr< Alg_seq > &b)
Definition: NoteTrack.cpp:753
std::pair< const char *, const char * > Pair
bool Comp(const CollectedItems::NewItem &a, const CollectedItems::NewItem &b)
Definition: Registry.cpp:484
std::shared_ptr< Track > FindTrack(TrackPanelCell *pCell)
Definition: TrackPanel.cpp:526
double CalculateAdjustmentForZoomLevel(const wxRect &viewRect, const ZoomInfo &zoomInfo, int rate, double &outAveragePPS, bool &outShowSamples)
const WaveClip * NextClipLooped(ViewInfo &viewInfo, Iter begin, Iter end, Comp comp)
STL namespace.
For defining overrides of the method.
ClipParameters(bool spectrum, const SampleTrack *track, const WaveClip *clip, const wxRect &rect, const SelectedRegion &selectedRegion, const ZoomInfo &zoomInfo)
sampleCount ssel1
sampleCount ssel0
static wxRect GetClipRect(const WaveClip &clip, const ZoomInfo &zoomInfo, const wxRect &viewRect, bool *outShowSamples=nullptr)
double averagePixelsPerSample
bool showIndividualSamples
SubViewAdjuster(WaveTrackView &view)
size_t NVisible() const
std::vector< wxCoord > ComputeHeights(wxCoord totalHeight)
void UpdateViews(bool rollback)
bool ModifyPermutation(bool top)
WaveTrackSubViewPlacements mNewPlacements
WaveTrackSubViewPtrs mSubViews
std::weak_ptr< WaveTrackView > mwView
size_t FindIndex(WaveTrackSubView &subView) const
std::vector< size_t > mPermutation
std::pair< size_t, bool > HitTest(WaveTrackSubView &subView, wxCoord yy, wxCoord top, wxCoord height)
WaveTrackSubViewPlacements mOrigPlacements
static Display Default()
Return a preferred type.