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