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