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