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 "WaveTrack.h"
23
24#include "../../../../../images/Cursors.h"
25#include "AllThemeResources.h"
26
27#include "../../../../HitTestResult.h"
28#include "../../../../RefreshCode.h"
29#include "../../../../TrackArtist.h"
30#include "../../../../TrackPanel.h"
31#include "../../../../TrackPanelDrawingContext.h"
32#include "../../../../TrackPanelMouseEvent.h"
33#include "../../../../TrackPanelResizeHandle.h"
34#include "../../../../prefs/TracksPrefs.h"
35#include "CommandContext.h"
36#include "PitchAndSpeedDialog.h"
37#include "ProjectHistory.h"
38#include "SyncLock.h"
39#include "TrackFocus.h"
40#include "ViewInfo.h"
41
42#include "../../../ui/TimeShiftHandle.h"
43#include "../../../ui/ButtonHandle.h"
44#include "../../../ui/CommonTrackInfo.h"
45
46#include "ClipParameters.h"
50#include "WaveTrackUtilities.h"
52#include "WaveClipUIUtilities.h"
53
55
56using WaveChannelSubViewPtrs = std::vector<std::shared_ptr<WaveChannelSubView>>;
57
58namespace {
60 static PlacementArray &Get(Track &track);
61 static const PlacementArray &Get(const Track &track);
62 ~PlacementArray() = default;
63 std::unique_ptr<Cloneable<>> Clone() const {
64 return std::make_unique<PlacementArray>(*this); }
66 bool mMultiView{ false };
67};
68
70key { [](auto &) { return std::make_unique<PlacementArray>(); } };
71
72// Access for per-track effect list
74{
75 return track.Attachments::Get<PlacementArray>(key);
76}
77
79{
80 return Get(const_cast<Track &>(track));
81}
82}
83
85{
86 auto &waveChannel = *FindWaveChannel();
87 return PlacementArray::Get(waveChannel.GetTrack()).mPlacements;
88}
89
91{
92 return const_cast<WaveChannelView&>(*this).DoGetPlacements();
93}
94
96{
97 auto &waveTrack = FindWaveChannel()->GetTrack();
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 WaveChannelView::Get(*pView->FindWaveChannel()).RestorePlacements(
254 rollback ? mOrigPlacements : mNewPlacements);
255 }
256 }
257
258 std::weak_ptr<WaveChannelView> 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 WaveChannelView &view,
274 WaveChannelSubView &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
308 std::shared_ptr<const Track> FindTrack() const override
309 {
310 auto pView = mAdjuster.mwView.lock();
311 if (pView)
312 return TrackFromChannel(pView->FindChannel());
313 return nullptr;
314 }
315
317 const TrackPanelMouseEvent &event, AudacityProject *pProject ) override
318 {
319 using namespace RefreshCode;
320 const auto &permutation = mAdjuster.mPermutation;
321 const auto size = permutation.size();
322 if ( mMySubView >= size )
323 return Cancelled;
324
325 if (event.event.LeftDClick()) {
326 for ( auto &placement : mAdjuster.mNewPlacements ) {
327 if ( placement.index >= 0 )
328 placement.fraction = 1.0f;
329 else
330 placement.fraction = 0.0f;
331 }
332 mAdjuster.UpdateViews( false );
333 ProjectHistory::Get( *pProject ).ModifyState( false );
334
335 // Do not start a drag
336 return Cancelled | RefreshAll;
337 }
338
339 const auto &rect = event.rect;
340 const auto height = rect.GetHeight();
341 mOrigHeight = height;
342
344
345 // Find the total height of the sub-views that may resize
346 mTotalHeight = 0;
347 auto index = ( mTop ? mAdjuster.mFirstSubView : mMySubView );
348 const auto end = ( mTop ? mMySubView + 1 : permutation.size() );
349 for (; index != end; ++index)
350 mTotalHeight += mOrigHeights[ index ];
351
352 wxASSERT( height == mOrigHeights[ mMySubView ] );
353
354 // Compute the maximum and minimum Y coordinates for drag effect
355 if ( mTop ) {
356 mOrigY = rect.GetTop();
357 mYMax = rect.GetBottom();
358 mYMin = mYMax - mTotalHeight + 1;
359 }
360 else {
361 mOrigY = rect.GetBottom();
362 mYMin = rect.GetTop();
363 mYMax = mYMin + mTotalHeight - 1;
364 }
365
366 return RefreshNone;
367 }
368
370 {
371 using namespace RefreshCode;
372 auto pView = mAdjuster.mwView.lock();
373 if ( !pView )
374 return Cancelled;
375
376 // Find new height for the dragged sub-view
377 auto newY = std::max( mYMin, std::min( mYMax, event.event.GetY() ) );
378 const auto delta = newY - mOrigY;
379 wxCoord newHeight = mTop
380 ? mOrigHeight - delta
381 : mOrigHeight + delta
382 ;
383 wxASSERT( newHeight >= 0 && newHeight <= mTotalHeight );
384 if ( newHeight < MinHeight )
385 // Snap the dragged sub-view to nothing
386 newHeight = 0;
387
388 // Reassign height for the dragged sub-view
389 auto &myPlacement =
391 myPlacement.fraction = newHeight;
392
393 // Grow or shrink other sub-views
394 auto excess = newHeight - mOrigHeight; // maybe negative
395 const auto adjustHeight = [&](size_t ii) {
396 if (excess == 0)
397 return true;
398
399 const auto oldFraction = mOrigHeights[ ii ];
400
401 auto index = mAdjuster.mPermutation[ ii ];
402 auto &placement = mAdjuster.mNewPlacements[ index ];
403 auto &fraction = placement.fraction;
404
405 if (excess > oldFraction) {
406 excess -= oldFraction, fraction = 0;
407 return false;
408 }
409 else {
410 auto newFraction = oldFraction - excess;
411 if ( newFraction < MinHeight ) {
412 // This snaps very short sub-views to nothing
413 myPlacement.fraction += newFraction;
414 fraction = 0;
415 }
416 else
417 fraction = newFraction;
418 return true;
419 }
420 };
421 if ( mTop ) {
422 for ( size_t ii = mMySubView; ii > 0; ) {
423 --ii;
424 if ( adjustHeight( ii ) )
425 break;
426 }
427 }
428 else {
429 for ( size_t ii = mMySubView + 1, size = mAdjuster.mPermutation.size();
430 ii < size; ++ii
431 ) {
432 if ( adjustHeight( ii ) )
433 break;
434 }
435 }
436
437 // Save adjustment to the track and request a redraw
438 mAdjuster.UpdateViews( false );
439 return RefreshAll;
440 }
441
443 const TrackPanelMouseState &state, AudacityProject * ) override
444 {
445 static auto resizeCursor =
446 ::MakeCursor(wxCURSOR_ARROW, SubViewsCursorXpm, 16, 16);
447 return {
448 XO(
449"Click and drag to adjust sizes of sub-views, double-click to split evenly"),
450 &*resizeCursor
451 };
452 }
453
455 const TrackPanelMouseEvent &event, AudacityProject *pProject,
456 wxWindow *pParent) override
457 {
458 ProjectHistory::Get( *pProject ).ModifyState( false );
460 }
461
463 {
464 mAdjuster.UpdateViews( true );
466 }
467
468private:
469
471 std::vector<wxCoord> mOrigHeights;
472
473 // An index into mAdjuster.mPermutation
474 size_t mMySubView{};
475
476 wxCoord mYMin{}, mYMax{};
477 wxCoord mViewHeight{}; // Total height of all sub-views
478 wxCoord mTotalHeight{}; // Total height of adjusting sub-views only
479 wxCoord mOrigHeight{};
480 wxCoord mOrigY{};
481
482 // Whether we drag the top or the bottom of the sub-view
483 bool mTop{};
484};
485
487{
488public:
489 // Make it somewhat wider than the close button
490 enum { HotZoneWidth = 3 * kTrackInfoBtnSize / 2 };
491
492 static UIHandlePtr HitTest(std::weak_ptr<SubViewRearrangeHandle> &holder,
493 WaveChannelView &view, WaveChannelSubView &subView,
494 const TrackPanelMouseState &state)
495 {
496 if ( !view.GetMultiView() )
497 return {};
498
499 SubViewAdjuster adjuster{ view };
500 if ( adjuster.NVisible() < 2 )
501 return {};
502
503 auto relX = state.state.GetX() - state.rect.GetLeft();
504 if ( relX >= HotZoneWidth )
505 return {};
506
507 auto index = adjuster.FindIndex( subView );
508
509 // Hit on the rearrange cursor only in the top and bottom thirds of
510 // sub-view height, leaving the rest free to hit the selection cursor
511 // first.
512 // And also exclude the top third of the topmost sub-view and bottom
513 // third of bottommost.
514 auto relY = state.state.GetY() - state.rect.GetTop();
515 auto height = state.rect.GetHeight();
516 bool hit =
517 ( ( 3 * relY < height ) && index > 0 ) // top hit
518 ||
519 ( ( 3 * relY > 2 * height ) &&
520 index < adjuster.mPermutation.size() - 1 ) // bottom
521 ;
522 if ( ! hit )
523 return {};
524
525 auto result = std::make_shared< SubViewRearrangeHandle >(
526 std::move( adjuster ),
527 index, view.GetLastHeight()
528 );
529 result = AssignUIHandlePtr( holder, result );
530 return result;
531 }
532
534 SubViewAdjuster &&adjuster, size_t subViewIndex,
535 wxCoord viewHeight )
536 : mAdjuster{ std::move( adjuster ) }
537 , mMySubView{ subViewIndex }
538 , mViewHeight{ viewHeight }
539 {
540 }
541
542 std::shared_ptr<const Track> FindTrack() const override
543 {
544 auto pView = mAdjuster.mwView.lock();
545 if (pView)
546 return TrackFromChannel(pView->FindChannel());
547 return nullptr;
548 }
549
551 const TrackPanelMouseEvent &event, AudacityProject *pProject ) override
552 {
553 using namespace RefreshCode;
554 const auto &permutation = mAdjuster.mPermutation;
555 const auto size = permutation.size();
556 if ( mMySubView >= size )
557 return Cancelled;
558
560
561 // Find y coordinate of first sub-view
562 wxCoord heightAbove = 0;
563 for (auto index = mAdjuster.mFirstSubView;
564 index != mMySubView; ++index)
565 heightAbove += mHeights[ index ];
566 mTopY = event.rect.GetTop() - heightAbove;
567
568 return RefreshNone;
569 }
570
571 bool Clicked() const { return !mHeights.empty(); }
572
574
576 {
577 // Disregard x coordinate -- so the mouse need not be in any sub-view,
578 // just in the correct range of y coordinates
579 auto yy = event.event.GetY();
580 auto coord = mTopY;
581 size_t ii = mAdjuster.mFirstSubView;
582 if ( yy < mTopY )
583 return ( mMySubView == ii ) ? Neutral : Upward;
584
585 for ( auto nn = mHeights.size(); ii < nn; ++ii ) {
586 const auto height = mHeights[ ii ];
587 coord += height;
588 if ( yy < coord )
589 break;
590 }
591
592 if ( ii < mMySubView ) {
593 if ( yy < coord - mHeights[ ii ] + mHeights[ mMySubView ] )
594 return Upward;
595 }
596
597 if ( ii > mMySubView ) {
598 if( mMySubView < mHeights.size() - 1 &&
599 yy >= coord - mHeights[ mMySubView ] )
600 return Downward;
601 }
602
603 return Neutral;
604 }
605
607 {
608 using namespace RefreshCode;
609 auto pView = mAdjuster.mwView.lock();
610 if ( !pView )
611 return Cancelled;
612
613 switch( DragChoice( event ) ) {
614 case Upward:
615 {
617 std::swap(
620 );
621 --mMySubView;
622 break;
623 }
624 case Downward:
625 {
627 std::swap(
630 );
631 ++mMySubView;
632 break;
633 }
634 default:
635 return RefreshNone;
636 }
637
638 // Save adjustment to the track and request a redraw
639 mAdjuster.UpdateViews( false );
640 return RefreshAll;
641 }
642
644 const TrackPanelMouseState &state, AudacityProject * ) override
645 {
646 static auto hoverCursor =
647 ::MakeCursor(wxCURSOR_HAND, RearrangeCursorXpm, 16, 16);
648 static auto clickedCursor =
649 ::MakeCursor(wxCURSOR_HAND, RearrangingCursorXpm, 16, 16);
650 return {
651 XO("Click and drag to rearrange sub-views"),
652 Clicked() ? &*clickedCursor : &*hoverCursor,
653 XO("Rearrange sub-views")
654 };
655 }
656
658 const TrackPanelMouseEvent &event, AudacityProject *pProject,
659 wxWindow *pParent) override
660 {
661 ProjectHistory::Get( *pProject ).ModifyState( false );
663 }
664
666 {
667 mAdjuster.UpdateViews( true );
669 }
670
671private:
672
674 std::vector<wxCoord> mHeights;
675 wxCoord mTopY;
676
677 // An index into mAdjuster.mPermutation
678 size_t mMySubView{};
679
680 wxCoord mViewHeight{}; // Total height of all sub-views
681};
682
684{
685 static wxRect GetButtonRect( const wxRect &rect )
686 {
687 return {
688 rect.GetLeft(),
689 rect.GetTop(),
692 };
693 }
694
695public:
696 static UIHandlePtr HitTest(std::weak_ptr<SubViewCloseHandle> &holder,
697 WaveChannelView &view, WaveChannelSubView &subView,
698 const TrackPanelMouseState &state)
699 {
700 SubViewAdjuster adjuster{ view };
701 if ( adjuster.NVisible() < 2 )
702 return {};
703
704 const auto rect = GetButtonRect( state.rect );
705 if ( !rect.Contains( state.state.GetPosition() ) )
706 return {};
707 auto index = adjuster.FindIndex( subView );
708 auto result = std::make_shared<SubViewCloseHandle>(
709 std::move(adjuster), index, view.FindChannel(), rect);
710 result = AssignUIHandlePtr( holder, result );
711 return result;
712 }
713
715 SubViewAdjuster &&adjuster, size_t index,
716 const std::shared_ptr<Channel> &pChannel, const wxRect &rect )
717 : ButtonHandle{ static_cast<Track&>(pChannel->GetChannelGroup())
718 .SharedPointer(),
719 rect }
720 , mpChannel{ pChannel }
721 , mAdjuster{ std::move( adjuster ) }
722 , mMySubView{ index }
723 {
724 }
725
727 const wxMouseEvent &event, AudacityProject *pProject, wxWindow *pParent)
728 override
729 {
730 ProjectHistory::Get( *pProject ).ModifyState( false );
731 auto &myPlacement =
733 myPlacement.fraction = 0;
734 mAdjuster.UpdateViews( false );
736 }
737
739 const wxMouseState &state, AudacityProject &project) const override
740 {
741 return XO("Close sub-view");
742 }
743
744 // TrackPanelDrawable implementation
745 void Draw(
746 TrackPanelDrawingContext &context, const wxRect &rect, unsigned iPass )
747 override
748 {
749 if (iPass == TrackArtist::PassMargins) { // after PassTracks
751 context, GetButtonRect(rect), mpChannel.lock().get(), this);
752 }
753 }
754
755private:
756 std::weak_ptr<Channel> mpChannel;
758 size_t mMySubView{};
759};
760
761std::pair<
762 bool, // if true, hit-testing is finished
763 std::vector<UIHandlePtr>
765 const TrackPanelMouseState &state,
766 const AudacityProject *pProject, int currentTool, bool bMultiTool,
767 const std::shared_ptr<WaveChannel> &wc)
768{
769 const auto waveTrack = wc->GetTrack().SharedPointer<WaveTrack>();
771 state, pProject, currentTool, bMultiTool, wc, *this);
772 if (results.first)
773 return results;
774
775 auto pWaveChannelView = mwWaveChannelView.lock();
776 if ( pWaveChannelView && !state.state.HasModifiers() ) {
777 if ( auto pHandle = SubViewCloseHandle::HitTest(
779 *pWaveChannelView, *this, state ) )
780 results.second.push_back( pHandle );
781
782 auto &&channels = waveTrack->Channels();
783 if(channels.size() > 1) {
784 // Only one cell is tested and we need to know
785 // which one and it's relative location to the border.
786 auto subviews = pWaveChannelView->GetSubViews();
787 auto currentSubview = std::find_if(subviews.begin(), subviews.end(),
788 [self = shared_from_this()](const auto& p){
789 return self == p.second;
790 });
791 if (currentSubview != subviews.end())
792 {
793 auto currentSubviewIndex = std::distance(subviews.begin(), currentSubview);
794
795 const auto py = state.state.GetY();
796 const auto topBorderHit = std::abs(py - state.rect.GetTop())
798 const auto bottomBorderHit = std::abs(py - state.rect.GetBottom())
800
801 auto it = channels.find(wc);
802 auto currentChannelIndex = std::distance(channels.begin(), it);
803
804 if (//for not-last-view check the bottom border hit
805 ((currentChannelIndex != channels.size() - 1)
806 && (currentSubviewIndex == static_cast<int>(subviews.size()) - 1)
807 && bottomBorderHit)
808 ||
809 //or for not-first-view check the top border hit
810 ((currentChannelIndex != 0) && currentSubviewIndex == 0 && topBorderHit))
811 {
812 //depending on which border hit test succeeded on we
813 //need to choose a proper target for resizing
814 if (!bottomBorderHit)
815 --it;
816 auto result = std::make_shared<TrackPanelResizeHandle>(*it, py);
817 result = AssignUIHandlePtr(mResizeHandle, result);
818 results.second.push_back(result);
819 }
820 }
821 }
822
823 if ( auto pHandle = SubViewAdjustHandle::HitTest(
825 *pWaveChannelView, *this, state ) )
826 results.second.push_back( pHandle );
827 if ( auto pHandle = SubViewRearrangeHandle::HitTest(
829 *pWaveChannelView, *this, state ) )
830 results.second.push_back( pHandle );
831 }
832 if( pWaveChannelView )
833 {
834 if (auto pHandle = WaveClipAdjustBorderHandle::HitTest(
836 *pWaveChannelView, pProject, state))
837 results.second.push_back(pHandle);
838 }
839 if (auto result = CutlineHandle::HitTest(
840 mCutlineHandle, state.state, state.rect, pProject, waveTrack))
841 // This overriding test applies in all tools
842 results.second.push_back(result);
843
844 return results;
845}
846
847
849 TrackPanelDrawingContext &context, const WaveChannel &channel,
850 const wxRect &rect)
851{
852 auto &dc = context.dc;
853 const auto artist = TrackArtist::Get( context );
854
855 const auto &zoomInfo = *artist->pZoomInfo;
856
857#ifdef EXPERIMENTAL_TRACK_PANEL_HIGHLIGHTING
858 auto target2 = dynamic_cast<CutlineHandle*>(context.target.get());
859#endif
860 // x coordinates for bold lines will be the same across channels
861 for (const auto loc : FindWaveTrackLocations(channel.GetTrack())) {
862 bool highlightLoc = false;
863#ifdef EXPERIMENTAL_TRACK_PANEL_HIGHLIGHTING
864 highlightLoc =
865 target2 && target2->GetTrack().get() == &channel &&
866 target2->GetLocation() == loc;
867#endif
868 const int xx = zoomInfo.TimeToPosition(loc.pos);
869 if (xx >= 0 && xx < rect.width) {
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 dc.SetPen( highlightLoc ? AColor::uglyPen : *wxRED_PEN );
873 AColor::Line(dc, (int) (rect.x + xx), rect.y, (int) (rect.x + xx), rect.y + rect.height);
874 dc.SetPen( highlightLoc ? AColor::uglyPen : *wxGREY_PEN );
875 AColor::Line(dc, (int) (rect.x + xx + 1), rect.y, (int) (rect.x + xx + 1), rect.y + rect.height);
876 }
877 }
878}
879
880std::weak_ptr<WaveChannelView> WaveChannelSubView::GetWaveChannelView() const
881{
882 return mwWaveChannelView;
883}
884
886 const wxRect &rect, const wxPoint *pPosition, AudacityProject *pProject)
887 -> std::vector<MenuItem>
888{
889 auto pChannel = FindWaveChannel();
890 if (pChannel != nullptr && pPosition != nullptr)
891 {
892 auto &track = pChannel->GetTrack();
893 const auto &viewInfo = ViewInfo::Get(*pProject);
894 const auto t = viewInfo.PositionToTime(pPosition->x, rect.x);
895 if ((track.IsSelected() &&
896 t > viewInfo.selectedRegion.t0() && t < viewInfo.selectedRegion.t1() &&
898 viewInfo.selectedRegion.t0(), viewInfo.selectedRegion.t1())
899 .empty())
900 ||
901 WaveChannelUtilities::GetClipAtTime(**track.Channels().begin(), t))
902 {
904 }
905 }
906 return {
907 { L"Paste", XO("Paste") },
908 {},
909 { L"TrackMute", XO("Mute/Unmute Track") },
910 };
911}
912
914{
915 return static_cast<WaveChannelView&>(ChannelView::Get(channel));
916}
917
919{
920 return Get(const_cast<WaveChannel&>(channel));
921}
922
924{
925 return static_cast<WaveChannelView*>(ChannelView::Find(pChannel));
926}
927
929{
930 return Find(const_cast<WaveChannel*>(pChannel));
931}
932
934{
935 assert(wt.Channels().size() > 0);
936 return Get(**wt.Channels().begin());
937}
938
940{
941 return GetFirst(const_cast<WaveTrack&>(wt));
942}
943
945{
946 return pWt ? &GetFirst(*pWt): nullptr;
947}
948
950{
951 return pWt ? &GetFirst(*pWt): nullptr;
952}
953
955 : CommonChannelView{ waveChannelView.FindChannel() }
956{
957 mwWaveChannelView = std::static_pointer_cast<WaveChannelView>(
958 waveChannelView.shared_from_this() );
959}
960
961std::shared_ptr<WaveChannel> WaveChannelSubView::FindWaveChannel()
962{
963 return FindChannel<WaveChannel>();
964}
965
967
968}
969
971{
972}
973
974std::shared_ptr<WaveChannel> WaveChannelView::FindWaveChannel()
975{
976 return FindChannel<WaveChannel>();
977}
978
979void WaveChannelView::CopyTo(Track &track, size_t iChannel) const
980{
982 auto &other = ChannelView::Get(*track.GetChannel(0));
983 if (const auto pOther = dynamic_cast<WaveChannelView*>(&other)) {
984 // only these fields are important to preserve in undo/redo history
985 pOther->RestorePlacements( SavePlacements() );
986 pOther->DoGetMultiView() = DoGetMultiView();
987
988 auto srcSubViewsPtrs =
989 const_cast<WaveChannelView*>(this)->GetAllSubViews();
990 auto destSubViewsPtrs =
991 const_cast<WaveChannelView*>(pOther)->GetAllSubViews();
992 wxASSERT(srcSubViewsPtrs.size() == destSubViewsPtrs.size());
993
994 for(auto i = 0; i != srcSubViewsPtrs.size(); i++){
995 srcSubViewsPtrs[i]->CopyToSubView(destSubViewsPtrs[i].get());
996 }
997 }
998}
999
1000std::vector<UIHandlePtr> WaveChannelView::DetailedHitTest(
1001 const TrackPanelMouseState &st,
1002 const AudacityProject *pProject, int currentTool, bool bMultiTool)
1003{
1004 // should not come here any more, delegation to sub-view instead
1005 wxASSERT( false );
1006 return {};
1007}
1008
1009std::pair< bool, std::vector<UIHandlePtr> >
1011 const TrackPanelMouseState &st,
1012 const AudacityProject *pProject, int currentTool, bool bMultiTool,
1013 const std::shared_ptr<WaveChannel> &pChannel,
1014 CommonChannelView &view)
1015{
1016 // common hit-testing for different sub-view types, to help implement their
1017 // DetailedHitTest()
1018
1019 // This is the only override of Track::DetailedHitTest that still
1020 // depends on the state of the Tools toolbar.
1021 // If that toolbar were eliminated, this could simplify to a sequence of
1022 // hit test routines describable by a table.
1023
1024 std::vector<UIHandlePtr> results;
1025
1026 const auto& viewInfo = ViewInfo::Get(*pProject);
1027 const auto pTrack = pChannel->GetTrack().SharedPointer<WaveTrack>();
1028
1029 for (const auto &clip : pChannel->Intervals())
1030 {
1031 if (!WaveChannelView::ClipDetailsVisible(*clip, viewInfo, st.rect)
1032 && HitTest(*clip, viewInfo, st.rect, st.state.GetPosition()))
1033 {
1034 auto &waveChannelView = WaveChannelView::Get(*pChannel);
1035 results.push_back(
1037 waveChannelView.mAffordanceHandle,
1038 std::make_shared<WaveTrackAffordanceHandle>(pTrack, clip)
1039 )
1040 );
1041 }
1042 }
1043
1044 if (bMultiTool && st.state.CmdDown()) {
1045 // Ctrl modifier key in multi-tool overrides everything else
1046 // (But this does not do the time shift constrained to the vertical only,
1047 // which is what happens when you hold Ctrl in the Time Shift tool mode)
1049 pTrack, false);
1050 if (result)
1051 results.push_back(result);
1052 return { true, results };
1053 }
1054
1055 return { false, results };
1056}
1057
1059 -> std::vector<WaveChannelSubView::Type>
1060{
1061 BuildSubViews();
1062
1063 // Collect the display types of visible views and sort them by position
1064 using Pair = std::pair<int, WaveChannelSubView::Type>;
1065 std::vector< Pair > pairs;
1066 size_t ii = 0;
1067 const auto &placements = DoGetPlacements();
1069 auto &placement = placements[ii];
1070 if (placement.fraction > 0)
1071 pairs.emplace_back(placement.index, subView.SubViewType());
1072 ++ii;
1073 } );
1074 std::sort( pairs.begin(), pairs.end() );
1075 std::vector<WaveChannelSubView::Type> results;
1076 for (const auto &pair : pairs)
1077 results.push_back(pair.second);
1078 return results;
1079}
1080
1081void WaveChannelView::SetDisplay(Display display, bool exclusive)
1082{
1083 BuildSubViews();
1084 DoSetDisplay( display, exclusive );
1085}
1086
1088{
1089 size_t ii = 0;
1090 size_t found = 0;
1091 if (WaveChannelSubViews::FindIf([&](const WaveChannelSubView &subView) {
1092 if (subView.SubViewType().id == display) {
1093 found = ii;
1094 return true;
1095 }
1096 ++ii;
1097 return false;
1098 })) {
1099 auto &placements = DoGetPlacements();
1100 auto &foundPlacement = placements[found];
1101 if ( foundPlacement.fraction > 0.0 ) {
1102 // Toggle off
1103
1104 if (GetDisplays().size() < 2)
1105 // refuse to do it
1106 return false;
1107
1108 auto index = foundPlacement.index;
1109 foundPlacement = { -1, 0.0 };
1110 if (index >= 0) {
1111 for ( auto &placement : placements ) {
1112 if ( placement.index > index )
1113 --placement.index;
1114 }
1115 }
1116
1117 return true;
1118 }
1119 else {
1120 // Toggle on
1121 float total = 0;
1122 int greatest = -1;
1123 unsigned nn = 0;
1124 for ( const auto &placement : placements ) {
1125 if ( placement.fraction > 0.0 && placement.index >= 0 ) {
1126 total += placement.fraction;
1127 greatest = std::max( greatest, placement.index );
1128 ++nn;
1129 }
1130 }
1131 // Turn on the sub-view, putting it lowest, and with average of the
1132 // heights of the other sub-views
1133 foundPlacement = { greatest + 1, total / nn };
1134
1135 return true;
1136 }
1137 }
1138 else
1139 // unknown sub-view
1140 return false;
1141}
1142
1143// If exclusive, make the chosen view take up all the height. Else,
1144// partition equally, putting the specified view on top.
1145// Be sure the sequence in which the other views appear is determinate.
1146void WaveChannelView::DoSetDisplay(Display display, bool exclusive)
1147{
1148 // Some generality here anticipating more than two views.
1149 // The order of sub-views in the array is not specified, so make it definite
1150 // by sorting by the view type constants.
1151 size_t ii = 0;
1152 std::vector<std::pair<WaveChannelViewConstants::Display, size_t>> pairs;
1153 WaveChannelSubViews::ForEach([&pairs, &ii](WaveChannelSubView &subView){
1154 pairs.push_back({ subView.SubViewType().id, ii++ });
1155 });
1156 std::sort( pairs.begin(), pairs.end() );
1157
1158 int jj = 1;
1159 auto &placements = DoGetPlacements();
1160 for ( const auto &pair : pairs ) {
1161 auto &placement = placements[ pair.second ];
1162 if (pair.first == display) {
1163 // 0 for first view
1164 placement = { 0, 1.0 };
1165 }
1166 else if( exclusive )
1167 // -1 for not displayed
1168 placement = { -1, 0.0 };
1169 else
1170 // positions other than the first.
1171 // (Note that the fractions in the placement don't need to be
1172 // denominated to 1. Just make them all equal to get an equal
1173 // partitioning of the sub-views.)
1174 placement = { jj++, 1.0 };
1175 }
1176}
1177
1178namespace {
1179 template<typename Iter>
1180 Iter SelectedClip(const ViewInfo& viewInfo, Iter begin, Iter end)
1181 {
1183 const auto isClipSelected =
1184 [&viewInfo](const std::shared_ptr<WaveChannelInterval> &pClip) {
1185 return pClip->GetPlayStartTime() == viewInfo.selectedRegion.t0() &&
1186 pClip->GetPlayEndTime() == viewInfo.selectedRegion.t1();
1187 };
1188 return std::find_if(begin, end, isClipSelected);
1189 }
1190
1191 template<typename Iter, typename Comp>
1192 std::shared_ptr<WaveChannelInterval>
1193 NextClipLooped(ViewInfo& viewInfo, Iter begin, Iter end, Comp comp)
1194 {
1195 auto it = SelectedClip(viewInfo, begin, end);
1196 if (it == end)
1197 it = std::find_if(begin, end, comp);
1198 else
1199 it = std::next(it);
1200
1201 if (it == end)
1202 return *begin;
1203 return *it;
1204 }
1205}
1206
1208 ViewInfo& viewInfo, AudacityProject* project, bool forward)
1209{
1210 //Iterates through clips in a looped manner
1211 const auto pChannel = FindWaveChannel();
1212 if (!pChannel)
1213 return false;
1214 auto clips = WaveChannelUtilities::SortedClipArray(*pChannel);
1215 if (clips.empty())
1216 return false;
1217
1218 std::shared_ptr<WaveChannelInterval> clip{};
1219 if (forward)
1220 {
1221 clip = NextClipLooped(viewInfo, clips.begin(), clips.end(),
1222 [&](const auto &other) {
1223 return other->GetPlayStartTime() >= viewInfo.selectedRegion.t1();
1224 });
1225 }
1226 else
1227 {
1228 clip = NextClipLooped(viewInfo, clips.rbegin(), clips.rend(),
1229 [&](const auto &other) {
1230 return other->GetPlayStartTime() <= viewInfo.selectedRegion.t0();
1231 });
1232 }
1233
1234 viewInfo.selectedRegion.setTimes(clip->GetPlayStartTime(), clip->GetPlayEndTime());
1235 ProjectHistory::Get(*project).ModifyState(false);
1236
1237 // create and send message to screen reader
1238 auto it = std::find(clips.begin(), clips.end(), clip);
1239 auto index = std::distance(clips.begin(), it);
1240
1241 auto &waveTrack = pChannel->GetTrack();
1242 auto wideClipIt = waveTrack.Intervals().first;
1243 std::advance(wideClipIt, waveTrack.GetClipIndex(clip->GetClip()));
1245 .Retarget(waveTrack.SharedPointer<WaveTrack>(), *wideClipIt);
1246
1247 auto message = XP(
1248 /* i18n-hint:
1249 string is the name of a clip
1250 first number is the position of that clip in a sequence of clips,
1251 second number counts the clips */
1252 "%s, %d of %d clip",
1253 "%s, %d of %d clips",
1254 2
1255 )(
1256 clip->GetClip().GetName(),
1257 static_cast<int>(index + 1),
1258 static_cast<int>(clips.size())
1259 );
1260
1261 TrackFocus::Get(*project).MessageForScreenReader(message);
1262 return true;
1263}
1264
1265auto WaveChannelView::GetSubViews(const wxRect &rect) -> Refinement
1266{
1267 return GetSubViews(&rect);
1268}
1269
1270auto WaveChannelView::GetSubViews(const wxRect* rect) -> Refinement
1271{
1272 BuildSubViews();
1273
1274 // Collect the visible views in the right sequence
1275 struct Item {
1276 int index; float fraction; std::shared_ptr<ChannelView> pView;
1277 };
1278 std::vector< Item > items;
1279 size_t ii = 0;
1280 float total = 0;
1281 const auto &placements = DoGetPlacements();
1283 auto& placement = placements[ii];
1284 auto index = placement.index;
1285 auto fraction = placement.fraction;
1286 if (index >= 0 && fraction > 0.0)
1287 total += fraction,
1288 items.push_back({ index, fraction, subView.shared_from_this() });
1289 ++ii;
1290 });
1291 std::sort(items.begin(), items.end(), [](const Item& a, const Item& b) {
1292 return a.index < b.index;
1293 });
1294
1295 // Remove views we don't need
1296 auto begin = items.begin(), end = items.end(),
1297 newEnd = std::remove_if(begin, end,
1298 [](const Item& item) { return !item.pView; });
1299 items.erase(newEnd, end);
1300
1301 Refinement results;
1302
1303 if (rect != nullptr)
1304 {
1305 // Assign coordinates, redenominating to the total height,
1306 // storing integer values
1307 results.reserve(items.size());
1308 const auto top = rect->GetTop();
1309 const auto height = rect->GetHeight();
1310 float partial = 0;
1311 wxCoord lastCoord = 0;
1312 for (const auto& item : items) {
1313 wxCoord newCoord = top + (partial / total) * height;
1314 results.emplace_back(newCoord, item.pView);
1315 partial += item.fraction;
1316 }
1317
1318 // Cache for the use of sub-view dragging
1319 mLastHeight = height;
1320 }
1321 else
1322 {
1323 std::transform(items.begin(), items.end(), std::back_inserter(results), [](const auto& item) {
1324 return std::make_pair(0, item.pView);
1325 });
1326 }
1327
1328 return results;
1329}
1330
1331/*
1332 Note that the WaveChannelView isn't in the TrackPanel subdivision, but it is
1333 set sometimes as the focused cell, and therefore the following functions can
1334 be visited. To visit their overrides in the sub-views and affordances,
1335 which are never focused, we must forward to them. To do that properly, if
1336 any cell declines to handle the event by setting it as skipped, it must be
1337 set again to not-skipped before attempting the next call-through.
1338 */
1340 wxKeyEvent& event, ViewInfo& viewInfo, wxWindow* pParent,
1342{
1343 unsigned result{ RefreshCode::RefreshNone };
1344 const auto pChannel = FindWaveChannel();
1345 if (!pChannel)
1346 return result;
1347 auto &track = pChannel->GetTrack();
1348 for (auto pChannel : track.Channels()) {
1349 event.Skip(false);
1350 auto &waveChannelView = WaveChannelView::Get(*pChannel);
1351 // Give sub-views first chance to handle the event
1352 for (auto &subView : waveChannelView.GetSubViews()) {
1353 // Event defaults in skipped state which must be turned off explicitly
1354 wxASSERT(!event.GetSkipped());
1355 result |= subView.second->CaptureKey(event, viewInfo, pParent, project);
1356 if (!event.GetSkipped()) {
1357 // sub view wants it
1358 mKeyEventDelegate = subView.second;
1359 return result;
1360 }
1361 else
1362 event.Skip(false);
1363 }
1364
1365 if (auto affordance = waveChannelView.GetAffordanceControls()) {
1366 result |= affordance->CaptureKey(event, viewInfo, pParent, project);
1367 if (!event.GetSkipped()) {
1368 mKeyEventDelegate = affordance;
1369 return result;
1370 }
1371 }
1372
1373 event.Skip(false);
1374 }
1375 switch (event.GetKeyCode())
1376 {
1377 case WXK_TAB:
1378 break;
1379 default:
1381 event, viewInfo, pParent, project);
1382 break;
1383 };
1384 if (!event.GetSkipped()) {
1385 mKeyEventDelegate = shared_from_this();
1386 }
1387
1388 return result;
1389}
1390
1392 wxKeyEvent& event, ViewInfo& viewInfo, wxWindow* pParent,
1394{
1395 unsigned result{ RefreshCode::RefreshNone };
1396 if (auto delegate = mKeyEventDelegate.lock()) {
1397 if (auto pWaveChannelView =
1398 dynamic_cast<WaveChannelView*>(delegate.get()))
1399 {
1400 if (event.GetKeyCode() == WXK_TAB)
1401 {
1402 SelectNextClip(viewInfo, project, event.GetModifiers() != wxMOD_SHIFT);
1403 result |= RefreshCode::RefreshCell;
1404 }
1405 else
1406 result |= pWaveChannelView->CommonChannelView::KeyDown(
1407 event, viewInfo, pParent, project);
1408 }
1409 else
1410 result |= delegate->KeyDown(event, viewInfo, pParent, project);
1411 }
1412 else
1413 event.Skip();
1414
1415 return result;
1416}
1417
1419 wxKeyEvent& event, ViewInfo& viewInfo, wxWindow* pParent,
1421{
1422 unsigned result{ RefreshCode::RefreshNone };
1423 if (auto delegate = mKeyEventDelegate.lock()) {
1424 if (auto pWaveChannelView =
1425 dynamic_cast<WaveChannelView*>(delegate.get()))
1426 result |= pWaveChannelView->CommonChannelView::Char(
1427 event, viewInfo, pParent, project);
1428 else
1429 result |= delegate->Char(event, viewInfo, pParent, project);
1430 }
1431 else
1432 event.Skip();
1433
1434 return result;
1435}
1436
1438{
1439 unsigned result = RefreshCode::RefreshNone;
1440 if (auto delegate = mKeyEventDelegate.lock()) {
1441 if (auto pWaveChannelView =
1442 dynamic_cast<WaveChannelView*>(delegate.get()))
1443 result = pWaveChannelView->CommonChannelView::LoseFocus(project);
1444 else
1445 result = delegate->LoseFocus(project);
1446 mKeyEventDelegate.reset();
1447 }
1448 return result;
1449}
1450
1451namespace {
1454{
1455 const auto pWaveChannel = view.FindWaveChannel();
1456 const auto pTrack = &pWaveChannel->GetTrack();
1457 auto& channelView = ChannelView::Get(**pTrack->Channels().begin());
1458 if (const auto affordance =
1459 std::dynamic_pointer_cast<WaveTrackAffordanceControls>(
1460 channelView.GetAffordanceControls()).get()
1461 ; affordance && (affordance->*pmf)(project)
1462 )
1463 return true;
1464 return false;
1465}
1466}
1467
1469{
1470 return
1472}
1473
1475{
1476 return
1478}
1479
1481 const ZoomInfo& zoomInfo, const wxRect& viewRect)
1482{
1483 //Do not fold clips to line at sample zoom level, as
1484 //it may become impossible to 'unfold' it when clip is trimmed
1485 //to a single sample
1486 bool showSamples{ false };
1487 auto clipRect = ClipParameters::GetClipRect(clip, zoomInfo, viewRect, &showSamples);
1488 return showSamples || clipRect.width >= kClipDetailedViewMinimumWidth;
1489}
1490
1492 const ZoomInfo& zoomInfo, const wxRect& viewRect)
1493{
1494 bool showSamples{ false };
1495 auto clipRect = ClipParameters::GetClipRect(clip, zoomInfo, viewRect, &showSamples);
1496 if (showSamples || clipRect.width >= kClipDetailedViewMinimumWidth)
1497 return clipRect;
1498
1499 return clipRect.Inflate(2, 0);
1500}
1501
1503 const ZoomInfo& viewInfo, const wxRect& viewRect, const wxPoint& pos)
1504{
1505 return ClipHitTestArea(clip, viewInfo, viewRect).Contains(pos);
1506}
1507
1509{
1510 return
1512}
1513
1515{
1516 return
1518}
1519
1520std::vector<std::shared_ptr<WaveChannelSubView>>
1522{
1523 BuildSubViews();
1524
1525 std::vector<std::shared_ptr<WaveChannelSubView>> results;
1527 results.push_back(std::static_pointer_cast<WaveChannelSubView>(
1528 subView.shared_from_this()));
1529 });
1530 return results;
1531}
1532
1533std::shared_ptr<CommonTrackCell> WaveChannelView::GetAffordanceControls()
1534{
1535 auto pChannel = FindWaveChannel();
1536 if (pChannel) {
1537 auto &track = pChannel->GetTrack();
1538 if (pChannel == *track.Channels().begin()) {
1539 return DoGetAffordance(track);
1540 }
1541 }
1542 return {};
1543}
1544
1546{
1547 BuildSubViews();
1548
1549 // May come here. Invoke also on sub-views.
1550 ChannelView::DoSetMinimized(minimized);
1551 WaveChannelSubViews::ForEach([minimized](WaveChannelSubView &subView) {
1552 subView.DoSetMinimized(minimized);
1553 });
1554}
1555
1556std::shared_ptr<CommonTrackCell>
1558{
1559 if (mpAffordanceCellControl == nullptr)
1561 std::make_shared<WaveTrackAffordanceControls>(track.SharedPointer());
1563}
1564
1567 return [](WaveTrack &track, size_t iChannel) {
1568 auto channels = track.Channels();
1569 assert(iChannel < channels.size());
1570 auto &iter = channels.first;
1571 std::advance(iter, iChannel);
1572 return std::make_shared<WaveChannelView>(*iter);
1573 };
1574}
1575
1576std::shared_ptr<ChannelVRulerControls> WaveChannelView::DoGetVRulerControls()
1577{
1578 return {};
1579}
1580
1582 const std::shared_ptr<Track> &parent, size_t iChannel)
1583{
1584 // BuildSubViews(); // not really needed
1587 {
1588 subView.Reparent(parent, iChannel);
1589 });
1591 mpAffordanceCellControl->Reparent(parent);
1592}
1593
1595{
1596 const auto pChannel = FindWaveChannel();
1597 if (!pChannel)
1598 return {};
1599 auto &track = pChannel->GetTrack();
1600 auto &topmostView = Get(**track.Channels().begin());
1601 if (auto affordance = std::dynamic_pointer_cast<WaveTrackAffordanceControls>(
1602 topmostView.GetAffordanceControls()))
1603 {
1604 return *affordance->GetSelectedInterval();
1605 }
1606 return {};
1607}
1608
1610{
1611 if (WaveChannelSubViews::size() == 0) {
1612 // On-demand steps that can't happen in the constructor
1613 auto pThis = const_cast<WaveChannelView*>(this);
1614 pThis->BuildAll();
1615 bool minimized = GetMinimized();
1616 pThis->WaveChannelSubViews::ForEach([&](WaveChannelSubView &subView){
1617 subView.DoSetMinimized(minimized);
1618 });
1619
1620 auto &placements = pThis->DoGetPlacements();
1621 if (placements.empty()) {
1622 placements.resize(WaveChannelSubViews::size());
1623
1624 auto display = TracksPrefs::ViewModeChoice();
1625 bool multi = (display == WaveChannelViewConstants::MultiView);
1626 if (multi) {
1627 pThis->SetMultiView( true );
1629 }
1630
1631 pThis->DoSetDisplay( display, !multi );
1632 }
1633 }
1634}
1635
1637 TrackPanelDrawingContext &context,
1638 const wxRect &rect, unsigned iPass)
1639{
1640 // Should not come here, drawing is now delegated to sub-views
1641 wxASSERT( false );
1642
1643 CommonChannelView::Draw(context, rect, iPass);
1644}
1645
1649 return [](auto &) { return SyncLockPolicy::Grouped; };
1650}
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:189
std::shared_ptr< Subclass > AssignUIHandlePtr(std::weak_ptr< Subclass > &holder, const std::shared_ptr< Subclass > &pNew)
Definition: UIHandle.h:164
@ kTrackInfoBtnSize
Definition: ViewInfo.h:96
DEFINE_ATTACHED_VIRTUAL_OVERRIDE(DoGetWaveChannelView)
std::vector< std::shared_ptr< WaveChannelSubView > > WaveChannelSubViewPtrs
constexpr int kClipDetailedViewMinimumWidth
std::vector< WaveChannelSubViewPlacement > WaveChannelSubViewPlacements
WaveTrackLocations FindWaveTrackLocations(const WaveTrack &track)
static void Line(wxDC &dc, wxCoord x1, wxCoord y1, wxCoord x2, wxCoord y2)
Definition: AColor.cpp:185
static wxPen uglyPen
Definition: AColor.h:139
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< ChannelType > GetChannel(size_t iChannel)
Retrieve a channel, cast to the given type.
Definition: Channel.h:320
static ChannelView * Find(Channel *pChannel)
void Reparent(const std::shared_ptr< Track > &parent, size_t iChannel) override
Object may be shared among tracks but hold a special back-pointer to one of them; reassign it.
Definition: ChannelView.cpp:31
static ChannelView & Get(Channel &channel)
bool GetMinimized() const
Definition: ChannelView.h:69
virtual void DoSetMinimized(bool isMinimized)
void CopyTo(Track &track, size_t iChannel) const override
Copy state, for undo/redo purposes.
Definition: ChannelView.cpp:67
std::vector< std::pair< wxCoord, std::shared_ptr< ChannelView > > > Refinement
Definition: ChannelView.h:121
Client code makes static instance from a factory of attachments; passes it to Get or Find as a retrie...
Definition: ClientData.h:275
size_t size() const
How many attachment pointers are in the Site.
Definition: ClientData.h:260
ClientData * FindIf(const Function &function)
Return pointer to first attachment in this that is not null and satisfies a predicate,...
Definition: ClientData.h:497
void ForEach(const Function &function)
Invoke function on each ClientData object that has been created in this.
Definition: ClientData.h:389
void BuildAll()
For each RegisteredFactory, if the corresponding attachment is absent in this, build and store it.
Definition: ClientData.h:546
auto FindChannel() -> std::shared_ptr< Subtype >
May return null.
Implements some hit-testing shared by many ChannelView subtypes.
std::weak_ptr< TimeShiftHandle > mTimeShiftHandle
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)
double t1() const
Definition: ViewInfo.h:36
bool setTimes(double t0, double t1)
Definition: ViewInfo.cpp:51
double t0() const
Definition: ViewInfo.h:35
static PitchAndSpeedDialog & Get(AudacityProject &project)
PitchAndSpeedDialog & Retarget(const std::shared_ptr< WaveTrack > &track, const WaveTrack::IntervalHolder &wideClip)
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)
HitTestPreview Preview(const TrackPanelMouseState &state, AudacityProject *) override
std::shared_ptr< const Track > FindTrack() const 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< Channel > &pChannel, const wxRect &rect)
void Draw(TrackPanelDrawingContext &context, const wxRect &rect, unsigned iPass) override
std::weak_ptr< Channel > mpChannel
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
Result Cancel(AudacityProject *) override
std::shared_ptr< const Track > FindTrack() const 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:110
std::shared_ptr< Subclass > SharedPointer()
Definition: Track.h:146
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:37
static std::shared_ptr< const Track > TrackFromChannel(const std::shared_ptr< const Channel > &pChannel)
A frequent convenience in the definition of UIHandles.
Definition: UIHandle.cpp:63
unsigned Result
Definition: UIHandle.h:40
NotifyingSelectedRegion selectedRegion
Definition: ViewInfo.h:215
static ViewInfo & Get(AudacityProject &project)
Definition: ViewInfo.cpp:235
WaveTrack & GetTrack()
Definition: WaveTrack.h:840
std::shared_ptr< WaveChannel > FindWaveChannel()
std::weak_ptr< SubViewAdjustHandle > mAdjustHandle
virtual const Type & SubViewType() const =0
std::weak_ptr< CutlineHandle > mCutlineHandle
static void DrawBoldBoundaries(TrackPanelDrawingContext &context, const WaveChannel &channel, const wxRect &rect)
virtual void CopyToSubView(WaveChannelSubView *destSubView) const
std::pair< bool, std::vector< UIHandlePtr > > DoDetailedHitTest(const TrackPanelMouseState &state, const AudacityProject *pProject, int currentTool, bool bMultiTool, const std::shared_ptr< WaveChannel > &wt)
std::weak_ptr< TrackPanelResizeHandle > mResizeHandle
std::weak_ptr< WaveChannelView > mwWaveChannelView
std::weak_ptr< WaveClipAdjustBorderHandle > mClipBorderAdjustHandle
WaveChannelSubView(WaveChannelView &waveChannelView)
std::weak_ptr< SubViewCloseHandle > mCloseHandle
std::weak_ptr< WaveChannelView > GetWaveChannelView() const
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
std::shared_ptr< WaveChannel > FindWaveChannel()
const WaveChannelSubViewPlacements & SavePlacements() const
bool GetMultiView() const
void BuildSubViews() const
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)
std::shared_ptr< CommonTrackCell > DoGetAffordance(Track &track)
std::shared_ptr< CommonTrackCell > mpAffordanceCellControl
bool ToggleSubView(Display id)
std::weak_ptr< TrackPanelCell > mKeyEventDelegate
static bool ClipDetailsVisible(const ClipTimes &clip, const ZoomInfo &zoomInfo, const wxRect &viewRect)
static bool HitTest(const ClipTimes &clip, const ZoomInfo &zoomInfo, const wxRect &rect, const wxPoint &pos)
unsigned KeyDown(wxKeyEvent &event, ViewInfo &viewInfo, wxWindow *pParent, AudacityProject *project) override
WaveTrack::IntervalHolder GetSelectedClip()
std::vector< UIHandlePtr > DetailedHitTest(const TrackPanelMouseState &state, const AudacityProject *pProject, int currentTool, bool bMultiTool) override
~WaveChannelView() override
static WaveChannelView & Get(WaveChannel &channel)
static WaveChannelView * FindFirst(WaveTrack *pWt)
If pWt is not null, return a pointer to the view of the first channel.
bool CopySelectedText(AudacityProject &project)
WaveChannelSubViewPlacements & DoGetPlacements()
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)
static WaveChannelView & GetFirst(WaveTrack &wt)
Get the view of the first channel.
void DoSetDisplay(Display display, bool exclusive=true)
wxCoord GetLastHeight() const
void Draw(TrackPanelDrawingContext &context, const wxRect &rect, unsigned iPass) override
bool SelectAllText(AudacityProject &project)
void CopyTo(Track &track, size_t iChannel) const override
Copy state, for undo/redo purposes.
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< WaveChannel > &wt, CommonChannelView &view)
static wxRect ClipHitTestArea(const ClipTimes &clip, const ZoomInfo &zoomInfo, const wxRect &viewRect)
std::vector< WaveChannelSubView::Type > GetDisplays() const
bool SelectNextClip(ViewInfo &viewInfo, AudacityProject *project, bool forward)
void Reparent(const std::shared_ptr< Track > &parent, size_t iChannel) override
Object may be shared among tracks but hold a special back-pointer to one of them; reassign it.
static UIHandlePtr HitTest(std::weak_ptr< WaveClipAdjustBorderHandle > &holder, WaveChannelView &view, const AudacityProject *pProject, const TrackPanelMouseState &state)
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:203
auto Channels()
Definition: WaveTrack.h:263
std::shared_ptr< Interval > IntervalHolder
Definition: WaveTrack.h:209
Services * Get()
Fetch the global instance, or nullptr if none is yet installed.
Definition: BasicUI.cpp:201
AUDACITY_DLL_API void DrawCloseButton(TrackPanelDrawingContext &context, const wxRect &bev, const Channel *pChannel, UIHandle *target)
Namespace containing an enum 'what to do on a refresh?'.
Definition: RefreshCode.h:16
WAVE_TRACK_API ClipPointer GetClipAtTime(WaveChannel &channel, double time)
WAVE_TRACK_API ClipPointers SortedClipArray(WaveChannel &channel)
Get clips sorted by play start time.
@ MultiView
"Multi" is special, not really a view type on par with the others.
std::vector< CommonTrackPanelCell::MenuItem > GetWaveClipMenuItems()
WAVE_TRACK_API WaveTrack::IntervalConstHolders GetClipsIntersecting(const WaveTrack &track, double t0, double t1)
void swap(std::unique_ptr< Alg_seq > &a, std::unique_ptr< Alg_seq > &b)
Definition: NoteTrack.cpp:628
std::pair< const char *, const char * > Pair
bool Comp(const CollectedItems::NewItem &a, const CollectedItems::NewItem &b)
Definition: Registry.cpp:491
bool(WaveTrackAffordanceControls::*)(AudacityProject &) PMF
std::shared_ptr< WaveChannelInterval > NextClipLooped(ViewInfo &viewInfo, Iter begin, Iter end, Comp comp)
Iter SelectedClip(const ViewInfo &viewInfo, Iter begin, Iter end)
static const ChannelGroup::Attachments::RegisteredFactory key
bool AnyAffordance(AudacityProject &project, WaveChannelView &view, PMF pmf)
const char * end(const char *str) noexcept
Definition: StringUtils.h:106
const char * begin(const char *str) noexcept
Definition: StringUtils.h:101
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:50
static wxRect GetClipRect(const ClipTimes &clip, const ZoomInfo &zoomInfo, const wxRect &viewRect, bool *outShowSamples=nullptr)
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.