Audacity 3.2.0
TrackPanel.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 TrackPanel.cpp
6
7 Dominic Mazzoni
8 and lots of other contributors
9
10 Implements TrackPanel.
11
12********************************************************************//***************************************************************//*****************************************************************//*****************************************************************/
45
46
47#include "TrackPanel.h"
48#include "TrackPanelConstants.h"
49
50#include <wx/setup.h> // for wxUSE_* macros
51
52#include "AdornedRulerPanel.h"
54#include "KeyboardCapture.h"
55#include "Project.h"
56#include "ProjectAudioIO.h"
57#include "ProjectAudioManager.h"
58#include "ProjectHistory.h"
59#include "ProjectWindows.h"
60#include "ProjectSettings.h"
61#include "ProjectStatus.h"
62#include "ProjectWindow.h"
63#include "SyncLock.h"
64#include "Theme.h"
65#include "TrackArt.h"
68
69#include "UndoManager.h"
70
71#include "AColor.h"
72#include "AllThemeResources.h"
73#include "AudioIO.h"
74#include "float_cast.h"
75
76#include "Prefs.h"
77#include "RefreshCode.h"
78#include "TrackArtist.h"
79#include "TrackPanelAx.h"
81#include "WaveTrack.h"
82
83#include "FrameStatistics.h"
84
86#include "tracks/ui/TrackView.h"
88
89//This loads the appropriate set of cursors, depending on platform.
90#include "../images/Cursors.h"
91
92#include <algorithm>
93
94#include <wx/dc.h>
95#include <wx/dcclient.h>
96#include <wx/graphics.h>
97
99
100static_assert( kVerticalPadding == kTopMargin + kBottomMargin );
101static_assert( kTrackInfoBtnSize == kAffordancesAreaHeight, "Drag bar is misaligned with the menu button");
102
166// Is the distance between A and B less than D?
167template < class A, class B, class DIST > bool within(A a, B b, DIST d)
168{
169 return (a > b - d) && (a < b + d);
170}
171
172BEGIN_EVENT_TABLE(TrackPanel, CellularPanel)
173 EVT_MOUSE_EVENTS(TrackPanel::OnMouseEvent)
174 EVT_KEY_DOWN(TrackPanel::OnKeyDown)
175
176 EVT_PAINT(TrackPanel::OnPaint)
177
178 EVT_TIMER(wxID_ANY, TrackPanel::OnTimer)
179
180 EVT_SIZE(TrackPanel::OnSize)
181
183
184
186std::unique_ptr<wxCursor> MakeCursor( int WXUNUSED(CursorId), const char * const pXpm[36], int HotX, int HotY )
187{
188#define CURSORS_SIZE32
189#ifdef CURSORS_SIZE32
190 const int HotAdjust =0;
191#else
192 const int HotAdjust =8;
193#endif
194
195 wxImage Image = wxImage(wxBitmap(pXpm).ConvertToImage());
196 Image.SetMaskColour(255,0,0);
197 Image.SetMask();// Enable mask.
198
199 Image.SetOption( wxIMAGE_OPTION_CUR_HOTSPOT_X, HotX-HotAdjust );
200 Image.SetOption( wxIMAGE_OPTION_CUR_HOTSPOT_Y, HotY-HotAdjust );
201 return std::make_unique<wxCursor>( Image );
202}
203
204
205namespace{
206
207AttachedWindows::RegisteredFactory sKey{
208 []( AudacityProject &project ) -> wxWeakRef< wxWindow > {
209 auto &ruler = AdornedRulerPanel::Get( project );
210 auto &viewInfo = ViewInfo::Get( project );
211 auto &window = ProjectWindow::Get( project );
212 auto mainPage = window.GetTrackListWindow();
213 wxASSERT( mainPage ); // to justify safenew
214
215 auto &tracks = TrackList::Get( project );
216 auto result = safenew TrackPanel(mainPage,
217 window.NextWindowID(),
218 wxDefaultPosition,
219 wxDefaultSize,
220 tracks.shared_from_this(),
221 &viewInfo,
222 &project,
223 &ruler);
224 SetProjectPanel( project, *result );
225 return result;
226 }
227};
228
229}
230
232{
233 return GetAttachedWindows(project).Get< TrackPanel >( sKey );
234}
235
237{
238 return Get( const_cast< AudacityProject & >( project ) );
239}
240
242{
243 auto *pPanel = GetAttachedWindows(project).Find<TrackPanel>( sKey );
244 if (pPanel) {
245 pPanel->wxWindow::Destroy();
246 GetAttachedWindows(project).Assign(sKey, nullptr);
247 }
248}
249
250// Don't warn us about using 'this' in the base member initializer list.
251#ifndef __WXGTK__ //Get rid if this pragma for gtk
252#pragma warning( disable: 4355 )
253#endif
254TrackPanel::TrackPanel(wxWindow * parent, wxWindowID id,
255 const wxPoint & pos,
256 const wxSize & size,
257 const std::shared_ptr<TrackList> &tracks,
258 ViewInfo * viewInfo,
259 AudacityProject * project,
261 : CellularPanel(parent, id, pos, size, viewInfo,
262 wxWANTS_CHARS | wxNO_BORDER),
263 mListener( &ProjectWindow::Get( *project ) ),
264 mTracks(tracks),
265 mRuler(ruler),
266 mTrackArtist(nullptr),
267 mRefreshBacking(false)
268#ifndef __WXGTK__ //Get rid if this pragma for gtk
269#pragma warning( default: 4355 )
270#endif
271{
272 SetLayoutDirection(wxLayout_LeftToRight);
273 SetLabel(XO("Track Panel"));
274 SetName(XO("Track Panel"));
275 SetBackgroundStyle(wxBG_STYLE_PAINT);
276
277 {
278 auto pAx = std::make_unique <TrackPanelAx>( *project );
279 pAx->SetWindow( this );
280 wxWeakRef< TrackPanel > weakThis{ this };
281 pAx->SetFinder(
282 [weakThis]( const Track &track ) -> wxRect {
283 if (weakThis)
284 return weakThis->FindTrackRect( &track );
285 return {};
286 }
287 );
288 TrackFocus::Get( *GetProject() ).SetAccessible(
289 *this, std::move( pAx ) );
290 }
291
292 mTrackArtist = std::make_unique<TrackArtist>( this );
293
294 mTimeCount = 0;
295 mTimer.parent = this;
296 // Timer is started after the window is visible
297 ProjectWindow::Get( *GetProject() ).Bind(wxEVT_IDLE,
298 &TrackPanel::OnIdle, this);
299
300 // Register for tracklist updates
302 mTracks->Subscribe([this](const TrackListEvent &event){
303 switch (event.mType) {
304 case TrackListEvent::RESIZING:
305 case TrackListEvent::ADDITION:
306 OnTrackListResizing(event); break;
307 case TrackListEvent::DELETION:
308 OnTrackListDeletion(); break;
309 case TrackListEvent::TRACK_REQUEST_VISIBLE:
310 OnEnsureVisible(event); break;
311 default:
312 break;
313 }
314 });
315
316 auto theProject = GetProject();
319
321 .Subscribe(*this, &TrackPanel::OnTrackFocusChange);
322
325
328
330 .Subscribe([this](const RealtimeEffectManagerMessage& msg)
331 {
332 if(msg.track)
333 //update "effects" button
334 RefreshTrack(msg.track.get());
335 });
336
337 UpdatePrefs();
338}
339
340
342{
343 mTimer.Stop();
344
345 // This can happen if a label is being edited and the user presses
346 // ALT+F4 or Command+Q
347 if (HasCapture())
348 ReleaseMouse();
349}
350
352{
353 // All vertical rulers must be recalculated since the minimum and maximum
354 // frequencies may have been changed.
356
357 Refresh();
358}
359
363{
364 auto window = GetParent();
365
366 while(window != nullptr)
367 {
368 if(const auto projectWindow = dynamic_cast<ProjectWindow*>(window))
369 return projectWindow->FindProject().get();
370
371 window = window->GetParent();
372 }
373 return nullptr;
374}
375
376void TrackPanel::OnSize( wxSizeEvent &evt )
377{
378 evt.Skip();
379 const auto &size = evt.GetSize();
380 mViewInfo->SetWidth( size.GetWidth() );
381 mViewInfo->SetHeight( size.GetHeight() );
382}
383
384void TrackPanel::OnIdle(wxIdleEvent& event)
385{
386 event.Skip();
387 // The window must be ready when the timer fires (#1401)
388 if (IsShownOnScreen())
389 {
390 mTimer.Start(std::chrono::milliseconds{kTimerInterval}.count(), FALSE);
391
392 // Timer is started, we don't need the event anymore
393 GetProjectFrame( *GetProject() ).Unbind(wxEVT_IDLE,
394 &TrackPanel::OnIdle, this);
395 }
396 else
397 {
398 // Get another idle event, wx only guarantees we get one
399 // event after "some other normal events occur"
400 event.RequestMore();
401 }
402}
403
405void TrackPanel::OnTimer(wxTimerEvent& )
406{
407 mTimeCount++;
408
409 AudacityProject *const p = GetProject();
410 auto &window = ProjectWindow::Get( *p );
411
412 auto &projectAudioIO = ProjectAudioIO::Get( *p );
413 auto gAudioIO = AudioIO::Get();
414
415 // Check whether we were playing or recording, but the stream has stopped.
416 if (projectAudioIO.GetAudioIOToken()>0 && !IsAudioActive())
417 {
418 //the stream may have been started up after this one finished (by some other project)
419 //in that case reset the buttons don't stop the stream
420 auto &projectAudioManager = ProjectAudioManager::Get( *p );
421 projectAudioManager.Stop(!gAudioIO->IsStreamActive());
422 }
423
424 // Next, check to see if we were playing or recording
425 // audio, but now Audio I/O is completely finished.
426 if (projectAudioIO.GetAudioIOToken()>0 &&
427 !gAudioIO->IsAudioTokenActive(projectAudioIO.GetAudioIOToken()))
428 {
429 projectAudioIO.SetAudioIOToken(0);
430 window.RedrawProject();
431 }
434 }
435
436 // Notify listeners for timer ticks
437 window.GetPlaybackScroller().OnTimer();
438
439 DrawOverlays(false);
440 mRuler->DrawOverlays(false);
441
442 if(IsAudioActive() && gAudioIO->GetNumCaptureChannels()) {
443
444 // Periodically update the display while recording
445
446 if ((mTimeCount % 5) == 0) {
447 // Must tell OnPaint() to recreate the backing bitmap
448 // since we've not done a full refresh.
449 mRefreshBacking = true;
450 Refresh( false );
451 }
452 }
453 if(mTimeCount > 1000)
454 mTimeCount = 0;
455}
456
458{
459 Refresh(false);
460}
461
463{
464 if (message.type == UndoRedoMessage::Reset) {
465 TrackFocus::Get( *GetProject() ).Set( nullptr );
466 Refresh( false );
467 }
468}
469
472void TrackPanel::OnPaint(wxPaintEvent & /* event */)
473{
475
476 auto sw =
478
479 {
480 wxPaintDC dc(this);
481
482 // Retrieve the damage rectangle
483 wxRect box = GetUpdateRegion().GetBox();
484
485 // Recreate the backing bitmap if we have a full refresh
486 // (See TrackPanel::Refresh())
487 if (mRefreshBacking || (box == GetRect()))
488 {
489 // Reset (should a mutex be used???)
490 mRefreshBacking = false;
491
492 // Redraw the backing bitmap
494
495 // Copy it to the display
496 DisplayBitmap(dc);
497 }
498 else
499 {
500 // Copy full, possibly clipped, damage rectangle
501 RepairBitmap(dc, box.x, box.y, box.width, box.height);
502 }
503
504 // Done with the clipped DC
505
506 // Drawing now goes directly to the client area.
507 // DrawOverlays() may need to draw outside the clipped region.
508 // (Used to make a NEW, separate wxClientDC, but that risks flashing
509 // problems on Mac.)
510 dc.DestroyClippingRegion();
511 DrawOverlays(true, &dc);
512 }
513}
514
516{
518}
519
520namespace {
521 std::shared_ptr<Track> FindTrack(TrackPanelCell *pCell )
522 {
523 if (pCell)
524 return static_cast<CommonTrackPanelCell*>( pCell )->FindTrack();
525 return {};
526 }
527}
528
530 (TrackPanelCell *pClickedCell, TrackPanelCell *pLatestCell,
531 UIHandle::Result refreshResult)
532{
533 const auto panel = this;
534 auto pLatestTrack = FindTrack( pLatestCell ).get();
535
536 // This precaution checks that the track is not only nonnull, but also
537 // really owned by the track list
538 auto pClickedTrack = GetTracks()->Lock(
539 std::weak_ptr<Track>{ FindTrack( pClickedCell ) }
540 ).get();
541
542 // TODO: make a finer distinction between refreshing the track control area,
543 // and the waveform area. As it is, redraw both whenever you must redraw either.
544
545 // Copy data from the underlying tracks to the pending tracks that are
546 // really displayed
547 TrackList::Get( *panel->GetProject() ).UpdatePendingTracks();
548
549 using namespace RefreshCode;
550
551 if (refreshResult & DestroyedCell) {
552 panel->UpdateViewIfNoTracks();
553 // Beware stale pointer!
554 if (pLatestTrack == pClickedTrack)
555 pLatestTrack = NULL;
556 pClickedTrack = NULL;
557 }
558
559 if (pClickedTrack && (refreshResult & RefreshCode::UpdateVRuler))
560 panel->UpdateVRuler(pClickedTrack);
561
562 if (refreshResult & RefreshCode::DrawOverlays) {
563 panel->DrawOverlays(false);
564 mRuler->DrawOverlays(false);
565 }
566
567 // Refresh all if told to do so, or if told to refresh a track that
568 // is not known.
569 const bool refreshAll =
570 ( (refreshResult & RefreshAll)
571 || ((refreshResult & RefreshCell) && !pClickedTrack)
572 || ((refreshResult & RefreshLatestCell) && !pLatestTrack));
573
574 if (refreshAll)
575 panel->Refresh(false);
576 else {
577 if (refreshResult & RefreshCell)
578 panel->RefreshTrack(pClickedTrack);
579 if (refreshResult & RefreshLatestCell)
580 panel->RefreshTrack(pLatestTrack);
581 }
582
583 if (refreshResult & FixScrollbars)
584 panel->MakeParentRedrawScrollbars();
585
586 if (refreshResult & Resize)
587 panel->GetListener()->TP_HandleResize();
588
589 if ((refreshResult & RefreshCode::EnsureVisible) && pClickedTrack) {
590 TrackFocus::Get(*GetProject()).Set(pClickedTrack);
591 pClickedTrack->EnsureVisible();
592 }
593}
594
596{
598}
599
601{
603}
604
606{
608 return ProjectAudioIO::Get( *p ).IsAudioActive();
609}
610
612{
613 auto status = st;
614 if (HasEscape())
615 /* i18n-hint Esc is a key on the keyboard */
616 status.Join( XO("(Esc to cancel)"), " " );
617 ProjectStatus::Get( *GetProject() ).Set( status );
618}
619
621{
622 // Full refresh since the label area may need to indicate
623 // newly selected tracks.
624 Refresh(false);
625
626 // Make sure the ruler follows suit.
628}
629
630// Counts selected tracks, counting stereo tracks as one track.
632{
633 return GetTracks()->SelectedLeaders().size();
634}
635
637{
638 if (mTracks->empty())
639 {
640 // BG: There are no more tracks on screen
641 //BG: Set zoom to normal
643
644 //STM: Set selection to 0,0
645 //PRL: and default the rest of the selection information
647
648 // PRL: Following causes the time ruler to align 0 with left edge.
649 // Bug 972
650 mViewInfo->h = 0;
651
653 //STM: Clear message if all tracks are removed
655 }
656}
657
658// The tracks positions within the list have changed, so update the vertical
659// ruler size for the track that triggered the event.
661{
662 auto t = e.mpTrack.lock();
663 // A deleted track can trigger the event. In which case do nothing here.
664 // A deleted track can have a valid pointer but no owner, bug 2060
665 if( t && t->HasOwner() )
666 UpdateVRuler(t.get());
667
668 // fix for bug 2477
670}
671
672// Tracks have been removed from the list.
674{
675 // copy shared_ptr for safety, as in HandleClick
676 auto handle = Target();
677 if (handle) {
678 handle->OnProjectChange(GetProject());
679 }
680
681 // If the focused track disappeared but there are still other tracks,
682 // this reassigns focus.
683 TrackFocus( *GetProject() ).Get();
684
686}
687
688void TrackPanel::OnKeyDown(wxKeyEvent & event)
689{
690 switch (event.GetKeyCode())
691 {
692 // Allow PageUp and PageDown keys to
693 //scroll the Track Panel left and right
694 case WXK_PAGEUP:
696 return;
697
698 case WXK_PAGEDOWN:
700 return;
701
702 default:
703 // fall through to base class handler
704 event.Skip();
705 }
706}
707
708void TrackPanel::OnMouseEvent(wxMouseEvent & event)
709{
710 if (event.LeftDown()) {
711 // wxTimers seem to be a little unreliable, so this
712 // "primes" it to make sure it keeps going for a while...
713
714 // When this timer fires, we call TrackPanel::OnTimer and
715 // possibly update the screen for offscreen scrolling.
716 mTimer.Stop();
717 mTimer.Start(std::chrono::milliseconds{kTimerInterval}.count(), FALSE);
718 }
719
720
721 if (event.ButtonUp()) {
722 //EnsureVisible should be called after processing the up-click.
723 this->CallAfter( [this, event]{
724 const auto foundCell = FindCell(event.m_x, event.m_y);
725 const auto t = FindTrack( foundCell.pCell.get() );
726 if ( t ) {
727 TrackFocus::Get(*GetProject()).Set(t.get());
728 t->EnsureVisible();
729 }
730 } );
731 }
732
733 // Must also fall through to base class handler
734 event.Skip();
735}
736
738{
741}
742
743void TrackPanel::RefreshTrack(Track *trk, bool refreshbacking)
744{
745 if (!trk)
746 return;
747
748 // Always move to the first channel of the group, and use only
749 // the sum of channel heights, not the height of any channel alone!
750 trk = *GetTracks()->FindLeader(trk);
751 auto &view = TrackView::Get( *trk );
752 auto height =
754
755 // Set rectangle top according to the scrolling position, `vpos`
756 // Subtract the inset (above) and shadow (below) from the height of the
757 // rectangle, but not the border
758 // This matters because some separators do paint over the border
759 const auto top =
760 -mViewInfo->vpos + view.GetCumulativeHeightBefore() + kTopInset;
761 height -= (kTopInset + kShadowThickness);
762
763 // Width also subtracts insets (left and right) plus shadow (right)
764 const auto left = kLeftInset;
765 const auto width = GetRect().GetWidth()
767
768 wxRect rect(left, top, width, height);
769
770 if( refreshbacking )
771 mRefreshBacking = true;
772
773 Refresh( false, &rect );
774}
775
776
781void TrackPanel::Refresh(bool eraseBackground /* = TRUE */,
782 const wxRect *rect /* = NULL */)
783{
784 // Tell OnPaint() to refresh the backing bitmap.
785 //
786 // Originally I had the check within the OnPaint() routine and it
787 // was working fine. That was until I found that, even though a full
788 // refresh was requested, Windows only set the onscreen portion of a
789 // window as damaged.
790 //
791 // So, if any part of the trackpanel was off the screen, full refreshes
792 // didn't work and the display got corrupted.
793 if( !rect || ( *rect == GetRect() ) )
794 {
795 mRefreshBacking = true;
796 }
797 wxWindow::Refresh(eraseBackground, rect);
798
800}
801
803{
804 if (evt.type == AudioIOEvent::MONITOR)
805 return;
806 // Some hit tests want to change their cursor to and from the ban symbol
808}
809
811
816{
817 wxRegion region = GetUpdateRegion();
818
819 const wxRect clip = GetRect();
820
822 mTrackArtist->pSelectedRegion = &sr;
823 mTrackArtist->pZoomInfo = mViewInfo;
825 *dc, Target(), mLastMouseState, mTrackArtist.get()
826 };
827
828 // Don't draw a bottom margin here.
829
830 const auto &settings = ProjectSettings::Get( *GetProject() );
831 bool bMultiToolDown =
832 (ToolCodes::multiTool == settings.GetTool());
833 bool envelopeFlag =
834 bMultiToolDown || (ToolCodes::envelopeTool == settings.GetTool());
835 bool bigPointsFlag =
836 bMultiToolDown || (ToolCodes::drawTool == settings.GetTool());
837 bool sliderFlag = bMultiToolDown;
838 bool brushFlag = false;
839#ifdef EXPERIMENTAL_BRUSH_TOOL
840 brushFlag = (ToolCodes::brushTool == settings.GetTool());
841#endif
842
843 const bool hasSolo = GetTracks()->Any< PlayableTrack >()
844 .any_of( []( const PlayableTrack *pt ) {
845 pt = static_cast< const PlayableTrack * >(
846 pt->SubstitutePendingChangedTrack().get() );
847 return (pt && pt->GetSolo());
848 } );
849
850 mTrackArtist->drawEnvelope = envelopeFlag;
851 mTrackArtist->bigPoints = bigPointsFlag;
852 mTrackArtist->drawSliders = sliderFlag;
853 mTrackArtist->onBrushTool = brushFlag;
854 mTrackArtist->hasSolo = hasSolo;
855
857}
858
860(const std::shared_ptr< CommonTrackPanelCell > &pCell)
861{
862 mpBackground = pCell;
863}
864
865std::shared_ptr< CommonTrackPanelCell > TrackPanel::GetBackgroundCell()
866{
867 return mpBackground;
868}
869
870namespace {
871std::vector<int> FindAdjustedChannelHeights( Track &t )
872{
873 auto channels = TrackList::Channels(&t);
874 wxASSERT(!channels.empty());
875
876 // Collect heights, and count affordances
877 int nAffordances = 0;
878 int totalHeight = 0;
879 std::vector<int> oldHeights;
880 for (auto channel : channels) {
881 auto &view = TrackView::Get( *channel );
882 const auto height = view.GetHeight();
883 totalHeight += height;
884 oldHeights.push_back( height );
885 if (view.GetAffordanceControls())
886 ++nAffordances;
887 }
888
889 // Allocate results
890 auto nChannels = static_cast<int>(oldHeights.size());
891 std::vector<int> results;
892 results.reserve(nChannels);
893
894 // Now reallocate the channel heights for the presence of affordances
895 // and separators
896 auto availableHeight = totalHeight
897 - nAffordances * kAffordancesAreaHeight
898 - (nChannels - 1) * kChannelSeparatorThickness
900 int cumulativeOldHeight = 0;
901 int cumulativeNewHeight = 0;
902 for (const auto &oldHeight : oldHeights) {
903 // Preserve the porportions among the stored heights
904 cumulativeOldHeight += oldHeight;
905 const auto newHeight =
906 cumulativeOldHeight * availableHeight / totalHeight
907 - cumulativeNewHeight;
908 cumulativeNewHeight += newHeight;
909 results.push_back(newHeight);
910 }
911
912 return results;
913}
914}
915
917{
918 for (auto t : GetTracks()->Any< WaveTrack >())
920
922}
923
925{
926 if (t)
928
930}
931
933{
934 wxASSERT(t);
935 if (!t)
936 return;
937
938 auto heights = FindAdjustedChannelHeights(*t);
939
940 wxRect rect(mViewInfo->GetVRulerOffset(),
941 0,
943 0);
944
945 auto pHeight = heights.begin();
946 for (auto channel : TrackList::Channels(t)) {
947 auto &view = TrackView::Get( *channel );
948 const auto height = *pHeight++;
949 rect.SetHeight( height );
950 const auto subViews = view.GetSubViews( rect );
951 if (subViews.empty())
952 continue;
953
954 auto iter = subViews.begin(), end = subViews.end(), next = iter;
955 auto yy = iter->first;
956 wxSize vRulerSize{ 0, 0 };
957 for ( ; iter != end; iter = next ) {
958 ++next;
959 auto nextY = ( next == end )
960 ? height
961 : next->first;
962 rect.SetHeight( nextY - yy );
963 // This causes ruler size in the track to be reassigned:
964 TrackVRulerControls::Get( *iter->second ).UpdateRuler( rect );
965 // But we want to know the maximum width and height over all sub-views:
966 vRulerSize.IncTo( {t->vrulerSize.first, t->vrulerSize.second} );
967 yy = nextY;
968 }
969 t->vrulerSize = {vRulerSize.x, vRulerSize.y};
970 }
971}
972
974{
975 auto trackRange = GetTracks()->Any();
976 if (trackRange) {
977 wxSize s { 0, 0 };
978 for (auto t : trackRange)
979 s.IncTo({t->vrulerSize.first, t->vrulerSize.second});
980
981 if (mViewInfo->GetVRulerWidth() != s.GetWidth()) {
982 mViewInfo->SetVRulerWidth( s.GetWidth() );
984 mViewInfo->GetLeftOffset()); // bevel on AdornedRuler
985 mRuler->Refresh();
986 }
987 }
988 Refresh(false);
989}
990
992{
993 CellularPanel::DoContextMenu( t ? &TrackView::Get( *t ) : nullptr );
994}
995
996// Tracks have been removed from the list.
998{
999 bool modifyState = e.mExtra;
1000
1001 auto pTrack = e.mpTrack.lock();
1002 auto t = pTrack.get();
1003
1004 int trackTop = 0;
1005 int trackHeight =0;
1006
1007 for (auto it : GetTracks()->Leaders()) {
1008 trackTop += trackHeight;
1009
1010 auto channels = TrackList::Channels(it);
1011 trackHeight = channels.sum( TrackView::GetTrackHeight );
1012
1013 //We have found the track we want to ensure is visible.
1014 if (channels.contains(t)) {
1015
1016 //Get the size of the trackpanel.
1017 int width, height;
1018 GetSize(&width, &height);
1019
1020 if (trackTop < mViewInfo->vpos) {
1021 height = mViewInfo->vpos - trackTop + mViewInfo->scrollStep;
1022 height /= mViewInfo->scrollStep;
1023 mListener->TP_ScrollUpDown(-height);
1024 }
1025 else if (trackTop + trackHeight > mViewInfo->vpos + height) {
1026 height = (trackTop + trackHeight) - (mViewInfo->vpos + height);
1027 height = (height + mViewInfo->scrollStep + 1) / mViewInfo->scrollStep;
1028 mListener->TP_ScrollUpDown(height);
1029 }
1030
1031 break;
1032 }
1033 }
1034 Refresh(false);
1035
1036 if ( modifyState )
1038}
1039
1040// 0.0 scrolls to top
1041// 1.0 scrolls to bottom.
1042void TrackPanel::VerticalScroll( float fracPosition){
1043
1044 int trackTop = 0;
1045 int trackHeight = 0;
1046
1047 auto tracks = GetTracks();
1048
1049 auto range = tracks->Leaders();
1050 if (!range.empty()) {
1051 trackHeight = TrackView::GetChannelGroupHeight( *range.rbegin() );
1052 --range.second;
1053 }
1054 trackTop = range.sum( TrackView::GetChannelGroupHeight );
1055
1056 int delta;
1057
1058 //Get the size of the trackpanel.
1059 int width, height;
1060 GetSize(&width, &height);
1061
1062 delta = (fracPosition * (trackTop + trackHeight - height)) - mViewInfo->vpos + mViewInfo->scrollStep;
1063 //wxLogDebug( "Scroll down by %i pixels", delta );
1064 delta /= mViewInfo->scrollStep;
1065 mListener->TP_ScrollUpDown(delta);
1066 Refresh(false);
1067}
1068
1069
1070namespace {
1071 // Drawing constants
1072 // DisplaceX and MarginX are large enough to avoid overwriting <- symbol
1073 // See TrackArt::DrawNegativeOffsetTrackArrows
1074 enum : int {
1075 // Displacement of the rectangle from upper left corner
1077 // Size of margins about the text extent that determine the rectangle size
1079 // Derived constants
1081 };
1082
1084 wxDC &dc, const Track *t, wxCoord *pW, wxCoord *pH )
1085{
1086 wxFont labelFont(12, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
1087 dc.SetFont(labelFont);
1088 dc.GetTextExtent( t->GetName(), pW, pH );
1089}
1090
1092 int leftOffset,
1093 const wxRect &trackRect, wxCoord textWidth, wxCoord textHeight )
1094{
1095 return {
1096 leftOffset + DisplaceX,
1097 trackRect.y + DisplaceY,
1098 textWidth + MarginsX,
1099 textHeight + MarginsY
1100 };
1101}
1102
1103// Draws the track name on the track, if it is needed.
1105 int leftOffset,
1106 TrackPanelDrawingContext &context, const Track * t, const wxRect & rect )
1107{
1108 if( !TrackArtist::Get( context )->mbShowTrackNameInTrack )
1109 return;
1110 auto name = t->GetName();
1111 if( name.IsEmpty())
1112 return;
1113 if( !t->IsLeader())
1114 return;
1115 auto &dc = context.dc;
1116 wxBrush Brush;
1117 wxCoord textWidth, textHeight;
1118 GetTrackNameExtent( dc, t, &textWidth, &textHeight );
1119
1120 // Logic for name background translucency (aka 'shields')
1121 // Tracks less than kOpaqueHeight high will have opaque shields.
1122 // Tracks more than kTranslucentHeight will have maximum translucency for shields.
1123 const int kOpaqueHeight = 44;
1124 const int kTranslucentHeight = 124;
1125
1126 // PRL: to do: reexamine this strange use of TrackView::GetHeight,
1127 // ultimately to compute an opacity
1128 int h = TrackView::Get( *t ).GetHeight();
1129
1130 // f codes the opacity as a number between 0.0 and 1.0
1131 float f = wxClip((h-kOpaqueHeight)/(float)(kTranslucentHeight-kOpaqueHeight),0.0,1.0);
1132 // kOpaque is the shield's alpha for tracks that are not tall
1133 // kTranslucent is the shield's alpha for tracks that are tall.
1134 const int kOpaque = 255;
1135 const int kTranslucent = 140;
1136 // 0.0 maps to full opacity, 1.0 maps to full translucency.
1137 int opacity = 255 - (255-140)*f;
1138
1139 const auto nameRect =
1140 GetTrackNameRect( leftOffset, rect, textWidth, textHeight );
1141
1142#ifdef __WXMAC__
1143 // Mac dc is a graphics dc already.
1144 AColor::UseThemeColour( &dc, clrTrackInfoSelected, clrTrackPanelText, opacity );
1145 dc.DrawRoundedRectangle( nameRect, 8.0 );
1146#else
1147 // This little dance with wxImage in order to draw to a graphic dc
1148 // which we can then paste as a translucent bitmap onto the real dc.
1149 enum : int {
1150 SecondMarginX = 1, SecondMarginY = 1,
1151 SecondMarginsX = 2 * SecondMarginX, SecondMarginsY = 2 * SecondMarginY,
1152 };
1153 wxImage image(
1154 textWidth + MarginsX + SecondMarginsX,
1155 textHeight + MarginsY + SecondMarginsY );
1156 image.InitAlpha();
1157 unsigned char *alpha=image.GetAlpha();
1158 memset(alpha, wxIMAGE_ALPHA_TRANSPARENT, image.GetWidth()*image.GetHeight());
1159
1160 {
1161 std::unique_ptr< wxGraphicsContext >
1162 pGc{ wxGraphicsContext::Create(image) };
1163 auto &gc = *pGc;
1164 // This is to a gc, not a dc.
1165 AColor::UseThemeColour( &gc, clrTrackInfoSelected, clrTrackPanelText, opacity );
1166 // Draw at 1,1, not at 0,0 to avoid clipping of the antialiasing.
1167 gc.DrawRoundedRectangle(
1168 SecondMarginX, SecondMarginY,
1169 textWidth + MarginsX, textHeight + MarginsY, 8.0 );
1170 // destructor of gc updates the wxImage.
1171 }
1172 wxBitmap bitmap( image );
1173 dc.DrawBitmap( bitmap,
1174 nameRect.x - SecondMarginX, nameRect.y - SecondMarginY );
1175#endif
1176 dc.SetTextForeground(theTheme.Colour( clrTrackPanelText ));
1177 dc.DrawText(t->GetName(),
1178 nameRect.x + MarginX,
1179 nameRect.y + MarginY);
1180}
1181
1182/*
1183
1184 The following classes define the subdivision of the area of the TrackPanel
1185 into cells with differing responses to mouse, keyboard, and scroll wheel
1186 events.
1187
1188 The classes defining the less inclusive areas are earlier, while those
1189 defining ever larger hierarchical groupings of cells are later.
1190
1191 To describe that subdivision again, informally, and top-down:
1192
1193 Firstly subtract margin areas, on the left and right, that do not interact.
1194
1195 Secondly subtract a noninterative margin above the first track, and an area
1196 below all tracks that causes deselection of all tracks if you click it.
1197 (One or both of those areas might be vertically scrolled off-screen, though.)
1198 Divide what remains into areas corresponding to the several tracks.
1199
1200 Thirdly, for each track, subtract an area below, which you can click and drag
1201 to resize the track vertically.
1202
1203 Fourthly, subtract an area at the left, which contains the track controls,
1204 such as the menu and delete and minimize buttons, and others appropriate
1205 to the track subtype.
1206
1207 Fifthly, divide what remains into the vertically stacked channels, if there
1208 are more than one, alternating with separators, which can be clicked to
1209 resize the channel views.
1210
1211 Sixthly, divide each channel into one or more vertically stacked sub-views.
1212
1213 Lastly, split the area for each sub-view into a vertical ruler, and an area
1214 that displays the channel's own contents.
1215
1216*/
1217
1219 std::vector< UIHandlePtr > HitTest(
1220 const TrackPanelMouseState &, const AudacityProject *) override
1221 { return {}; }
1222 virtual std::shared_ptr< Track > DoFindTrack() override { return {}; }
1223 static std::shared_ptr<EmptyCell> Instance()
1224 {
1225 static auto instance = std::make_shared< EmptyCell >();
1226 return instance;
1227 }
1228
1229 // TrackPanelDrawable implementation
1230 void Draw(
1231 TrackPanelDrawingContext &context,
1232 const wxRect &rect, unsigned iPass ) override
1233 {
1234 if ( iPass == TrackArtist::PassMargins ) {
1235 // Draw a margin area of TrackPanel
1236 auto dc = &context.dc;
1237
1238 AColor::TrackPanelBackground( dc, false );
1239 dc->DrawRectangle( rect );
1240 }
1241 }
1242};
1243
1244// A vertical ruler left of a channel
1247 const std::shared_ptr< TrackView > &pView, wxCoord leftOffset )
1248 : mpView{ pView }, mLeftOffset{ leftOffset } {}
1249 Subdivision Children( const wxRect &rect ) override
1250 {
1251 return { Axis::X, Refinement{
1252 { rect.GetLeft(),
1253 TrackVRulerControls::Get( *mpView ).shared_from_this() },
1254 { mLeftOffset, mpView }
1255 } };
1256 }
1257 std::shared_ptr< TrackView > mpView;
1259};
1260
1261// One or more sub-views of one channel, stacked vertically, each containing
1262// a vertical ruler and a channel
1265 const std::shared_ptr<Track> &pTrack,
1266 TrackView::Refinement refinement, wxCoord leftOffset )
1267 : mpTrack{ pTrack }
1268 , mRefinement{ std::move( refinement ) }
1269 , mLeftOffset{ leftOffset } {}
1270 Subdivision Children( const wxRect &rect ) override
1271 {
1272 Refinement refinement;
1273 auto y1 = rect.GetTop();
1274 for ( const auto &subView : mRefinement ) {
1275 y1 = std::max( y1, subView.first );
1276 refinement.emplace_back( y1,
1277 std::make_shared< VRulerAndChannel >(
1278 subView.second, mLeftOffset ) );
1279 }
1280 return { Axis::Y, std::move( refinement ) };
1281 }
1282
1283 // TrackPanelDrawable implementation
1284 void Draw(
1285 TrackPanelDrawingContext &context,
1286 const wxRect &rect, unsigned iPass ) override
1287 {
1288 // This overpaints the track area, but sometimes too the stereo channel
1289 // separator, so draw at least later than that
1290 if ( iPass == TrackArtist::PassBorders ) {
1291 DrawTrackName( mLeftOffset,
1292 context, mpTrack->SubstitutePendingChangedTrack().get(), rect );
1293 }
1294 if ( iPass == TrackArtist::PassControls ) {
1295 if (mRefinement.size() > 1) {
1296 // Draw lines separating sub-views
1297 auto &dc = context.dc;
1298 AColor::CursorColor( &dc );
1299 auto iter = mRefinement.begin() + 1, end = mRefinement.end();
1300 for ( ; iter != end; ++iter ) {
1301 auto yy = iter->first;
1302 AColor::Line( dc, mLeftOffset, yy, rect.GetRight(), yy );
1303 }
1304 }
1305 }
1306 }
1307
1309 TrackPanelDrawingContext &context,
1310 const wxRect &rect, const wxRect &panelRect, unsigned iPass ) override
1311 {
1312 auto result = rect;
1313 if ( iPass == TrackArtist::PassBorders ) {
1314 if ( true ) {
1315 wxCoord textWidth, textHeight;
1316 GetTrackNameExtent( context.dc, mpTrack.get(),
1317 &textWidth, &textHeight );
1318 result =
1319 GetTrackNameRect( mLeftOffset, rect, textWidth, textHeight );
1320 }
1321 }
1322 return result;
1323 }
1324
1325 std::shared_ptr< Track > mpTrack;
1328};
1329
1330//Simply fills area using specified brush and outlines borders
1332{
1333 //Required to keep selection behaviour similar to others
1334 std::shared_ptr<Track> mTrack;
1336public:
1337 explicit EmptyPanelRect(const std::shared_ptr<Track>& track, int fillBrushName)
1338 : mTrack(track), mFillBrushName(fillBrushName)
1339 {
1340 }
1341
1343
1345 const wxRect& rect, unsigned iPass) override
1346 {
1347 if (iPass == TrackArtist::PassBackground)
1348 {
1349 context.dc.SetPen(*wxTRANSPARENT_PEN);
1350 AColor::UseThemeColour(&context.dc, mFillBrushName);
1351 context.dc.DrawRectangle(rect);
1352 wxRect bevel(rect.x, rect.y, rect.width - 1, rect.height - 1);
1353 AColor::BevelTrackInfo(context.dc, true, bevel, false);
1354 }
1355 }
1356
1357 std::shared_ptr<Track> DoFindTrack() override
1358 {
1359 return mTrack;
1360 }
1361
1362 std::vector<UIHandlePtr> HitTest(const TrackPanelMouseState& state,
1363 const AudacityProject* pProject) override
1364 {
1365 return {};
1366 }
1367};
1368
1369//Simply place children one after another horizontally, without any specific logic
1371
1373
1375 : mRefinement(std::move(refinement))
1376 {
1377 }
1378
1379 Subdivision Children(const wxRect& /*rect*/) override
1380 {
1381 return { Axis::X, mRefinement };
1382 }
1383
1384};
1385
1386
1387// optional affordance areas, and n channels with vertical rulers,
1388// alternating with n - 1 resizers;
1389// each channel-ruler pair might be divided into multiple views
1391 ChannelGroup( const std::shared_ptr< Track > &pTrack, wxCoord leftOffset )
1392 : mpTrack{ pTrack }, mLeftOffset{ leftOffset } {}
1393 Subdivision Children( const wxRect &rect_ ) override
1394 {
1395 auto rect = rect_;
1396 Refinement refinement;
1397
1398 const auto channels = TrackList::Channels( mpTrack.get() );
1399 const auto pLast = *channels.rbegin();
1400 wxCoord yy = rect.GetTop();
1401 auto heights = FindAdjustedChannelHeights(*mpTrack);
1402 auto pHeight = heights.begin();
1403 for ( auto channel : channels )
1404 {
1405 auto &view = TrackView::Get( *channel );
1406 if (auto affordance = view.GetAffordanceControls())
1407 {
1408 auto panelRect = std::make_shared<EmptyPanelRect>(
1409 channel->shared_from_this(),
1410 channel->GetSelected() ? clrTrackInfoSelected : clrTrackInfo);
1411 Refinement hgroup {
1412 std::make_pair(rect.GetLeft() + 1, panelRect),
1413 std::make_pair(mLeftOffset, affordance)
1414 };
1415 refinement.emplace_back(yy, std::make_shared<HorizontalGroup>(hgroup));
1417 }
1418
1419 auto height = *pHeight++;
1420 rect.SetTop( yy );
1421 rect.SetHeight( height - kChannelSeparatorThickness );
1422 refinement.emplace_back( yy,
1423 std::make_shared< VRulersAndChannels >(
1424 channel->shared_from_this(),
1425 TrackView::Get( *channel ).GetSubViews( rect ),
1426 mLeftOffset ) );
1427 if ( channel != pLast ) {
1428 yy += height;
1429 refinement.emplace_back(
1431 TrackPanelResizerCell::Get( *channel ).shared_from_this() );
1432 }
1433 }
1434
1435 return { Axis::Y, std::move( refinement ) };
1436 }
1437
1438 void Draw(TrackPanelDrawingContext& context, const wxRect& rect, unsigned iPass) override
1439 {
1440 TrackPanelGroup::Draw(context, rect, iPass);
1441 if (iPass == TrackArtist::PassFocus && mpTrack->IsSelected())
1442 {
1443 const auto channels = TrackList::Channels(mpTrack.get());
1444 const auto pLast = *channels.rbegin();
1445 wxCoord yy = rect.GetTop();
1446 auto heights = FindAdjustedChannelHeights(*mpTrack);
1447 auto pHeight = heights.begin();
1448 for (auto channel : channels)
1449 {
1450 auto& view = TrackView::Get(*channel);
1451 auto height = *pHeight++;
1452 if (auto affordance = view.GetAffordanceControls())
1453 {
1454 height += kAffordancesAreaHeight;
1455 }
1456 auto trackRect = wxRect(
1457 mLeftOffset,
1458 yy,
1459 rect.GetRight() - mLeftOffset,
1461 TrackArt::DrawCursor(context, trackRect, mpTrack.get());
1462 yy += height;
1463 }
1464 }
1465 }
1466
1467 std::shared_ptr< Track > mpTrack;
1469};
1470
1471// A track control panel, left of n vertical rulers and n channels
1472// alternating with n - 1 resizers
1475 const std::shared_ptr< Track > &pTrack, wxCoord leftOffset )
1476 : mpTrack{ pTrack }, mLeftOffset{ leftOffset } {}
1477 Subdivision Children( const wxRect &rect ) override
1478 { return { Axis::X, Refinement{
1479 { rect.GetLeft(),
1480 TrackControls::Get( *mpTrack ).shared_from_this() },
1481 { rect.GetLeft() + kTrackInfoWidth,
1482 std::make_shared< ChannelGroup >( mpTrack, mLeftOffset ) }
1483 } }; }
1484
1485 // TrackPanelDrawable implementation
1487 const wxRect &rect, unsigned iPass ) override
1488 {
1489 if ( iPass == TrackArtist::PassBorders ) {
1490 auto &dc = context.dc;
1491 dc.SetBrush(*wxTRANSPARENT_BRUSH);
1492 dc.SetPen(*wxBLACK_PEN);
1493
1494 // border
1495 dc.DrawRectangle(
1496 rect.x, rect.y,
1497 rect.width - kShadowThickness, rect.height - kShadowThickness
1498 );
1499
1500 // shadow
1501 // Stroke lines along bottom and right, which are slightly short at
1502 // bottom-left and top-right
1503 const auto right = rect.GetRight();
1504 const auto bottom = rect.GetBottom();
1505
1506 // bottom
1507 AColor::Line(dc, rect.x + 2, bottom, right, bottom);
1508 // right
1509 AColor::Line(dc, right, rect.y + 2, right, bottom);
1510 }
1511 if ( iPass == TrackArtist::PassFocus ) {
1512 // Sometimes highlight is not drawn on backing bitmap. I thought
1513 // it was because FindFocus did not return the TrackPanel on Mac, but
1514 // when I removed that test, yielding this condition:
1515 // if (GetFocusedTrack() != NULL) {
1516 // the highlight was reportedly drawn even when something else
1517 // was the focus and no highlight should be drawn. -RBD
1518 const auto artist = TrackArtist::Get( context );
1519 auto &trackPanel = *artist->parent;
1520 auto &trackFocus = TrackFocus::Get( *trackPanel.GetProject() );
1521 if (trackFocus.Get() == mpTrack.get() &&
1522 wxWindow::FindFocus() == &trackPanel ) {
1524 wxRect theRect = rect;
1525 auto &dc = context.dc;
1526 dc.SetBrush(*wxTRANSPARENT_BRUSH);
1527
1528 AColor::TrackFocusPen( &dc, 2 );
1529 dc.DrawRectangle(theRect);
1530 theRect.Deflate(1);
1531
1532 AColor::TrackFocusPen( &dc, 1 );
1533 dc.DrawRectangle(theRect);
1534 theRect.Deflate(1);
1535
1536 AColor::TrackFocusPen( &dc, 0 );
1537 dc.DrawRectangle(theRect);
1538 }
1539 }
1540 }
1541
1544 const wxRect &rect, const wxRect &, unsigned iPass ) override
1545 {
1546 if ( iPass == TrackArtist::PassBorders )
1547 return {
1548 rect.x - kBorderThickness,
1549 rect.y - kBorderThickness,
1550 rect.width + 2 * kBorderThickness + kShadowThickness,
1551 rect.height + 2 * kBorderThickness + kShadowThickness
1552 };
1553 else if ( iPass == TrackArtist::PassFocus ) {
1554 constexpr auto extra = kBorderThickness + 3;
1555 return {
1556 rect.x - extra,
1557 rect.y - extra,
1558 rect.width + 2 * extra + kShadowThickness,
1559 rect.height + 2 * extra + kShadowThickness
1560 };
1561 }
1562 else
1563 return rect;
1564 }
1565
1566 std::shared_ptr< Track > mpTrack;
1568};
1569
1570// Stacks a label and a single or multi-channel track on a resizer below,
1571// which is associated with the last channel
1574 const std::shared_ptr< Track > &pTrack, wxCoord leftOffset )
1575 : mpTrack{ pTrack }, mLeftOffset{ leftOffset } {}
1576 Subdivision Children( const wxRect &rect ) override
1577 { return { Axis::Y, Refinement{
1578 { rect.GetTop(),
1579 std::make_shared< LabeledChannelGroup >( mpTrack, mLeftOffset ) },
1580 { rect.GetTop() + rect.GetHeight() - kTrackSeparatorThickness,
1582 **TrackList::Channels( mpTrack.get() ).rbegin() ).shared_from_this()
1583 }
1584 } }; }
1585 std::shared_ptr< Track > mpTrack;
1587};
1588
1589// Stacks a dead area at top, the tracks, and the click-to-deselect area below
1591 explicit Subgroup( TrackPanel &panel ) : mPanel{ panel } {}
1592 Subdivision Children( const wxRect &rect ) override
1593 {
1594 const auto &viewInfo = *mPanel.GetViewInfo();
1595 wxCoord yy = -viewInfo.vpos;
1596 Refinement refinement;
1597
1598 auto &tracks = *mPanel.GetTracks();
1599 if ( tracks.Any() )
1600 refinement.emplace_back( yy, EmptyCell::Instance() ),
1601 yy += kTopMargin;
1602
1603 for ( const auto leader : tracks.Leaders() ) {
1604 wxCoord height = 0;
1605 for ( auto channel : TrackList::Channels( leader ) ) {
1606 auto &view = TrackView::Get( *channel );
1607 height += view.GetHeight();
1608 }
1609 refinement.emplace_back( yy,
1610 std::make_shared< ResizingChannelGroup >(
1611 leader->SharedPointer(), viewInfo.GetLeftOffset() )
1612 );
1613 yy += height;
1614 }
1615
1616 refinement.emplace_back( std::max( 0, yy ), mPanel.GetBackgroundCell() );
1617
1618 return { Axis::Y, std::move( refinement ) };
1619 }
1621};
1622
1623// Main group shaves off the left and right margins
1625 explicit MainGroup( TrackPanel &panel ) : mPanel{ panel } {}
1626 Subdivision Children( const wxRect &rect ) override
1627 { return { Axis::X, Refinement{
1628 { 0, EmptyCell::Instance() },
1629 { kLeftMargin, std::make_shared< Subgroup >( mPanel ) },
1630 { rect.GetRight() + 1 - kRightMargin, EmptyCell::Instance() }
1631 } }; }
1633};
1634
1635}
1636
1637std::shared_ptr<TrackPanelNode> TrackPanel::Root()
1638{
1639 // Root and other subgroup objects are throwaways.
1640 // They might instead be cached to avoid repeated allocation.
1641 // That cache would need invalidation when there is addition, deletion, or
1642 // permutation of tracks, or change of width of the vertical rulers.
1643 return std::make_shared< MainGroup >( *this );
1644}
1645
1646// This finds the rectangle of a given track (including all channels),
1647// either that of the label 'adornment' or the track itself
1648// The given track is assumed to be the first channel
1649wxRect TrackPanel::FindTrackRect( const Track * target )
1650{
1651 auto leader = *GetTracks()->FindLeader( target );
1652 if (!leader) {
1653 return {};
1654 }
1655
1656 return CellularPanel::FindRect( [&] ( TrackPanelNode &node ) {
1657 if (auto pGroup = dynamic_cast<const LabeledChannelGroup*>( &node ))
1658 return pGroup->mpTrack.get() == leader;
1659 return false;
1660 } );
1661}
1662
1664{
1665 auto rect = FindTrackRect(target);
1666 if (rect != wxRect{}) {
1667 // Enlarge horizontally.
1668 // PRL: perhaps it's one pixel too much each side, including some gray
1669 // beyond the yellow?
1670 rect.x = 0;
1671 GetClientSize(&rect.width, nullptr);
1672
1673 // Enlarge vertically, enough to enclose the yellow focus border pixels
1674 // The the outermost ring of gray pixels is included on three sides
1675 // but not the top (should that be fixed?)
1676
1677 // (Note that TrackPanel paints its focus over the "top margin" of the
1678 // rectangle allotted to the track, according to TrackView::GetY() and
1679 // TrackView::GetHeight(), but also over the margin of the next track.)
1680
1681 rect.height += kBottomMargin;
1682 int dy = kTopMargin - 1;
1683 rect.Inflate( 0, dy );
1684
1685 // Note that this rectangle does not coincide with any one of
1686 // the nodes in the subdivision.
1687 }
1688 return rect;
1689}
1690
1691std::vector<wxRect> TrackPanel::FindRulerRects( const Track *target )
1692{
1693 std::vector<wxRect> results;
1694 if (target)
1695 VisitCells( [&]( const wxRect &rect, TrackPanelCell &visited ) {
1696 if (auto pRuler = dynamic_cast<const TrackVRulerControls*>(&visited);
1697 pRuler && pRuler->FindTrack().get() == target)
1698 results.push_back(rect);
1699 } );
1700 return results;
1701}
1702
1704{
1705 auto pTrack = TrackFocus::Get( *GetProject() ).Get();
1706 return pTrack ? &TrackView::Get( *pTrack ) : GetBackgroundCell().get();
1707}
1708
1710{
1711 // This may have a side-effect of assigning a focus if there was none
1712 auto& trackFocus = TrackFocus::Get(*GetProject());
1713 trackFocus.Set(trackFocus.Get());
1715}
1716
1718{
1719 if (auto cell = GetFocusedCell())
1720 Refresh( false );
1721}
wxImage(22, 22)
END_EVENT_TABLE()
const TranslatableString name
Definition: Distortion.cpp:76
XO("Cut/Copy/Paste")
#define safenew
Definition: MemoryX.h:10
AUDACITY_DLL_API void SetProjectPanel(AudacityProject &project, wxWindow &panel)
AUDACITY_DLL_API wxFrame & GetProjectFrame(AudacityProject &project)
Get the top-level window associated with the project (as a wxFrame only, when you do not need to use ...
AUDACITY_DLL_API AttachedWindows & GetAttachedWindows(AudacityProject &project)
accessors for certain important windows associated with each project
THEME_API Theme theTheme
Definition: Theme.cpp:82
constexpr auto kTimerInterval
#define A(N)
Definition: ToChars.cpp:62
static Settings & settings()
Definition: TrackInfo.cpp:87
std::unique_ptr< wxCursor > MakeCursor(int WXUNUSED(CursorId), const char *const pXpm[36], int HotX, int HotY)
Definition: TrackPanel.cpp:186
bool within(A a, B b, DIST d)
Definition: TrackPanel.cpp:167
@ kBottomMargin
@ kTopInset
@ kTrackSeparatorThickness
@ kTopMargin
@ kChannelSeparatorThickness
@ kAffordancesAreaHeight
@ kTrackInfoBtnSize
Definition: ViewInfo.h:96
@ kVerticalPadding
Definition: ViewInfo.h:92
int id
@ kRightMargin
Definition: ZoomInfo.h:28
@ kTrackInfoWidth
Definition: ZoomInfo.h:30
@ kRightInset
Definition: ZoomInfo.h:26
@ kShadowThickness
Definition: ZoomInfo.h:23
@ kBorderThickness
Definition: ZoomInfo.h:22
@ kLeftMargin
Definition: ZoomInfo.h:27
@ kLeftInset
Definition: ZoomInfo.h:25
static void Line(wxDC &dc, wxCoord x1, wxCoord y1, wxCoord x2, wxCoord y2)
Definition: AColor.cpp:187
static void CursorColor(wxDC *dc)
Definition: AColor.cpp:463
static void TrackFocusPen(wxDC *dc, int level)
Definition: AColor.cpp:481
static void BevelTrackInfo(wxDC &dc, bool up, const wxRect &r, bool highlight=false)
Definition: AColor.cpp:340
static void TrackPanelBackground(wxDC *dc, bool selected)
Definition: AColor.cpp:454
static void UseThemeColour(wxDC *dc, int iBrush, int iPen=-1, int alpha=255)
Definition: AColor.cpp:372
This is an Audacity Specific ruler panel which additionally has border, selection markers,...
void Refresh(bool eraseBackground=true, const wxRect *rect=(const wxRect *) NULL) override
void SetLeftOffset(int offset)
static AdornedRulerPanel & Get(AudacityProject &project)
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:90
static AudioIO * Get()
Definition: AudioIO.cpp:147
wxDC & GetBackingDCForRepaint()
Definition: BackedPanel.cpp:35
void DisplayBitmap(wxDC &dc)
Definition: BackedPanel.cpp:65
void RepairBitmap(wxDC &dc, wxCoord x, wxCoord y, wxCoord width, wxCoord height)
Definition: BackedPanel.cpp:60
Formerly part of TrackPanel, this abstract base class has no special knowledge of Track objects and i...
Definition: CellularPanel.h:34
void Draw(TrackPanelDrawingContext &context, unsigned nPasses)
void DoContextMenu(TrackPanelCell *pCell=nullptr)
void VisitCells(const SimpleCellVisitor &visitor)
UIHandlePtr Target()
ViewInfo * mViewInfo
FoundCell FindCell(int mouseX, int mouseY)
wxRect FindRect(const TrackPanelCell &cell)
wxMouseState mLastMouseState
wxCoord MostRecentXCoord() const
void HandleCursorForPresentMouseState(bool doHit=true)
Subclass * Find(const RegisteredFactory &key)
Get a (bare) pointer to an attachment, or null, down-cast it to Subclass *; will not create on demand...
Definition: ClientData.h:333
Subclass & Get(const RegisteredFactory &key)
Get reference to an attachment, creating on demand if not present, down-cast it to Subclass.
Definition: ClientData.h:309
void Assign(const RegisteredFactory &key, ReplacementPointer &&replacement)
Reassign Site's pointer to ClientData.
Definition: ClientData.h:355
std::shared_ptr< Track > FindTrack()
@ TrackPanel
Full repaint time of the TrackPanel.
static Stopwatch CreateStopwatch(SectionID section) noexcept
Create a Stopwatch for the section specified.
Subscription Subscribe(Callback callback)
Connect a callback to the Publisher; later-connected are called earlier.
Definition: Observer.h:199
void DrawOverlays(bool repaint_all, wxDC *pDC=nullptr)
AudioTrack subclass that can also be audibly replayed by the program.
Definition: Track.h:917
bool GetSolo() const
Definition: Track.h:925
bool IsAudioActive() const
static ProjectAudioIO & Get(AudacityProject &project)
static ProjectAudioManager & Get(AudacityProject &project)
void ModifyState(bool bWantsAutoSave)
static ProjectHistory & Get(AudacityProject &project)
static ProjectSettings & Get(AudacityProject &project)
static ProjectStatus & Get(AudacityProject &project)
void Set(const TranslatableString &msg, StatusBarField field=mainStatusBarField)
A top-level window associated with a project, and handling scrollbars and zooming.
Definition: ProjectWindow.h:41
static ProjectWindow & Get(AudacityProject &project)
static RealtimeEffectManager & Get(AudacityProject &project)
Defines a selected portion of a project.
static SyncLockState & Get(AudacityProject &project)
Definition: SyncLock.cpp:26
wxColour & Colour(int iIndex)
static TrackArtist * Get(TrackPanelDrawingContext &)
Definition: TrackArtist.cpp:69
static TrackControls & Get(Track &track)
static TrackFocus & Get(AudacityProject &project)
Track * Get()
Abstract base class for an object holding data associated with points on a time axis.
Definition: Track.h:226
void EnsureVisible(bool modifyState=false)
Definition: Track.cpp:98
std::pair< int, int > vrulerSize
Definition: Track.h:387
std::shared_ptr< Track > SubstitutePendingChangedTrack()
Definition: Track.cpp:1211
wxString GetName() const
Definition: Track.h:467
bool IsLeader() const
Definition: Track.cpp:406
std::shared_ptr< Subclass > Lock(const std::weak_ptr< Subclass > &wTrack)
Definition: Track.h:1606
auto SelectedLeaders() -> TrackIterRange< TrackType >
Definition: Track.h:1491
auto Any() -> TrackIterRange< TrackType >
Definition: Track.h:1440
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:487
void UpdatePendingTracks()
Definition: Track.cpp:1086
TrackIter< Track > FindLeader(Track *pTrack)
Definition: Track.cpp:645
static auto Channels(TrackType *pTrack) -> TrackIterRange< TrackType >
Definition: Track.h:1544
virtual void Draw(TrackPanelDrawingContext &context, const wxRect &rect, unsigned iPass)
std::pair< Axis, Refinement > Subdivision
std::vector< Child > Refinement
The TrackPanel class coordinates updates and operations on the main part of the screen which contains...
Definition: TrackPanel.h:65
void UpdateVRuler(Track *t)
Definition: TrackPanel.cpp:924
void UpdatePrefs() override
Definition: TrackPanel.cpp:351
void HandlePageUpKey()
Definition: TrackPanel.cpp:595
AdornedRulerPanel * mRuler
Definition: TrackPanel.h:199
size_t GetSelectedTrackCount() const
Definition: TrackPanel.cpp:631
TrackPanelListener * mListener
Definition: TrackPanel.h:195
void UpdateViewIfNoTracks()
Definition: TrackPanel.cpp:636
std::shared_ptr< TrackList > mTracks
Definition: TrackPanel.h:197
double GetMostRecentXPos()
Definition: TrackPanel.cpp:737
virtual ~TrackPanel()
Definition: TrackPanel.cpp:341
std::shared_ptr< TrackPanelNode > Root() override
void ProcessUIHandleResult(TrackPanelCell *pClickedTrack, TrackPanelCell *pLatestCell, unsigned refreshResult) override
Definition: TrackPanel.cpp:530
Observer::Subscription mAudioIOSubscription
Definition: TrackPanel.h:189
std::shared_ptr< CommonTrackPanelCell > GetBackgroundCell()
Definition: TrackPanel.cpp:865
AudacityProject * GetProject() const override
Definition: TrackPanel.cpp:362
void OnEnsureVisible(const TrackListEvent &event)
Definition: TrackPanel.cpp:997
void OnSize(wxSizeEvent &)
Definition: TrackPanel.cpp:376
std::vector< wxRect > FindRulerRects(const Track *target)
Observer::Subscription mTrackListSubscription
Definition: TrackPanel.h:188
SelectedRegion mLastDrawnSelectedRegion
Definition: TrackPanel.h:226
void Refresh(bool eraseBackground=true, const wxRect *rect=(const wxRect *) NULL) override
Definition: TrackPanel.cpp:781
void OnTrackMenu(Track *t=NULL)
Definition: TrackPanel.cpp:991
const TrackList * GetTracks() const
Definition: TrackPanel.h:168
static TrackPanel & Get(AudacityProject &project)
Definition: TrackPanel.cpp:231
void OnTrackFocusChange(struct TrackFocusChangeMessage)
void UpdateTrackVRuler(Track *t)
Definition: TrackPanel.cpp:932
void UpdateSelectionDisplay()
Definition: TrackPanel.cpp:620
void HandlePageDownKey()
Definition: TrackPanel.cpp:600
Observer::Subscription mRealtimeEffectManagerSubscription
Definition: TrackPanel.h:192
std::shared_ptr< CommonTrackPanelCell > mpBackground
Definition: TrackPanel.h:230
wxRect FindFocusedTrackRect(const Track *target)
Observer::Subscription mSyncLockSubscription
Definition: TrackPanel.h:193
void OnTimer(wxTimerEvent &event)
AS: This gets called on our wx timer events.
Definition: TrackPanel.cpp:405
void OnKeyDown(wxKeyEvent &event)
Definition: TrackPanel.cpp:688
void SetFocusedCell() override
void UpdateStatusMessage(const TranslatableString &status) override
Definition: TrackPanel.cpp:611
static void Destroy(AudacityProject &project)
Definition: TrackPanel.cpp:241
void UpdateVRulerSize()
Definition: TrackPanel.cpp:973
void OnTrackListDeletion()
Definition: TrackPanel.cpp:673
bool mRefreshBacking
Definition: TrackPanel.h:221
wxRect FindTrackRect(const Track *target)
void OnMouseEvent(wxMouseEvent &event)
Definition: TrackPanel.cpp:708
void DrawTracks(wxDC *dc)
Definition: TrackPanel.cpp:815
void SetBackgroundCell(const std::shared_ptr< CommonTrackPanelCell > &pCell)
Definition: TrackPanel.cpp:860
TrackPanel::AudacityTimer mTimer
void OnUndoReset(struct UndoRedoMessage)
Definition: TrackPanel.cpp:462
Observer::Subscription mUndoSubscription
Definition: TrackPanel.h:190
void UpdateVRulers()
Definition: TrackPanel.cpp:916
int mTimeCount
Definition: TrackPanel.h:219
void VerticalScroll(float fracPosition)
void OnAudioIO(AudioIOEvent)
Definition: TrackPanel.cpp:802
TrackPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size, const std::shared_ptr< TrackList > &tracks, ViewInfo *viewInfo, AudacityProject *project, AdornedRulerPanel *ruler)
Definition: TrackPanel.cpp:254
void OnSyncLockChange(struct SyncLockChangeMessage)
Definition: TrackPanel.cpp:457
bool IsAudioActive()
Definition: TrackPanel.cpp:605
void MakeParentRedrawScrollbars()
Definition: TrackPanel.cpp:515
void OnIdle(wxIdleEvent &event)
Definition: TrackPanel.cpp:384
void OnPaint(wxPaintEvent &event)
Definition: TrackPanel.cpp:472
Observer::Subscription mFocusChangeSubscription
Definition: TrackPanel.h:191
void RefreshTrack(Track *trk, bool refreshbacking=true)
Definition: TrackPanel.cpp:743
std::unique_ptr< TrackArtist > mTrackArtist
Definition: TrackPanel.h:201
TrackPanelCell * GetFocusedCell() override
void OnTrackListResizing(const TrackListEvent &event)
Definition: TrackPanel.cpp:660
virtual void TP_HandleResize()=0
virtual void TP_ScrollWindow(double scrollto)=0
virtual bool TP_ScrollUpDown(int delta)=0
virtual void TP_RedrawScrollbars()=0
The TrackPanel is built up of nodes, subtrees of the CellularPanel's area Common base class for Track...
static TrackPanelResizerCell & Get(Track &track)
static TrackVRulerControls & Get(TrackView &)
virtual void UpdateRuler(const wxRect &rect)=0
static int GetChannelGroupHeight(const Track *pTrack)
Definition: TrackView.cpp:34
int GetHeight() const
Definition: TrackView.cpp:164
virtual Refinement GetSubViews(const wxRect &rect)
Definition: TrackView.cpp:131
static int GetTrackHeight(const Track *pTrack)
Definition: TrackView.cpp:29
static TrackView & Get(Track &)
Definition: TrackView.cpp:69
std::vector< std::pair< wxCoord, std::shared_ptr< TrackView > > > Refinement
Definition: TrackView.h:104
Holds a msgid for the translation catalog; may also bind format arguments.
TranslatableString & Join(TranslatableString arg, const wxString &separator={}) &
Append another translatable string.
unsigned Result
Definition: UIHandle.h:38
static UndoManager & Get(AudacityProject &project)
Definition: UndoManager.cpp:71
NotifyingSelectedRegion selectedRegion
Definition: ViewInfo.h:219
static ViewInfo & Get(AudacityProject &project)
Definition: ViewInfo.cpp:235
int scrollStep
Definition: ViewInfo.h:237
void SetHeight(int height)
Definition: ViewInfo.h:202
double PositionToTime(int64 position, int64 origin=0, bool ignoreFisheye=false) const
Definition: ZoomInfo.cpp:35
static double GetDefaultZoom()
Definition: ZoomInfo.h:113
void SetWidth(int width)
Definition: ZoomInfo.h:86
double GetScreenEndTime() const
Definition: ZoomInfo.h:104
int GetVRulerOffset() const
Definition: ZoomInfo.h:90
double h
Definition: ZoomInfo.h:52
void SetVRulerWidth(int width)
Definition: ZoomInfo.h:89
int GetLeftOffset() const
Definition: ZoomInfo.h:93
void SetZoom(double pixelsPerSecond)
Definition: ZoomInfo.cpp:80
int GetVRulerWidth() const
Definition: ZoomInfo.h:88
int vpos
Definition: ZoomInfo.h:50
std::shared_ptr< Track > DoFindTrack() override
void Draw(TrackPanelDrawingContext &context, const wxRect &rect, unsigned iPass) override
std::vector< UIHandlePtr > HitTest(const TrackPanelMouseState &state, const AudacityProject *pProject) override
EmptyPanelRect(const std::shared_ptr< Track > &track, int fillBrushName)
void SetLabel(const TranslatableString &label)
void CallAfter(Action action)
Schedule an action to be done later, and in the main thread.
Definition: BasicUI.cpp:208
Services * Get()
Fetch the global instance, or nullptr if none is yet installed.
Definition: BasicUI.cpp:196
std::unique_ptr< WindowPlacement > FindFocus()
Find the window that is accepting keyboard input, if any.
Definition: BasicUI.h:343
void Capture(wxWindow *handler)
auto end(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:159
Namespace containing an enum 'what to do on a refresh?'.
Definition: RefreshCode.h:16
@ RefreshLatestCell
Definition: RefreshCode.h:25
AUDACITY_DLL_API void DrawCursor(TrackPanelDrawingContext &context, const wxRect &rect, const Track *track)
Definition: TrackArt.cpp:501
AttachedWindows::RegisteredFactory sKey
Definition: TrackPanel.cpp:207
std::shared_ptr< Track > FindTrack(TrackPanelCell *pCell)
Definition: TrackPanel.cpp:521
wxRect GetTrackNameRect(int leftOffset, const wxRect &trackRect, wxCoord textWidth, wxCoord textHeight)
void DrawTrackName(int leftOffset, TrackPanelDrawingContext &context, const Track *t, const wxRect &rect)
std::vector< int > FindAdjustedChannelHeights(Track &t)
Definition: TrackPanel.cpp:871
void GetTrackNameExtent(wxDC &dc, const Track *t, wxCoord *pW, wxCoord *pH)
STL namespace.
enum AudioIOEvent::Type type
Posted when effect is being added or removed to/from track or project.
std::shared_ptr< Track > track
null, if changes happened in the project scope
Sent after sync lock setting changes, with its new state.
Definition: SyncLock.h:20
Notification of changes in individual tracks of TrackList, or of TrackList's composition.
Definition: Track.h:1289
const int mExtra
Definition: Track.h:1328
const std::weak_ptr< Track > mpTrack
Definition: Track.h:1327
const Type mType
Definition: Track.h:1326
Type of message published by UndoManager.
Definition: UndoManager.h:55
enum UndoRedoMessage::Type type
ChannelGroup(const std::shared_ptr< Track > &pTrack, wxCoord leftOffset)
void Draw(TrackPanelDrawingContext &context, const wxRect &rect, unsigned iPass) override
Subdivision Children(const wxRect &rect_) override
void Draw(TrackPanelDrawingContext &context, const wxRect &rect, unsigned iPass) override
static std::shared_ptr< EmptyCell > Instance()
virtual std::shared_ptr< Track > DoFindTrack() override
std::vector< UIHandlePtr > HitTest(const TrackPanelMouseState &, const AudacityProject *) override
Subdivision Children(const wxRect &) override
void Draw(TrackPanelDrawingContext &context, const wxRect &rect, unsigned iPass) override
LabeledChannelGroup(const std::shared_ptr< Track > &pTrack, wxCoord leftOffset)
Subdivision Children(const wxRect &rect) override
wxRect DrawingArea(TrackPanelDrawingContext &, const wxRect &rect, const wxRect &, unsigned iPass) override
Subdivision Children(const wxRect &rect) override
ResizingChannelGroup(const std::shared_ptr< Track > &pTrack, wxCoord leftOffset)
Subdivision Children(const wxRect &rect) override
Subdivision Children(const wxRect &rect) override
Subdivision Children(const wxRect &rect) override
VRulerAndChannel(const std::shared_ptr< TrackView > &pView, wxCoord leftOffset)
wxRect DrawingArea(TrackPanelDrawingContext &context, const wxRect &rect, const wxRect &panelRect, unsigned iPass) override
void Draw(TrackPanelDrawingContext &context, const wxRect &rect, unsigned iPass) override
Subdivision Children(const wxRect &rect) override
VRulersAndChannels(const std::shared_ptr< Track > &pTrack, TrackView::Refinement refinement, wxCoord leftOffset)