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
922WaveTrackView::WaveTrackView( const std::shared_ptr<Track> &pTrack )
923 : CommonTrackView{ pTrack }
924{
925}
926
928 : CommonTrackView( waveTrackView.FindTrack() )
929{
930 mwWaveTrackView = std::static_pointer_cast<WaveTrackView>(
931 waveTrackView.shared_from_this() );
932}
933
935
936}
937
939{
940}
941
942void WaveTrackView::CopyTo( Track &track ) const
943{
944 TrackView::CopyTo( track );
945 auto &other = TrackView::Get( track );
946
947 if ( const auto pOther = dynamic_cast< WaveTrackView* >( &other ) ) {
948 // only these fields are important to preserve in undo/redo history
949 pOther->RestorePlacements( SavePlacements() );
950 pOther->DoGetMultiView() = DoGetMultiView();
951
952 auto srcSubViewsPtrs = const_cast<WaveTrackView*>( this )->GetAllSubViews();
953 auto destSubViewsPtrs = const_cast<WaveTrackView*>( pOther )->GetAllSubViews();
954 wxASSERT(srcSubViewsPtrs.size() == destSubViewsPtrs.size());
955
956 for(auto i = 0; i != srcSubViewsPtrs.size(); i++){
957 srcSubViewsPtrs[i]->CopyToSubView(destSubViewsPtrs[i].get());
958 }
959 }
960}
961
962std::vector<UIHandlePtr> WaveTrackView::DetailedHitTest
963(const TrackPanelMouseState &st,
964 const AudacityProject *pProject, int currentTool, bool bMultiTool)
965{
966 // should not come here any more, delegation to sub-view instead
967 wxASSERT( false );
968 return {};
969}
970
971std::pair< bool, std::vector<UIHandlePtr> >
973(const TrackPanelMouseState &st,
974 const AudacityProject *pProject, int currentTool, bool bMultiTool,
975 const std::shared_ptr<WaveTrack> &pTrack,
976 CommonTrackView &view)
977{
978 // common hit-testing for different sub-view types, to help implement their
979 // DetailedHitTest()
980
981 // This is the only override of Track::DetailedHitTest that still
982 // depends on the state of the Tools toolbar.
983 // If that toolbar were eliminated, this could simplify to a sequence of
984 // hit test routines describable by a table.
985
986 std::vector<UIHandlePtr> results;
987
988 const auto& viewInfo = ViewInfo::Get(*pProject);
989
990 for (auto& clip : pTrack->GetClips())
991 {
992 if (!WaveTrackView::ClipDetailsVisible(*clip, viewInfo, st.rect)
993 && HitTest(*clip, viewInfo, st.rect, st.state.GetPosition()))
994 {
995 auto &waveTrackView = WaveTrackView::Get(*pTrack);
996 results.push_back(
998 waveTrackView.mAffordanceHandle,
999 std::make_shared<WaveTrackAffordanceHandle>(pTrack, clip)
1000 )
1001 );
1002 }
1003 }
1004
1005 if (bMultiTool && st.state.CmdDown()) {
1006 // Ctrl modifier key in multi-tool overrides everything else
1007 // (But this does not do the time shift constrained to the vertical only,
1008 // which is what happens when you hold Ctrl in the Time Shift tool mode)
1009 auto result = TimeShiftHandle::HitAnywhere(
1010 view.mTimeShiftHandle, pTrack, false);
1011 if (result)
1012 results.push_back(result);
1013 return { true, results };
1014 }
1015
1016 return { false, results };
1017}
1018
1020 -> std::vector< WaveTrackSubView::Type >
1021{
1022 BuildSubViews();
1023
1024 // Collect the display types of visible views and sort them by position
1025 using Pair = std::pair< int, WaveTrackSubView::Type >;
1026 std::vector< Pair > pairs;
1027 size_t ii = 0;
1028 const auto &placements = DoGetPlacements();
1029 WaveTrackSubViews::ForEach( [&]( const WaveTrackSubView &subView ){
1030 auto &placement = placements[ii];
1031 if ( placement.fraction > 0 )
1032 pairs.emplace_back( placement.index, subView.SubViewType() );
1033 ++ii;
1034 } );
1035 std::sort( pairs.begin(), pairs.end() );
1036 std::vector< WaveTrackSubView::Type > results;
1037 for ( const auto &pair : pairs )
1038 results.push_back( pair.second );
1039 return results;
1040}
1041
1042void WaveTrackView::SetDisplay(Display display, bool exclusive)
1043{
1044 BuildSubViews();
1045 DoSetDisplay( display, exclusive );
1046}
1047
1049{
1050 size_t ii = 0;
1051 size_t found = 0;
1052 if ( WaveTrackSubViews::FindIf( [&]( const WaveTrackSubView &subView ) {
1053 if ( subView.SubViewType().id == display ) {
1054 found = ii;
1055 return true;
1056 }
1057 ++ii;
1058 return false;
1059 } ) ) {
1060 auto &placements = DoGetPlacements();
1061 auto &foundPlacement = placements[found];
1062 if ( foundPlacement.fraction > 0.0 ) {
1063 // Toggle off
1064
1065 if (GetDisplays().size() < 2)
1066 // refuse to do it
1067 return false;
1068
1069 auto index = foundPlacement.index;
1070 foundPlacement = { -1, 0.0 };
1071 if (index >= 0) {
1072 for ( auto &placement : placements ) {
1073 if ( placement.index > index )
1074 --placement.index;
1075 }
1076 }
1077
1078 return true;
1079 }
1080 else {
1081 // Toggle on
1082 float total = 0;
1083 int greatest = -1;
1084 unsigned nn = 0;
1085 for ( const auto &placement : placements ) {
1086 if ( placement.fraction > 0.0 && placement.index >= 0 ) {
1087 total += placement.fraction;
1088 greatest = std::max( greatest, placement.index );
1089 ++nn;
1090 }
1091 }
1092 // Turn on the sub-view, putting it lowest, and with average of the
1093 // heights of the other sub-views
1094 foundPlacement = { greatest + 1, total / nn };
1095
1096 return true;
1097 }
1098 }
1099 else
1100 // unknown sub-view
1101 return false;
1102}
1103
1104// If exclusive, make the chosen view take up all the height. Else,
1105// partition equally, putting the specified view on top.
1106// Be sure the sequence in which the other views appear is determinate.
1107void WaveTrackView::DoSetDisplay(Display display, bool exclusive)
1108{
1109 // Some generality here anticipating more than two views.
1110 // The order of sub-views in the array is not specified, so make it definite
1111 // by sorting by the view type constants.
1112 size_t ii = 0;
1113 std::vector< std::pair< WaveTrackViewConstants::Display, size_t > > pairs;
1114 WaveTrackSubViews::ForEach( [&pairs, &ii]( WaveTrackSubView &subView ){
1115 pairs.push_back( { subView.SubViewType().id, ii++ } );
1116 } );
1117 std::sort( pairs.begin(), pairs.end() );
1118
1119 int jj = 1;
1120 auto &placements = DoGetPlacements();
1121 for ( const auto &pair : pairs ) {
1122 auto &placement = placements[ pair.second ];
1123 if (pair.first == display) {
1124 // 0 for first view
1125 placement = { 0, 1.0 };
1126 }
1127 else if( exclusive )
1128 // -1 for not displayed
1129 placement = { -1, 0.0 };
1130 else
1131 // positions other than the first.
1132 // (Note that the fractions in the placement don't need to be
1133 // denominated to 1. Just make them all equal to get an equal
1134 // partitioning of the sub-views.)
1135 placement = { jj++, 1.0 };
1136 }
1137}
1138
1139namespace {
1140 template<typename Iter, typename Comp>
1141 const WaveClip* NextClipLooped(ViewInfo& viewInfo, Iter begin, Iter end, Comp comp)
1142 {
1143 auto it = WaveTrackUtils::SelectedClip(viewInfo, begin, end);
1144 if (it == end)
1145 it = std::find_if(begin, end, comp);
1146 else
1147 it = std::next(it);
1148
1149 if (it == end)
1150 return *begin;
1151 return *it;
1152 }
1153}
1154
1155bool WaveTrackView::SelectNextClip(ViewInfo& viewInfo, AudacityProject* project, bool forward)
1156{
1157 //Iterates through clips in a looped manner
1158 auto waveTrack = std::dynamic_pointer_cast<WaveTrack>(FindTrack());
1159 if (!waveTrack)
1160 return false;
1161 auto clips = waveTrack->SortedClipArray();
1162 if (clips.empty())
1163 return false;
1164
1165 const WaveClip* clip{ };
1166 if (forward)
1167 {
1168 clip = NextClipLooped(viewInfo, clips.begin(), clips.end(), [&](const WaveClip* other) {
1169 return other->GetPlayStartTime() >= viewInfo.selectedRegion.t1();
1170 });
1171 }
1172 else
1173 {
1174 clip = NextClipLooped(viewInfo, clips.rbegin(), clips.rend(), [&](const WaveClip* other) {
1175 return other->GetPlayStartTime() <= viewInfo.selectedRegion.t0();
1176 });
1177 }
1178
1179 viewInfo.selectedRegion.setTimes(clip->GetPlayStartTime(), clip->GetPlayEndTime());
1180 ProjectHistory::Get(*project).ModifyState(false);
1181
1182 // create and send message to screen reader
1183 auto it = std::find(clips.begin(), clips.end(), clip);
1184 auto index = std::distance(clips.begin(), it);
1185
1186 auto message = XP(
1187 /* i18n-hint:
1188 string is the name of a clip
1189 first number is the position of that clip in a sequence of clips,
1190 second number counts the clips */
1191 "%s, %d of %d clip",
1192 "%s, %d of %d clips",
1193 2
1194 )(
1195 clip->GetName(),
1196 static_cast<int>(index + 1),
1197 static_cast<int>(clips.size())
1198 );
1199
1200 TrackFocus::Get(*project).MessageForScreenReader(message);
1201 return true;
1202}
1203
1204auto WaveTrackView::GetSubViews( const wxRect &rect ) -> Refinement
1205{
1206 return GetSubViews(&rect);
1207}
1208
1209auto WaveTrackView::GetSubViews(const wxRect* rect) -> Refinement
1210{
1211 BuildSubViews();
1212
1213 // Collect the visible views in the right sequence
1214 struct Item {
1215 int index; float fraction; std::shared_ptr< TrackView > pView;
1216 };
1217 std::vector< Item > items;
1218 size_t ii = 0;
1219 float total = 0;
1220 const auto &placements = DoGetPlacements();
1222 auto& placement = placements[ii];
1223 auto index = placement.index;
1224 auto fraction = placement.fraction;
1225 if (index >= 0 && fraction > 0.0)
1226 total += fraction,
1227 items.push_back({ index, fraction, subView.shared_from_this() });
1228 ++ii;
1229 });
1230 std::sort(items.begin(), items.end(), [](const Item& a, const Item& b) {
1231 return a.index < b.index;
1232 });
1233
1234 // Remove views we don't need
1235 auto begin = items.begin(), end = items.end(),
1236 newEnd = std::remove_if(begin, end,
1237 [](const Item& item) { return !item.pView; });
1238 items.erase(newEnd, end);
1239
1240 Refinement results;
1241
1242 if (rect != nullptr)
1243 {
1244 // Assign coordinates, redenominating to the total height,
1245 // storing integer values
1246 results.reserve(items.size());
1247 const auto top = rect->GetTop();
1248 const auto height = rect->GetHeight();
1249 float partial = 0;
1250 wxCoord lastCoord = 0;
1251 for (const auto& item : items) {
1252 wxCoord newCoord = top + (partial / total) * height;
1253 results.emplace_back(newCoord, item.pView);
1254 partial += item.fraction;
1255 }
1256
1257 // Cache for the use of sub-view dragging
1258 mLastHeight = height;
1259 }
1260 else
1261 {
1262 std::transform(items.begin(), items.end(), std::back_inserter(results), [](const auto& item) {
1263 return std::make_pair(0, item.pView);
1264 });
1265 }
1266
1267 return results;
1268}
1269
1270/*
1271 Note that the WaveTrackView isn't in the TrackPanel subdivision, but it is
1272 set sometimes as the focused cell, and therefore the following functions can
1273 be visited. To visit their overrides in the sub-views and affordances,
1274 which are never focused, we must forward to them. To do that properly, if
1275 any cell declines to handle the event by setting it as skipped, it must be
1276 set again to not-skipped before attempting the next call-through.
1277 */
1278unsigned WaveTrackView::CaptureKey(wxKeyEvent& event, ViewInfo& viewInfo, wxWindow* pParent, AudacityProject* project)
1279{
1280 unsigned result{ RefreshCode::RefreshNone };
1281 auto pTrack = static_cast<WaveTrack*>(FindTrack().get());
1282 for (auto pChannel : TrackList::Channels(pTrack)) {
1283 event.Skip(false);
1284 auto &waveTrackView = WaveTrackView::Get(*pChannel);
1285 // Give sub-views first chance to handle the event
1286 for (auto &subView : waveTrackView.GetSubViews()) {
1287 // Event defaults in skipped state which must be turned off explicitly
1288 wxASSERT(!event.GetSkipped());
1289 result |= subView.second->CaptureKey(event, viewInfo, pParent, project);
1290 if (!event.GetSkipped()) {
1291 // sub view wants it
1292 mKeyEventDelegate = subView.second;
1293 return result;
1294 }
1295 else
1296 event.Skip(false);
1297 }
1298
1299 if (auto affordance = waveTrackView.GetAffordanceControls()) {
1300 result |= affordance->CaptureKey(event, viewInfo, pParent, project);
1301 if (!event.GetSkipped()) {
1302 mKeyEventDelegate = affordance;
1303 return result;
1304 }
1305 }
1306
1307 event.Skip(false);
1308 }
1309 switch (event.GetKeyCode())
1310 {
1311 case WXK_TAB:
1312 break;
1313 default:
1315 event, viewInfo, pParent, project);
1316 break;
1317 };
1318 if (!event.GetSkipped()) {
1319 mKeyEventDelegate = shared_from_this();
1320 }
1321
1322 return result;
1323}
1324
1325unsigned WaveTrackView::KeyDown(wxKeyEvent& event, ViewInfo& viewInfo, wxWindow* pParent, AudacityProject* project)
1326{
1327 unsigned result{ RefreshCode::RefreshNone };
1328 if (auto delegate = mKeyEventDelegate.lock()) {
1329 if (auto pWaveTrackView = dynamic_cast<WaveTrackView*>(delegate.get()))
1330 {
1331 if (event.GetKeyCode() == WXK_TAB)
1332 {
1333 SelectNextClip(viewInfo, project, event.GetModifiers() != wxMOD_SHIFT);
1334 result |= RefreshCode::RefreshCell;
1335 }
1336 else
1337 result |= pWaveTrackView->CommonTrackView::KeyDown(
1338 event, viewInfo, pParent, project);
1339 }
1340 else
1341 result |= delegate->KeyDown(event, viewInfo, pParent, project);
1342 }
1343 else
1344 event.Skip();
1345
1346 return result;
1347}
1348
1349unsigned WaveTrackView::Char(wxKeyEvent& event, ViewInfo& viewInfo, wxWindow* pParent, AudacityProject* project)
1350{
1351 unsigned result{ RefreshCode::RefreshNone };
1352 if (auto delegate = mKeyEventDelegate.lock()) {
1353 if (auto pWaveTrackView = dynamic_cast<WaveTrackView*>(delegate.get()))
1354 result |= pWaveTrackView->CommonTrackView::Char(
1355 event, viewInfo, pParent, project);
1356 else
1357 result |= delegate->Char(event, viewInfo, pParent, project);
1358 }
1359 else
1360 event.Skip();
1361
1362 return result;
1363}
1364
1366{
1367 unsigned result = RefreshCode::RefreshNone;
1368 if (auto delegate = mKeyEventDelegate.lock()) {
1369 if (auto waveTrackView = dynamic_cast<WaveTrackView*>(delegate.get()))
1370 result = waveTrackView->CommonTrackView::LoseFocus(project);
1371 else
1372 result = delegate->LoseFocus(project);
1373 mKeyEventDelegate.reset();
1374 }
1375 return result;
1376}
1377
1379{
1380 for (auto channel : TrackList::Channels(FindTrack().get()))
1381 {
1382 auto& view = TrackView::Get(*channel);
1383 if (auto affordance
1384 = std::dynamic_pointer_cast<WaveTrackAffordanceControls>(view.GetAffordanceControls()))
1385 {
1386 if (affordance->OnTextCut(project))
1387 return true;
1388 }
1389 }
1390 return false;
1391}
1392
1394{
1395 for (auto channel : TrackList::Channels(FindTrack().get()))
1396 {
1397 auto& view = TrackView::Get(*channel);
1398 if (auto affordance
1399 = std::dynamic_pointer_cast<WaveTrackAffordanceControls>(view.GetAffordanceControls()))
1400 {
1401 if (affordance->OnTextCopy(project))
1402 return true;
1403 }
1404 }
1405 return false;
1406}
1407
1408bool WaveTrackView::ClipDetailsVisible(const WaveClip& clip, const ZoomInfo& zoomInfo, const wxRect& viewRect)
1409{
1410 //Do not fold clips to line at sample zoom level, as
1411 //it may become impossible to 'unfold' it when clip is trimmed
1412 //to a single sample
1413 bool showSamples{ false };
1414 auto clipRect = ClipParameters::GetClipRect(clip, zoomInfo, viewRect, &showSamples);
1415 return showSamples || clipRect.width >= kClipDetailedViewMinimumWidth;
1416}
1417
1418wxRect WaveTrackView::ClipHitTestArea(const WaveClip& clip, const ZoomInfo& zoomInfo, const wxRect& viewRect)
1419{
1420 bool showSamples{ false };
1421 auto clipRect = ClipParameters::GetClipRect(clip, zoomInfo, viewRect, &showSamples);
1422 if (showSamples || clipRect.width >= kClipDetailedViewMinimumWidth)
1423 return clipRect;
1424
1425 return clipRect.Inflate(2, 0);
1426}
1427
1428bool WaveTrackView::HitTest(const WaveClip& clip, const ZoomInfo& viewInfo, const wxRect& viewRect, const wxPoint& pos)
1429{
1430 return ClipHitTestArea(clip, viewInfo, viewRect).Contains(pos);
1431}
1432
1434{
1435 for (auto channel : TrackList::Channels(FindTrack().get()))
1436 {
1437 auto& view = TrackView::Get(*channel);
1438 if (auto affordance
1439 = std::dynamic_pointer_cast<WaveTrackAffordanceControls>(view.GetAffordanceControls()))
1440 {
1441 if (affordance->OnTextPaste(project))
1442 return true;
1443 }
1444 }
1445 return false;
1446}
1447
1449{
1450 for (auto channel : TrackList::Channels(FindTrack().get()))
1451 {
1452 auto& view = TrackView::Get(*channel);
1453 if (auto affordance
1454 = std::dynamic_pointer_cast<WaveTrackAffordanceControls>(view.GetAffordanceControls()))
1455 {
1456 if (affordance->OnTextSelect(project))
1457 return true;
1458 }
1459 }
1460 return false;
1461}
1462
1463std::vector< std::shared_ptr< WaveTrackSubView > >
1465{
1466 BuildSubViews();
1467
1468 std::vector< std::shared_ptr< WaveTrackSubView > > results;
1470 results.push_back( std::static_pointer_cast<WaveTrackSubView>(
1471 subView.shared_from_this() ) );
1472 } );
1473 return results;
1474}
1475
1476std::shared_ptr<CommonTrackCell> WaveTrackView::GetAffordanceControls()
1477{
1478 auto track = FindTrack();
1479 if (!track->IsAlignedWithLeader())
1480 {
1481 return DoGetAffordance(track);
1482 }
1483 return {};
1484}
1485
1486void WaveTrackView::DoSetMinimized( bool minimized )
1487{
1488 BuildSubViews();
1489
1490 // May come here. Invoke also on sub-views.
1491 TrackView::DoSetMinimized( minimized );
1492 WaveTrackSubViews::ForEach( [minimized](WaveTrackSubView &subView){
1493 subView.DoSetMinimized( minimized );
1494 } );
1495}
1496
1497std::shared_ptr<CommonTrackCell> WaveTrackView::DoGetAffordance(const std::shared_ptr<Track>& track)
1498{
1499 if (mpAffordanceCellControl == nullptr)
1500 mpAffordanceCellControl = std::make_shared<WaveTrackAffordanceControls>(track);
1502}
1503
1506 return [](WaveTrack &track) {
1507 return std::make_shared<WaveTrackView>( track.SharedPointer() );
1508 };
1509}
1510
1511std::shared_ptr<TrackVRulerControls> WaveTrackView::DoGetVRulerControls()
1512{
1513 // This should never be called because of delegation to the spectrum or
1514 // waveform sub-view
1515 wxASSERT( false );
1516 return {};
1517}
1518
1519namespace
1520{
1521 // Returns an offset in seconds to be applied to the right clip
1522 // boundary so that it does not overlap the last sample
1524 const wxRect& viewRect,
1525 const ZoomInfo& zoomInfo,
1526 int rate,
1527 double& outAveragePPS,
1528 //Is zoom level sufficient to show individual samples?
1529 bool& outShowSamples)
1530 {
1531 static constexpr double pixelsOffset{ 2 };//The desired offset in pixels
1532
1533 auto h = zoomInfo.PositionToTime(0, 0, true);
1534 auto h1 = zoomInfo.PositionToTime(viewRect.width, 0, true);
1535
1536 // Determine whether we should show individual samples
1537 // or draw circular points as well
1538 outAveragePPS = viewRect.width / (rate * (h1 - h));// pixels per sample
1539 outShowSamples = outAveragePPS > 0.5;
1540
1541 if(outShowSamples)
1542 // adjustment so that the last circular point doesn't appear
1543 // to be hanging off the end
1544 return pixelsOffset / (outAveragePPS * rate); // pixels / ( pixels / second ) = seconds
1545 return .0;
1546 }
1547}
1548
1550 (bool spectrum, const SampleTrack *track, const WaveClip *clip, const wxRect &rect,
1551 const SelectedRegion &selectedRegion, const ZoomInfo &zoomInfo)
1552{
1553 tOffset = clip->GetPlayStartTime();
1554 rate = clip->GetRate();
1555
1556 h = zoomInfo.PositionToTime(0, 0
1557 , true
1558 );
1559 h1 = zoomInfo.PositionToTime(rect.width, 0
1560 , true
1561 );
1562
1563 double sel0 = selectedRegion.t0(); //left selection bound
1564 double sel1 = selectedRegion.t1(); //right selection bound
1565
1566 //If the track isn't selected, make the selection empty
1567 if (!track->GetSelected() &&
1568 (spectrum ||
1569 !SyncLock::IsSyncLockSelected(track))) { // PRL: why was there a difference for spectrum?
1570 sel0 = sel1 = 0.0;
1571 }
1572
1573 const double trackLen = clip->GetPlayEndTime() - clip->GetPlayStartTime();
1574
1575 tpre = h - tOffset; // offset corrected time of
1576 // left edge of display
1577 tpost = h1 - tOffset; // offset corrected time of
1578 // right edge of display
1579
1580 const double sps = 1. / rate; //seconds-per-sample
1581
1582 // Calculate actual selection bounds so that t0 > 0 and t1 < the
1583 // end of the track
1584 t0 = std::max(tpre, .0);
1585 t1 = std::min(tpost, trackLen - sps * .99)
1587
1588 // Make sure t1 (the right bound) is greater than 0
1589 if (t1 < 0.0) {
1590 t1 = 0.0;
1591 }
1592
1593 // Make sure t1 is greater than t0
1594 if (t0 > t1) {
1595 t0 = t1;
1596 }
1597
1598 // Use the WaveTrack method to show what is selected and 'should' be copied, pasted etc.
1599 ssel0 = std::max(sampleCount(0), spectrum
1600 ? sampleCount((sel0 - tOffset) * rate + .99) // PRL: why?
1601 : track->TimeToLongSamples(sel0 - tOffset)
1602 );
1603 ssel1 = std::max(sampleCount(0), spectrum
1604 ? sampleCount((sel1 - tOffset) * rate + .99) // PRL: why?
1605 : track->TimeToLongSamples(sel1 - tOffset)
1606 );
1607
1608 //trim selection so that it only contains the actual samples
1609 if (ssel0 != ssel1 && ssel1 > (sampleCount)(0.5 + trackLen * rate)) {
1610 ssel1 = sampleCount( 0.5 + trackLen * rate );
1611 }
1612
1613 // The variable "hiddenMid" will be the rectangle containing the
1614 // actual waveform, as opposed to any blank area before
1615 // or after the track, as it would appear without the fisheye.
1616 hiddenMid = rect;
1617
1618 // If the left edge of the track is to the right of the left
1619 // edge of the display, then there's some unused area to the
1620 // left of the track. Reduce the "hiddenMid"
1621 hiddenLeftOffset = 0;
1622 if (tpre < 0) {
1623 // Fix Bug #1296 caused by premature conversion to (int).
1624 wxInt64 time64 = zoomInfo.TimeToPosition(tOffset, 0 , true);
1625 if( time64 < 0 )
1626 time64 = 0;
1627 hiddenLeftOffset = (time64 < rect.width) ? (int)time64 : rect.width;
1628
1630 hiddenMid.width -= hiddenLeftOffset;
1631 }
1632
1633 // If the right edge of the track is to the left of the right
1634 // edge of the display, then there's some unused area to the right
1635 // of the track. Reduce the "hiddenMid" rect by the
1636 // size of the blank area.
1637 if (tpost > t1) {
1638 wxInt64 time64 = zoomInfo.TimeToPosition(tOffset+t1, 0 , true);
1639 if( time64 < 0 )
1640 time64 = 0;
1641 const int hiddenRightOffset = (time64 < rect.width) ? (int)time64 : rect.width;
1642
1643 hiddenMid.width = std::max(0, hiddenRightOffset - hiddenLeftOffset);
1644 }
1645 // The variable "mid" will be the rectangle containing the
1646 // actual waveform, as distorted by the fisheye,
1647 // as opposed to any blank area before or after the track.
1648 mid = rect;
1649
1650 // If the left edge of the track is to the right of the left
1651 // edge of the display, then there's some unused area to the
1652 // left of the track. Reduce the "mid"
1653 leftOffset = 0;
1654 if (tpre < 0) {
1655 wxInt64 time64 = zoomInfo.TimeToPosition(tOffset, 0 , false);
1656 if( time64 < 0 )
1657 time64 = 0;
1658 leftOffset = (time64 < rect.width) ? (int)time64 : rect.width;
1659
1660 mid.x += leftOffset;
1661 mid.width -= leftOffset;
1662 }
1663
1664 // If the right edge of the track is to the left of the right
1665 // edge of the display, then there's some unused area to the right
1666 // of the track. Reduce the "mid" rect by the
1667 // size of the blank area.
1668 if (tpost > t1) {
1669 wxInt64 time64 = zoomInfo.TimeToPosition(tOffset+t1, 0 , false);
1670 if( time64 < 0 )
1671 time64 = 0;
1672 const int distortedRightOffset = (time64 < rect.width) ? (int)time64 : rect.width;
1673
1674 mid.width = std::max(0, distortedRightOffset - leftOffset);
1675 }
1676}
1677
1678wxRect ClipParameters::GetClipRect(const WaveClip& clip, const ZoomInfo& zoomInfo, const wxRect& viewRect, bool* outShowSamples)
1679{
1680 auto srs = 1. / static_cast<double>(clip.GetRate());
1681 double averagePixelsPerSample{};
1682 bool showIndividualSamples{};
1683 auto clipEndingAdjustemt
1685 if (outShowSamples != nullptr)
1686 *outShowSamples = showIndividualSamples;
1687 constexpr auto edgeLeft = static_cast<ZoomInfo::int64>(std::numeric_limits<int>::min());
1688 constexpr auto edgeRight = static_cast<ZoomInfo::int64>(std::numeric_limits<int>::max());
1689 auto left = std::clamp(
1690 zoomInfo.TimeToPosition(
1691 clip.GetPlayStartTime(), viewRect.x, true
1692 ), edgeLeft, edgeRight
1693 );
1694 auto right = std::clamp(
1695 zoomInfo.TimeToPosition(
1696 clip.GetPlayEndTime() - .99 * srs + clipEndingAdjustemt, viewRect.x, true
1697 ), edgeLeft, edgeRight
1698 );
1699 if (right >= left)
1700 {
1701 //after clamping we can expect that left and right
1702 //are small enough to be put into int
1703 return wxRect(
1704 static_cast<int>(left),
1705 viewRect.y,
1706 std::max(1, static_cast<int>(right - left)),
1707 viewRect.height
1708 );
1709 }
1710 return wxRect();
1711}
1712
1713void WaveTrackView::Reparent( const std::shared_ptr<Track> &parent )
1714{
1715 // BuildSubViews(); // not really needed
1716 CommonTrackView::Reparent( parent );
1717 WaveTrackSubViews::ForEach( [&parent](WaveTrackSubView &subView){
1718 subView.Reparent( parent );
1719 } );
1721 mpAffordanceCellControl->Reparent(parent);
1722}
1723
1724std::weak_ptr<WaveClip> WaveTrackView::GetSelectedClip()
1725{
1726 if (auto affordance = std::dynamic_pointer_cast<WaveTrackAffordanceControls>(GetAffordanceControls()))
1727 {
1728 return affordance->GetSelectedClip();
1729 }
1730 return {};
1731}
1732
1734{
1735 if ( WaveTrackSubViews::size() == 0) {
1736 // On-demand steps that can't happen in the constructor
1737 auto pThis = const_cast<WaveTrackView*>( this );
1738 pThis->BuildAll();
1739 bool minimized = GetMinimized();
1740 pThis->WaveTrackSubViews::ForEach( [&]( WaveTrackSubView &subView ){
1741 subView.DoSetMinimized( minimized );
1742 } );
1743
1744 auto &placements = pThis->DoGetPlacements();
1745 if (placements.empty()) {
1746 placements.resize( WaveTrackSubViews::size() );
1747
1748 auto pTrack = pThis->FindTrack();
1749 auto display = TracksPrefs::ViewModeChoice();
1750 bool multi = (display == WaveTrackViewConstants::MultiView);
1751 if ( multi ) {
1752 pThis->SetMultiView( true );
1754 }
1755
1756 pThis->DoSetDisplay( display, !multi );
1757 }
1758 }
1759}
1760
1762 TrackPanelDrawingContext &context,
1763 const wxRect &rect, unsigned iPass )
1764{
1765 // Should not come here, drawing is now delegated to sub-views
1766 wxASSERT( false );
1767
1768 CommonTrackView::Draw( context, rect, iPass );
1769}
1770
1774 return [](auto &) { return SyncLockPolicy::Grouped; };
1775}
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: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: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)
sampleCount TimeToLongSamples(double t0) const
Convert correctly between an (absolute) time in seconds and a number of samples.
Definition: SampleTrack.cpp:43
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:162
bool GetSelected() const
Selectedness is always the same for all channels of a group.
Definition: Track.cpp:82
ChannelGroupData & GetGroupData()
Definition: Track.cpp:175
static auto Channels(TrackType *pTrack) -> TrackIterRange< TrackType >
Definition: Track.h:1417
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:101
double GetPlayStartTime() const noexcept
Definition: WaveClip.cpp:889
double GetPlayEndTime() const
Definition: WaveClip.cpp:899
int GetRate() const
Definition: WaveClip.h:140
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:51
const std::vector< Location > & Get() const
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
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)
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:501
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