Audacity 3.2.0
AdornedRulerPanel.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 AdornedRulerPanel.cpp
6
7 Dominic Mazzoni
8
9*******************************************************************//******************************************************************/
20
21
22#include "AdornedRulerPanel.h"
23
24#include <wx/app.h>
25#include <wx/setup.h> // for wxUSE_* macros
26#include <wx/tooltip.h>
27
28#include "AColor.h"
29#include "AllThemeResources.h"
30#include "AudioIO.h"
31#include "widgets/BasicMenu.h"
32#include "Beats.h"
33#include "CellularPanel.h"
34#include "../images/Cursors.h"
35#include "HitTestResult.h"
36#include "Prefs.h"
37#include "Project.h"
38#include "ProjectAudioIO.h"
39#include "ProjectAudioManager.h"
40#include "ProjectWindows.h"
41#include "ProjectStatus.h"
42#include "ProjectWindow.h"
43#include "RefreshCode.h"
44#include "SelectUtilities.h"
45#include "Snap.h"
46#include "Track.h"
48#include "UIHandle.h"
49#include "ViewInfo.h"
50#include "prefs/TracksPrefs.h"
51#include "prefs/ThemePrefs.h"
52#include "toolbars/ToolBar.h"
54#include "tracks/ui/Scrubbing.h"
55#include "tracks/ui/TrackView.h"
56#include "widgets/AButton.h"
57#include "AudacityMessageBox.h"
58#include "widgets/Grabber.h"
60#include "widgets/BeatsFormat.h"
61#include "widgets/TimeFormat.h"
63
64#include <wx/dcclient.h>
65#include <wx/menu.h>
66
67using std::min;
68using std::max;
69
70//#define SCRUB_ABOVE
71
72#define SELECT_TOLERANCE_PIXEL 4
73
74#define PLAY_REGION_TRIANGLE_SIZE 6
75#define PLAY_REGION_RECT_WIDTH 1
76#define PLAY_REGION_RECT_HEIGHT 3
77#define PLAY_REGION_GLOBAL_OFFSET_Y 7
78
79enum : int {
83
85 BottomMargin = 2, // for bottom bevel and bottom line
87
89};
90
91enum {
94};
95
96inline int IndicatorHeightForWidth(int width)
97{
98 return ((width / 2) * 3) / 2;
99}
100
101inline int IndicatorWidthForHeight(int height)
102{
103 // Not an exact inverse of the above, with rounding, but good enough
104 return std::max(static_cast<int>(IndicatorSmallWidth),
105 (((height) * 2) / 3) * 2
106 );
107}
108
110{
111 return std::max((int)(ScrubHeight - TopMargin),
112 (int)(IndicatorMediumWidth));
113}
114
116{
118}
119
121{
122public:
124 AdornedRulerPanel *pParent, wxCoord xx, MenuChoice menuChoice )
125 : mParent(pParent)
126 , mX( xx )
127 , mClickedX( xx )
128 , mChoice( menuChoice )
129 {}
130
131 bool Clicked() const { return mClicked != Button::None; }
132
134 const CommonRulerHandle &oldState, const CommonRulerHandle &newState)
135 {
136 if (oldState.mX != newState.mX)
138 return 0;
139 }
140
141protected:
142 bool HandlesRightClick() override { return true; }
143
145 const TrackPanelMouseEvent &event, AudacityProject *) override
146 {
147 mClicked = event.event.LeftIsDown() ? Button::Left : Button::Right;
148 mClickedX = event.event.GetX();
150 }
151
153 const TrackPanelMouseEvent &, AudacityProject *) override
154 {
156 }
157
160 wxWindow *) override
161 {
162 if ( mParent && mClicked == Button::Right ) {
163 const auto pos = event.event.GetPosition();
164 mParent->ShowContextMenu( mChoice, &pos );
165 }
167 }
168
169 double Time(AudacityProject &project) const
170 {
171 auto &viewInfo = ViewInfo::Get(project);
172 return viewInfo.PositionToTime(mX, viewInfo.GetLeftOffset());
173 }
174
175 // Danger! `this` may be deleted!
176 void StartPlay(AudacityProject &project, const wxMouseEvent &event)
177 {
178 auto &ruler = AdornedRulerPanel::Get(project);
179
180 // Keep a shared pointer to self. Otherwise *this might get deleted
181 // in HandleQPRelease on Windows! Because there is an event-loop yield
182 // stopping playback, which caused OnCaptureLost to be called, which caused
183 // clearing of CellularPanel targets!
184 auto saveMe = ruler.Target();
185
186 const auto startTime = Time(project);
187 ruler.StartQPPlay(!event.ShiftDown(), false, &startTime);
188 }
189
191 {
193 }
194
195 void Enter(bool, AudacityProject *) override
196 {
198 }
199
200 wxWeakRef<AdornedRulerPanel> mParent;
201
202 wxCoord mX;
203 wxCoord mClickedX;
204
206
207 enum class Button { None, Left, Right };
209};
210
212public:
214 AdornedRulerPanel *pParent, wxCoord xx, MenuChoice menuChoice,
215 wxCursor cursor,
216 size_t numGuides = 1)
217 : CommonRulerHandle{ pParent, xx, menuChoice }
218 , mCursor{ cursor }
219 , mNumGuides{ numGuides }
220 {}
221
223 const TrackPanelMouseState &state, AudacityProject *pProject)
224 override
225 {
226 mParent->SetNumGuides(mNumGuides);
227 const auto message = XO("Click and drag to define a looping region.");
228 return {
229 message,
230 &mCursor,
231 message
232 };
233 }
234
236 const TrackPanelMouseEvent &event, AudacityProject *pProject) override
237 {
238 using namespace RefreshCode;
239 auto result = CommonRulerHandle::Drag(event, pProject);
240 if (0 != (result & Cancelled) || mClicked != Button::Left)
241 return result;
242
243 auto &ruler = AdornedRulerPanel::Get(*pProject);
244 mX = event.event.m_x;
245 ruler.UpdateQuickPlayPos(event.event.m_x);
246
247 if (!mDragged) {
248 if (fabs(mX - mClickedX) < SELECT_TOLERANCE_PIXEL)
249 // Don't start a drag yet for a small mouse movement
250 return RefreshNone;
251 SavePlayRegion(*pProject);
252 const auto time = SnappedTime(*pProject, 0);
253 DoStartAdjust(*pProject, time);
254 mDragged = true;
255 }
256 else
257 DoAdjust(*pProject);
258
260 DragSelection(*pProject);
261
262 return RefreshAll;
263 }
264
266 const TrackPanelMouseEvent &event,
267 AudacityProject *pProject, wxWindow *pParent)
268 override
269 {
270 using namespace RefreshCode;
271 auto result = CommonRulerHandle::Release(event, pProject, pParent);
272
273 if (mClicked == Button::Left && !mDragged)
274 StartPlay(*pProject, event.event);
275
276 if (!mDragged || 0 != (result & Cancelled))
277 return result;
278
279 // Set the play region endpoints correctly even if not strictly needed
280 auto &viewInfo = ViewInfo::Get( *pProject );
281 auto &playRegion = viewInfo.playRegion;
282 playRegion.Order();
283
284 return result;
285 }
286
287 Result Cancel(AudacityProject *pProject) override
288 {
289 using namespace RefreshCode;
290 auto result = CommonRulerHandle::Cancel(pProject);
291 if (!mSaved)
292 return result;
293
295 auto &viewInfo = ViewInfo::Get(*pProject);
296 viewInfo.selectedRegion = mOldSelectedRegion;
297 auto &playRegion = viewInfo.playRegion;
298 playRegion.SetTimes(mOldStart, mOldEnd);
299 if (!mWasActive)
300 playRegion.SetActive(false);
301
302 return RefreshAll;
303 }
304
305 // Compare with class SelectHandle. Perhaps a common base class for that
306 // class too should be defined.
307 bool HasEscape(AudacityProject *pProject) const override
308 {
309 auto &ruler = AdornedRulerPanel::Get(*pProject);
310 auto values = ruler.mIsSnapped;
311 auto identity = [](auto x){ return x; }; // in the C++20 standard...
312 return std::any_of( values, values + ruler.mNumGuides, identity );
313 }
314
315 bool Escape(AudacityProject *pProject) override
316 {
317 if (HasEscape(pProject)) {
318 Unsnap(false, pProject);
319 return true;
320 }
321 return false;
322 }
323
324protected:
325
326 double SnappedTime( AudacityProject &project, size_t ii )
327 {
328 const auto &ruler = AdornedRulerPanel::Get(project);
329 bool isSnapped = ruler.mIsSnapped[ii];
330 return isSnapped
331 ? ruler.mQuickPlayPos[ii]
332 : ruler.mQuickPlayPosUnsnapped[ii];
333 }
334
335 std::pair<double, double> SnappedTimes( AudacityProject &project )
336 {
337 const auto &ruler = AdornedRulerPanel::Get(project);
338 for (size_t ii = 0; ii < ruler.mNumGuides; ++ii)
339 if (ruler.mIsSnapped[ii]) {
340 double time0 = ruler.mQuickPlayPos[ii];
341 double delta = time0 - ruler.mQuickPlayPosUnsnapped[ii];
342 double time1 = ruler.mQuickPlayPosUnsnapped[1 - ii] + delta;
343 if (ii == 1)
344 return { time1, time0 };
345 else
346 return { time0, time1 };
347 }
348 // No snap
349 return { ruler.mQuickPlayPos[0], ruler.mQuickPlayPos[1] };
350 }
351
352 void Unsnap(bool use, AudacityProject *pProject)
353 {
354 auto &ruler = AdornedRulerPanel::Get(*pProject);
355 std::fill( ruler.mIsSnapped, ruler.mIsSnapped + ruler.mNumGuides, false );
356 // Repaint to turn the snap lines on or off
358 if (Clicked())
359 DoAdjust(*pProject);
360 }
361
362 virtual void DoStartAdjust(AudacityProject &, double) = 0;
363 virtual void DoAdjust(AudacityProject &) = 0;
364
366 {
367 auto &viewInfo = ViewInfo::Get(project);
368 mOldSelectedRegion = viewInfo.selectedRegion;
369 auto &playRegion = viewInfo.playRegion;
370 mWasActive = playRegion.Active();
371 mOldStart = playRegion.GetLastActiveStart();
372 mOldEnd = playRegion.GetLastActiveEnd();
373 if (!mWasActive)
374 playRegion.SetActive(true);
375 mSaved = true;
376 }
377
378private:
379 // wxCursor is a cheaply copied reference-counting handle to a resource
380 wxCursor mCursor;
382
384 double mOldStart = 0.0, mOldEnd = 0.0;
385 bool mWasActive = false;
386 bool mSaved = false;
387 bool mDragged = false;
388};
389
390/**********************************************************************
391
392ScrubbingRulerOverlay.
393Graphical helper for AdornedRulerPanel.
394
395**********************************************************************/
396
398
399// This is an overlay drawn on the ruler.
401{
402public:
404
407
408 bool mNewScrub {};
409 bool mNewSeek {};
410
411 void Update();
412
413private:
415
416 unsigned SequenceNumber() const override;
417
418 std::pair<wxRect, bool> DoGetRectangle(wxSize size) override;
419 void Draw(OverlayPanel &panel, wxDC &dc) override;
420
422
423 // Used by this only
425 bool mOldScrub {};
426 bool mOldSeek {};
427};
428
429/**********************************************************************
430
431 TrackPanelGuidelineOverlay.
432 Updated for mouse events in AdornedRulerPanel, but draws on the TrackPanel.
433
434 **********************************************************************/
435
436// This is an overlay drawn on a different window, the track panel.
437// It draws the pale guide line that follows mouse movement.
439{
442
443public:
445
446private:
447 void Update();
448
449 unsigned SequenceNumber() const override;
450 std::pair<wxRect, bool> DoGetRectangle(wxSize size) override;
451 void Draw(OverlayPanel &panel, wxDC &dc) override;
452
454
455 std::shared_ptr<ScrubbingRulerOverlay> mPartner
456 { std::make_shared<ScrubbingRulerOverlay>(*this) };
457
459
463};
464
465/**********************************************************************
466
467 Implementation of ScrubbingRulerOverlay.
468
469 **********************************************************************/
470
473: mPartner(partner)
474{
475}
476
478{
479 return &Get( *mPartner.mProject );
480}
481
483{
484 const auto project = mPartner.mProject;
485 auto &scrubber = Scrubber::Get( *project );
486 auto ruler = GetRuler();
487
488 bool scrubbing = (scrubber.IsScrubbing()
489 && !scrubber.IsSpeedPlaying()
490 && !scrubber.IsKeyboardScrubbing());
491
492 // Hide during transport, or if mouse is not in the ruler, unless scrubbing
493 if ((!ruler->LastCell() || ProjectAudioIO::Get( *project ).IsAudioActive())
494 && !scrubbing)
495 mNewQPIndicatorPos = -1;
496 else {
497 const auto &selectedRegion = ViewInfo::Get( *project ).selectedRegion;
498 double latestEnd =
499 std::max(ruler->mTracks->GetEndTime(), selectedRegion.t1());
500 // This will determine the x coordinate of the line and of the
501 // ruler indicator
502
503 // Test all snap points
504 mNewIndicatorSnapped = -1;
505 for (size_t ii = 0;
506 mNewIndicatorSnapped == -1 && ii < ruler->mNumGuides; ++ii) {
507 if (ruler->mIsSnapped[ii]) {
508 mNewIndicatorSnapped = ii;
509 }
510 }
511 mNewQPIndicatorPos = ruler->Time2Pos(
512 ruler->mQuickPlayPos[std::max(0, mNewIndicatorSnapped)]);
513
514 // These determine which shape is drawn on the ruler, and whether
515 // in the scrub or the qp zone
516 mNewScrub =
517 !ruler->IsMouseCaptured() && // not doing some other drag in the ruler
518 (ruler->LastCell() == ruler->mScrubbingCell ||
519 (scrubber.HasMark()));
520 mNewSeek = mNewScrub &&
521 (scrubber.Seeks() || scrubber.TemporarilySeeks());
522 }
523}
524
525unsigned
527{
528 return 30;
529}
530
531std::pair<wxRect, bool>
533{
534 Update();
535
536 const auto x = mOldQPIndicatorPos;
537 if (x >= 0) {
538 // These dimensions are always sufficient, even if a little
539 // excessive for the small triangle:
540 const int width = IndicatorBigWidth() * 3 / 2;
541 //const auto height = IndicatorHeightForWidth(width);
542
543 const int indsize = width / 2;
544
545 auto xx = x - indsize;
546 auto yy = 0;
547 return {
548 { xx, yy,
549 indsize * 2 + 1,
550 GetRuler()->GetSize().GetHeight() },
551 (x != mNewQPIndicatorPos
552 || (mOldScrub != mNewScrub)
553 || (mOldSeek != mNewSeek) )
554 };
555 }
556 else
557 return { {}, mNewQPIndicatorPos >= 0 };
558}
559
561 OverlayPanel & /*panel*/, wxDC &dc)
562{
563 mOldQPIndicatorPos = mNewQPIndicatorPos;
564 mOldScrub = mNewScrub;
565 mOldSeek = mNewSeek;
566 if (mOldQPIndicatorPos >= 0) {
567 auto ruler = GetRuler();
568 auto width = mOldScrub ? IndicatorBigWidth() : IndicatorSmallWidth;
569 ruler->DoDrawScrubIndicator(
570 &dc, mOldQPIndicatorPos, width, mOldScrub, mOldSeek);
571 }
572}
573
574/**********************************************************************
575
576 Implementation of TrackPanelGuidelineOverlay.
577
578 **********************************************************************/
579
581 AudacityProject *project)
582 : mProject(project)
583{
584}
585
586unsigned
588{
589 return 30;
590}
591
593{
594 const auto project = mProject;
595 auto &scrubber = Scrubber::Get( *project );
596 const auto ruler = &Get( *project );
597
598 // Determine the color of the line stroked over
599 // the track panel, green for scrub or yellow for snapped or white
600 mNewPreviewingScrub =
601 ruler->LastCell() == ruler->mScrubbingCell &&
602 !scrubber.IsScrubbing();
603}
604
605std::pair<wxRect, bool>
607{
608 Update();
609
610 wxRect rect(mOldQPIndicatorPos, 0, 1, size.GetHeight());
611 return std::make_pair(
612 rect,
613 (mOldQPIndicatorPos != mPartner->mNewQPIndicatorPos ||
614 mOldIndicatorSnapped != mPartner->mNewIndicatorSnapped ||
615 mOldPreviewingScrub != mNewPreviewingScrub)
616 );
617}
618
620 OverlayPanel &panel, wxDC &dc)
621{
622 mOldQPIndicatorPos = mPartner->mNewQPIndicatorPos;
623 mOldIndicatorSnapped = mPartner->mNewIndicatorSnapped;
624 mOldPreviewingScrub = mNewPreviewingScrub;
625
626 if (mOldQPIndicatorPos >= 0) {
627 if (!mOldPreviewingScrub && mOldIndicatorSnapped < 0) {
629 if (auto pHandle =
630 dynamic_cast<PlayRegionAdjustingHandle*>(ruler.Target().get());
631 pHandle != nullptr && pHandle->Clicked())
632 // Do not draw the quick-play guideline
633 return;
634 }
635
636 mOldPreviewingScrub
637 ? AColor::IndicatorColor(&dc, true) // Draw green line for preview.
638 : (mOldIndicatorSnapped >= 0)
639 ? AColor::SnapGuidePen(&dc) // Yellow snap guideline
640 : AColor::Light(&dc, false);
641
642 // Draw indicator in all visible tracks
643 auto pCellularPanel = dynamic_cast<CellularPanel*>( &panel );
644 if ( !pCellularPanel ) {
645 wxASSERT( false );
646 return;
647 }
648 pCellularPanel
649 ->VisitCells( [&]( const wxRect &rect, TrackPanelCell &cell ) {
650 const auto pTrackView = dynamic_cast<TrackView*>(&cell);
651 if (!pTrackView)
652 return;
653
654 // Draw the NEW indicator in its NEW location
655 AColor::Line(dc,
656 mOldQPIndicatorPos,
657 rect.GetTop(),
658 mOldQPIndicatorPos,
659 rect.GetBottom());
660 } );
661 }
662}
663
664/**********************************************************************
665
666 Implementation of AdornedRulerPanel.
667 Either we find a way to make this more generic, Or it will move
668 out of the widgets subdirectory into its own source file.
669
670**********************************************************************/
671
672enum {
681};
682
683BEGIN_EVENT_TABLE(AdornedRulerPanel, CellularPanel)
684 EVT_IDLE( AdornedRulerPanel::OnIdle )
687 EVT_LEAVE_WINDOW(AdornedRulerPanel::OnLeave)
688
689 // Context menu commands
699
703
705
707{
708public:
709 explicit
711 : mParent{ parent }
712 , mMenuChoice{ menuChoice }
713 {}
714
716 const TrackPanelMouseState &, const AudacityProject *)
717 override
718 {
719 // May come here when recording is in progress, so hit tests are turned
720 // off.
721 TranslatableString tooltip;
722 if (mParent->mTimelineToolTip)
723 tooltip = XO("Timeline actions disabled during recording");
724
725 static wxCursor cursor{ wxCURSOR_DEFAULT };
726 return {
727 {},
728 &cursor,
729 tooltip,
730 };
731 }
732
734 const wxRect &,
735 wxWindow *, const wxPoint *pPosition, AudacityProject*) final
736 {
737 mParent->ShowContextMenu(mMenuChoice, pPosition);
738 return 0;
739 }
740
741protected:
744};
745
746#undef QUICK_PLAY_HANDLE
747#ifdef QUICK_PLAY_HANDLE
748class AdornedRulerPanel::QPHandle final : public CommonRulerHandle
749{
750public:
751 explicit
752 QPHandle( AdornedRulerPanel *pParent, wxCoord xx )
754 {
755 }
756
757private:
759 const TrackPanelMouseEvent &event, AudacityProject *pProject) override;
760
761 Result Drag(
762 const TrackPanelMouseEvent &event, AudacityProject *pProject) override;
763
765 const TrackPanelMouseState &state, AudacityProject *pProject)
766 override;
767
769 const TrackPanelMouseEvent &event, AudacityProject *pProject,
770 wxWindow *pParent) override;
771
772 Result Cancel(AudacityProject *pProject) override;
773
774 SelectedRegion mOldSelection;
775};
776#endif
777
778static auto handOpenCursor =
779 MakeCursor(wxCURSOR_HAND, RearrangeCursorXpm, 16, 16);
780
782public:
784 : PlayRegionAdjustingHandle( pParent, xx, MenuChoice::QuickPlay, *handOpenCursor, 2 )
785 {
786 }
787
789 {
790 mParent->mQuickPlayOffset[0] = 0;
791 mParent->mQuickPlayOffset[1] = 0;
792 }
793
794private:
795 void DoStartAdjust(AudacityProject &project, double) override
796 {
797 // Ignore the snapping of the time at the mouse
798 const auto time = Time(project);
799 auto &playRegion = ViewInfo::Get(project).playRegion;
800 mParent->mQuickPlayOffset[0] = playRegion.GetStart() - time;
801 mParent->mQuickPlayOffset[1] = playRegion.GetEnd() - time;
802 }
803
804 void DoAdjust(AudacityProject &project) override
805 {
806 // Move the whole play region rigidly (usually)
807 // though the length might change slightly if only one edge snaps
808 const auto times = SnappedTimes(project);
809
810 auto &playRegion = ViewInfo::Get(project).playRegion;
811 playRegion.SetTimes(times.first, times.second);
812 }
813};
814
816public:
818 AdornedRulerPanel *pParent, wxCoord xx, bool hitLeft )
819 : PlayRegionAdjustingHandle( pParent, xx, MenuChoice::QuickPlay, {wxCURSOR_SIZEWE} )
820 , mHitLeft{ hitLeft }
821 {
822 }
823
824private:
825 void DoStartAdjust(AudacityProject &project, double time) override
826 {
827 }
828
829 void DoAdjust(AudacityProject &project) override
830 {
831 const auto time = SnappedTime(project, 0);
832
833 // Change the play region
834 // The check whether this new time should be start or end isn't
835 // important. The accessors for PlayRegion use min and max of stored
836 // values.
837 auto &playRegion = ViewInfo::Get(project).playRegion;
838 if (mHitLeft)
839 playRegion.SetStart(time);
840 else
841 playRegion.SetEnd(time);
842 }
843
844 bool mHitLeft = false;
845};
846
848public:
850 : PlayRegionAdjustingHandle( pParent, xx, MenuChoice::QuickPlay, {wxCURSOR_DEFAULT} )
851 {
852 }
853
854private:
855 void DoStartAdjust(AudacityProject &project, double time) override
856 {
857 auto &playRegion = ViewInfo::Get(project).playRegion;
858 playRegion.SetTimes(time, time);
859 }
860
861 void DoAdjust(AudacityProject &project) override
862 {
863 const auto time = SnappedTime(project, 0);
864
865 // Change the play region
866 // The check whether this new time should be start or end isn't
867 // important. The accessors for PlayRegion use min and max of stored
868 // values.
869 auto &playRegion = ViewInfo::Get(project).playRegion;
870 playRegion.SetEnd(time);
871 }
872};
873
874namespace
875{
876
877wxCoord GetPlayHeadX( const AudacityProject *pProject )
878{
879 const auto &viewInfo = ViewInfo::Get( *pProject );
880 auto width = viewInfo.GetTracksUsableWidth();
881 return viewInfo.GetLeftOffset()
883}
884
885double GetPlayHeadFraction( const AudacityProject *pProject, wxCoord xx )
886{
887 const auto &viewInfo = ViewInfo::Get( *pProject );
888 auto width = viewInfo.GetTracksUsableWidth();
889 auto fraction = (xx - viewInfo.GetLeftOffset()) / double(width);
890 return std::max(0.0, std::min(1.0, fraction));
891}
892
893// Handle for dragging the pinned play head, which so far does not need
894// to be a friend of the AdornedRulerPanel class, so we don't make it nested.
896{
897public:
898 PlayheadHandle( AdornedRulerPanel &parent, wxCoord xx )
899 : mpParent{ &parent }
900 , mX( xx )
901 {}
902
904 const PlayheadHandle &oldState, const PlayheadHandle &newState)
905 {
906 if (oldState.mX != newState.mX)
908 return 0;
909 }
910
911 static std::shared_ptr<PlayheadHandle>
913 const AudacityProject *pProject, AdornedRulerPanel &parent, wxCoord xx )
914 {
915 if( Scrubber::Get( *pProject )
916 .IsTransportingPinned() &&
917 ProjectAudioIO::Get( *pProject ).IsAudioActive() )
918 {
919 const auto targetX = GetPlayHeadX( pProject );
920 if ( abs( xx - targetX ) <= SELECT_TOLERANCE_PIXEL )
921 return std::make_shared<PlayheadHandle>( parent, xx );
922 }
923 return {};
924 }
925
926protected:
928 const TrackPanelMouseEvent &event, AudacityProject *) override
929 {
930 if (event.event.LeftDClick()) {
931 // Restore default position on double click
933
935 // Do not start a drag
937 }
938 // Fix for Bug 2357
939 if (!event.event.LeftIsDown())
941
943 return 0;
944 }
945
947 const TrackPanelMouseEvent &event, AudacityProject *pProject) override
948 {
949
950 auto value = GetPlayHeadFraction(pProject, event.event.m_x );
953 }
954
957 override
958 {
959 mpParent->SetNumGuides(1);
960 static wxCursor cursor{ wxCURSOR_SIZEWE };
961 return {
962 XO( "Click and drag to adjust, double-click to reset" ),
963 &cursor,
964 /* i18n-hint: This text is a tooltip on the icon (of a pin) representing
965 the temporal position in the audio. */
966 XO( "Record/Play head" )
967 };
968 }
969
971 const TrackPanelMouseEvent &event, AudacityProject *pProject,
972 wxWindow *) override
973 {
974 auto value = GetPlayHeadFraction(pProject, event.event.m_x );
977 }
978
980 {
983 }
984
985 void Enter(bool, AudacityProject *) override
986 {
987 mChangeHighlight = RefreshCode::DrawOverlays;
988 }
989
991 wxCoord mX;
992 double mOrigPreference {};
993};
994
995}
996
998{
999public:
1000 explicit
1002 : AdornedRulerPanel::CommonCell{ parent, MenuChoice::QuickPlay }
1003 {}
1004
1005 std::vector<UIHandlePtr> HitTest(
1006 const TrackPanelMouseState &state,
1007 const AudacityProject *pProject) override;
1008
1009 // Return shared_ptr to self, stored in parent
1010 std::shared_ptr<TrackPanelCell> ContextMenuDelegate() override
1011 { return mParent->mQPCell; }
1012
1013 bool Clicked() const {
1014#ifdef QUICK_PLAY_HANDLE
1015 if (auto ptr = mHolder.lock())
1016 return ptr->Clicked();
1017#endif
1018 return false;
1019 }
1020
1021#ifdef QUICK_PLAY_HANDLE
1022 std::weak_ptr<QPHandle> mHolder;
1023#endif
1024
1025 std::weak_ptr<ResizePlayRegionHandle> mResizePlayRegionHolder;
1026 std::weak_ptr<MovePlayRegionHandle> mMovePlayRegionHolder;
1027 std::weak_ptr<NewPlayRegionHandle> mNewPlayRegionHolder;
1028 std::weak_ptr<PlayheadHandle> mPlayheadHolder;
1029};
1030
1031std::vector<UIHandlePtr> AdornedRulerPanel::QPCell::HitTest(
1032 const TrackPanelMouseState &state,
1033 const AudacityProject *pProject)
1034{
1035 // Creation of overlays on demand here -- constructor of AdornedRulerPanel
1036 // is too early to do it
1037 mParent->CreateOverlays();
1038
1039 std::vector<UIHandlePtr> results;
1040 auto xx = state.state.m_x;
1041
1042#ifdef EXPERIMENTAL_DRAGGABLE_PLAY_HEAD
1043 {
1044 // Allow click and drag on the play head even while recording
1045 // Make this handle more prominent then the quick play handle
1046 auto result = PlayheadHandle::HitTest( pProject, *mParent, xx );
1047 if (result) {
1048 result = AssignUIHandlePtr( mPlayheadHolder, result );
1049 results.push_back( result );
1050 }
1051 }
1052#endif
1053
1054 // Disable mouse actions on Timeline while recording.
1055 if (!mParent->mIsRecording) {
1056 mParent->UpdateQuickPlayPos( xx );
1057
1058 #if 0
1059 auto result = std::make_shared<QPHandle>( mParent, xx );
1060 result = AssignUIHandlePtr( mHolder, result );
1061 results.push_back( result );
1062 #endif
1063 }
1064
1065 // High priority hit is a handle to change the existing play region
1066 bool hitLeft = false;
1067 const auto &playRegion = ViewInfo::Get(*pProject).playRegion;
1068 if ((hitLeft =
1069 mParent->IsWithinMarker(xx, playRegion.GetLastActiveStart())) ||
1070 mParent->IsWithinMarker(xx, playRegion.GetLastActiveEnd()))
1071 {
1072 auto result =
1073 std::make_shared<ResizePlayRegionHandle>( mParent, xx, hitLeft );
1074 result = AssignUIHandlePtr( mResizePlayRegionHolder, result );
1075 results.push_back(result);
1076 }
1077
1078 // Middle priority hit is a handle to change the existing play region at
1079 // both ends, but only when the play region is active
1080 if (auto time = mParent->Pos2Time(xx);
1081 playRegion.Active() &&
1082 time >= playRegion.GetStart() &&
1083 time <= playRegion.GetEnd())
1084 {
1085 auto result =
1086 std::make_shared<MovePlayRegionHandle>( mParent, xx );
1087 result = AssignUIHandlePtr( mMovePlayRegionHolder, result );
1088 results.push_back(result);
1089 }
1090
1091 // Lowest priority hit is a handle to drag a completely new play region
1092 {
1093 auto result = std::make_shared<NewPlayRegionHandle>( mParent, xx );
1094 result = AssignUIHandlePtr( mNewPlayRegionHolder, result );
1095 results.push_back(result);
1096 }
1097
1098 return results;
1099}
1100
1102{
1103public:
1104 explicit
1105 ScrubbingHandle( AdornedRulerPanel *pParent, wxCoord xx )
1106 : CommonRulerHandle( pParent, xx, MenuChoice::Scrub )
1107 {
1108 }
1109
1110private:
1112 const TrackPanelMouseEvent &event, AudacityProject *pProject) override
1113 {
1114 auto result = CommonRulerHandle::Click(event, pProject);
1115 if (!( result & RefreshCode::Cancelled )) {
1116 if (mClicked == Button::Left) {
1117 auto &scrubber = Scrubber::Get( *pProject );
1118 // only if scrubbing is allowed now
1119 bool canScrub =
1120 scrubber.CanScrub() &&
1121 mParent &&
1122 mParent->ShowingScrubRuler();
1123
1124 if (!canScrub)
1126 if (!scrubber.HasMark()) {
1127 // Asynchronous scrub poller gets activated here
1128 scrubber.MarkScrubStart(
1129 event.event.m_x, Scrubber::ShouldScrubPinned(), false);
1130 }
1131 }
1132 }
1133 return result;
1134 }
1135
1137 const TrackPanelMouseEvent &event, AudacityProject *pProject) override
1138 {
1139 auto result = CommonRulerHandle::Drag(event, pProject);
1140 if (!( result & RefreshCode::Cancelled )) {
1141 // Nothing needed here. The scrubber works by polling mouse state
1142 // after the start has been marked.
1143 }
1144 return result;
1145 }
1146
1148 const TrackPanelMouseState &state, AudacityProject *pProject)
1149 override;
1150
1152 const TrackPanelMouseEvent &event, AudacityProject *pProject,
1153 wxWindow *pParent) override {
1154 auto result = CommonRulerHandle::Release(event, pProject, pParent);
1155 if (!( result & RefreshCode::Cancelled )) {
1156 // Nothing needed here either. The scrub poller may have decided to
1157 // seek because a drag happened before button up, or it may decide
1158 // to start a scrub, as it watches mouse movement after the button up.
1159 }
1160 return result;
1161 }
1162
1163 Result Cancel(AudacityProject *pProject) override
1164 {
1165 auto result = CommonRulerHandle::Cancel(pProject);
1166
1167 if (mClicked == Button::Left) {
1168 auto &scrubber = Scrubber::Get( *pProject );
1169 scrubber.Cancel();
1170
1171 ProjectAudioManager::Get( *pProject ).Stop();
1172 }
1173
1174 return result;
1175 }
1176};
1177
1179{
1180public:
1181 explicit
1183 : AdornedRulerPanel::CommonCell{ parent, MenuChoice::Scrub }
1184 {}
1185
1186 std::vector<UIHandlePtr> HitTest(
1187 const TrackPanelMouseState &state,
1188 const AudacityProject *pProject) override;
1189
1190 // Return shared_ptr to self, stored in parent
1191 std::shared_ptr<TrackPanelCell> ContextMenuDelegate() override
1192 { return mParent->mScrubbingCell; }
1193
1194 bool Hit() const { return !mHolder.expired(); }
1195 bool Clicked() const {
1196 if (auto ptr = mHolder.lock())
1197 return ptr->Clicked();
1198 return false;
1199 }
1200
1201private:
1202 std::weak_ptr<ScrubbingHandle> mHolder;
1203};
1204
1206 const TrackPanelMouseState &state, const AudacityProject *)
1207{
1208 // Creation of overlays on demand here -- constructor of AdornedRulerPanel
1209 // is too early to do it
1210 mParent->CreateOverlays();
1211
1212 std::vector<UIHandlePtr> results;
1213
1214 // Disable mouse actions on Timeline while recording.
1215 if (!mParent->mIsRecording) {
1216 auto xx = state.state.m_x;
1217 mParent->UpdateQuickPlayPos( xx );
1218 auto result = std::make_shared<ScrubbingHandle>( mParent, xx );
1219 result = AssignUIHandlePtr( mHolder, result );
1220 results.push_back( result );
1221 }
1222
1223 return results;
1224}
1225
1226namespace{
1227AttachedWindows::RegisteredFactory sKey{
1228[]( AudacityProject &project ) -> wxWeakRef< wxWindow > {
1229 auto &viewInfo = ViewInfo::Get( project );
1230 auto &window = ProjectWindow::Get( project );
1231
1232 return safenew AdornedRulerPanel( &project, window.GetTrackListWindow(),
1233 wxID_ANY,
1234 wxDefaultPosition,
1235 wxSize( -1, AdornedRulerPanel::GetRulerHeight(false) ),
1236 &viewInfo );
1237}
1238};
1239}
1240
1242{
1243 return GetAttachedWindows(project).Get< AdornedRulerPanel >( sKey );
1244}
1245
1247 const AudacityProject &project )
1248{
1249 return Get( const_cast< AudacityProject & >( project ) );
1250}
1251
1253{
1254 auto *pPanel = GetAttachedWindows(project).Find( sKey );
1255 if (pPanel) {
1256 pPanel->wxWindow::Destroy();
1257 GetAttachedWindows(project).Assign( sKey, nullptr );
1258 }
1259}
1260
1262 wxWindow *parent,
1263 wxWindowID id,
1264 const wxPoint& pos,
1265 const wxSize& size,
1266 ViewInfo *viewinfo
1267) : CellularPanel(parent, id, pos, size, viewinfo)
1268 , mProject(project)
1269{
1270 SetLayoutDirection(wxLayout_LeftToRight);
1271
1272 mQPCell = std::make_shared<QPCell>( this );
1273 mScrubbingCell = std::make_shared<ScrubbingCell>( this );
1274
1275 for (auto &button : mButtons)
1276 button = nullptr;
1277
1278 SetLabel( XO("Timeline") );
1279 SetName();
1280 SetBackgroundStyle(wxBG_STYLE_PAINT);
1281
1282 mLeftOffset = 0;
1283 mIndTime = -1;
1284
1285 mLeftDownClick = -1;
1287 mIsDragging = false;
1288
1289 mOuter = GetClientRect();
1290
1291 mBeatsAndMeasures = false;
1292
1294
1295 mRuler.SetLabelEdges( false );
1296
1297 mTracks = &TrackList::Get( *project );
1298
1299 mIsRecording = false;
1300
1301 mTimelineToolTip = !!gPrefs->Read(wxT("/QuickPlay/ToolTips"), 1L);
1302 mPlayRegionDragsSelection = (gPrefs->Read(wxT("/QuickPlay/DragSelection"), 0L) == 1)? true : false;
1303
1304#if wxUSE_TOOLTIPS
1305 wxToolTip::Enable(true);
1306#endif
1307
1310
1311 // Delay until after CommandManager has been populated:
1313
1316
1317 // Bind event that updates the play region
1320
1321 // And call it once to initialize it
1323}
1324
1326{
1327}
1328
1329void AdornedRulerPanel::Refresh( bool eraseBackground, const wxRect *rect )
1330{
1331 CellularPanel::Refresh( eraseBackground, rect );
1333}
1334
1336{
1337 if (mNeedButtonUpdate) {
1338 // Visit this block once only in the lifetime of this panel
1339 mNeedButtonUpdate = false;
1340 // Do this first time setting of button status texts
1341 // when we are sure the CommandManager is initialized.
1343 }
1344
1345 // Update button texts for language change
1347
1348 mTimelineToolTip = !!gPrefs->Read(wxT("/QuickPlay/ToolTips"), 1L);
1349
1350#ifdef EXPERIMENTAL_SCROLLING_LIMITS
1351#ifdef EXPERIMENTAL_TWO_TONE_TIME_RULER
1352 {
1353 auto scrollBeyondZero = ScrollingPreference.Read();
1354 mRuler.SetTwoTone(scrollBeyondZero);
1355 }
1356#endif
1357#endif
1358
1360 // Update();
1361}
1362
1364{
1365 // TODO: Should we do this to destroy the grabber??
1366 // Get rid of any children we may have
1367 // DestroyChildren();
1368
1370 SetBackgroundColour(theTheme.Colour( clrMedium ));
1371
1372 for (auto & button : mButtons) {
1373 if (button)
1374 button->Destroy();
1375 button = nullptr;
1376 }
1377
1378 size_t iButton = 0;
1379 // Make the short row of time ruler pushbottons.
1380 // Don't bother with sizers. Their sizes and positions are fixed.
1381 // Add a grabber converted to a spacer.
1382 // This makes it visually clearer that the button is a button.
1383
1384 wxPoint position( 1, 0 );
1385
1386 Grabber * pGrabber = safenew Grabber(this, {});
1387 pGrabber->SetAsSpacer( true );
1388 //pGrabber->SetSize( 10, 27 ); // default is 10,27
1389 pGrabber->SetPosition( position );
1390
1391 position.x = 12;
1392
1393 auto size = theTheme.ImageSize( bmpRecoloredUpSmall );
1394 size.y = std::min(size.y, GetRulerHeight(false));
1395
1396 auto buttonMaker = [&]
1397 (wxWindowID id, teBmps bitmap, bool toggle)
1398 {
1399 const auto button =
1401 this,
1402 bmpRecoloredUpSmall, bmpRecoloredDownSmall,
1403 bmpRecoloredUpHiliteSmall, bmpRecoloredHiliteSmall,
1404 bitmap, bitmap, bitmap,
1405 id, position, toggle, size
1406 );
1407
1408 position.x += size.GetWidth();
1409 mButtons[iButton++] = button;
1410 return button;
1411 };
1412 auto button = buttonMaker(OnTogglePinnedStateID, bmpPlayPointerPinned, true);
1414 *button, 3,
1415 bmpRecoloredUpSmall, bmpRecoloredDownSmall,
1416 bmpRecoloredUpHiliteSmall, bmpRecoloredHiliteSmall,
1417 //bmpUnpinnedPlayHead, bmpUnpinnedPlayHead, bmpUnpinnedPlayHead,
1418 bmpRecordPointer, bmpRecordPointer, bmpRecordPointer,
1419 size);
1421 *button, 2,
1422 bmpRecoloredUpSmall, bmpRecoloredDownSmall,
1423 bmpRecoloredUpHiliteSmall, bmpRecoloredHiliteSmall,
1424 //bmpUnpinnedPlayHead, bmpUnpinnedPlayHead, bmpUnpinnedPlayHead,
1425 bmpRecordPointerPinned, bmpRecordPointerPinned, bmpRecordPointerPinned,
1426 size);
1428 *button, 1,
1429 bmpRecoloredUpSmall, bmpRecoloredDownSmall,
1430 bmpRecoloredUpHiliteSmall, bmpRecoloredHiliteSmall,
1431 //bmpUnpinnedPlayHead, bmpUnpinnedPlayHead, bmpUnpinnedPlayHead,
1432 bmpPlayPointer, bmpPlayPointer, bmpPlayPointer,
1433 size);
1434
1436}
1437
1439{
1441}
1442
1443namespace {
1445 {
1446#if 0
1447 if(scrubber.Seeks())
1448 /* i18n-hint: These commands assist the user in finding a sound by ear. ...
1449 "Scrubbing" is variable-speed playback, ...
1450 "Seeking" is normal speed playback but with skips
1451 */
1452 return XO("Click or drag to begin Seek");
1453 else
1454 /* i18n-hint: These commands assist the user in finding a sound by ear. ...
1455 "Scrubbing" is variable-speed playback, ...
1456 "Seeking" is normal speed playback but with skips
1457 */
1458 return XO("Click or drag to begin Scrub");
1459#else
1460 /* i18n-hint: These commands assist the user in finding a sound by ear. ...
1461 "Scrubbing" is variable-speed playback, ...
1462 "Seeking" is normal speed playback but with skips
1463 */
1464 return XO("Click & move to Scrub. Click & drag to Seek.");
1465#endif
1466 }
1467
1469 const Scrubber &scrubber, bool clicked)
1470 {
1471#if 0
1472 if(scrubber.Seeks())
1473 /* i18n-hint: These commands assist the user in finding a sound by ear. ...
1474 "Scrubbing" is variable-speed playback, ...
1475 "Seeking" is normal speed playback but with skips
1476 */
1477 return XO("Move to Seek");
1478 else
1479 /* i18n-hint: These commands assist the user in finding a sound by ear. ...
1480 "Scrubbing" is variable-speed playback, ...
1481 "Seeking" is normal speed playback but with skips
1482 */
1483 return XO("Move to Scrub");
1484#else
1485 if( clicked ) {
1486 // Since mouse is down, mention dragging first.
1487 // IsScrubbing is true if Scrubbing OR seeking.
1488 if( scrubber.IsScrubbing() )
1489 // User is dragging already, explain.
1490 return XO("Drag to Seek. Release to stop seeking.");
1491 else
1492 // User has clicked but not yet moved or released.
1493 return XO("Drag to Seek. Release and move to Scrub.");
1494 }
1495 // Since mouse is up, mention moving first.
1496 return XO("Move to Scrub. Drag to Seek.");
1497#endif
1498 }
1499
1500 const TranslatableString ScrubbingMessage(const Scrubber &scrubber, bool clicked)
1501 {
1502 if (scrubber.HasMark())
1503 return ContinueScrubbingMessage(scrubber, clicked);
1504 else
1505 return StartScrubbingMessage(scrubber);
1506 }
1507}
1508
1509void AdornedRulerPanel::OnIdle( wxIdleEvent &evt )
1510{
1511 evt.Skip();
1512 DoIdle();
1513}
1514
1516{
1517 bool changed = UpdateRects();
1518 changed = SetPanelSize() || changed;
1519
1520 auto &project = *mProject;
1521 auto &viewInfo = ViewInfo::Get( project );
1522 const auto &selectedRegion = viewInfo.selectedRegion;
1523 const auto &playRegion = viewInfo.playRegion;
1524
1525 changed = changed
1526 || mLastDrawnSelectedRegion != selectedRegion
1527 || mLastDrawnPlayRegion != std::pair{
1528 playRegion.GetLastActiveStart(), playRegion.GetLastActiveEnd() }
1529 || mLastDrawnH != viewInfo.h
1530 || mLastDrawnZoom != viewInfo.GetZoom()
1531 || mLastPlayRegionActive != viewInfo.playRegion.Active()
1532 ;
1533 if (changed)
1534 // Cause ruler redraw anyway, because we may be zooming or scrolling,
1535 // showing or hiding the scrub bar, etc.
1536 Refresh();
1537}
1538
1540{
1541 if (evt.type == AudioIOEvent::MONITOR)
1542 return;
1543 if ( evt.type == AudioIOEvent::CAPTURE ) {
1544 if (evt.on)
1545 {
1546 mIsRecording = true;
1547 this->CellularPanel::CancelDragging( false );
1549
1551 }
1552 else {
1553 mIsRecording = false;
1555 }
1556 }
1557
1558 if ( !evt.on )
1559 // So that the play region is updated
1561}
1562
1563void AdornedRulerPanel::OnPaint(wxPaintEvent & WXUNUSED(evt))
1564{
1565 const auto &viewInfo = ViewInfo::Get( *GetProject() );
1566 const auto &playRegion = viewInfo.playRegion;
1567 const auto playRegionBounds = std::pair{
1568 playRegion.GetLastActiveStart(), playRegion.GetLastActiveEnd() };
1569 mLastDrawnH = viewInfo.h;
1570 mLastDrawnZoom = viewInfo.GetZoom();
1571 mLastDrawnPlayRegion = playRegionBounds;
1572 mLastDrawnSelectedRegion = viewInfo.selectedRegion;
1573 // To do, note other fisheye state when we have that
1574
1575 wxPaintDC dc(this);
1576
1577 auto &backDC = GetBackingDCForRepaint();
1578
1579 DoDrawBackground(&backDC);
1580
1581 // Find play region rectangle, selected rectangle, and their overlap
1582 const auto rectP = PlayRegionRectangle(),
1583 rectS = SelectedRegionRectangle(),
1584 rectO = rectP.Intersect(rectS);
1585
1586 // What's left and right of the overlap? Assume same tops and bottoms
1587 const auto top = rectP.GetTop(),
1588 bottom = rectP.GetBottom();
1589 wxRect rectL{
1590 wxPoint{ 0, top }, wxPoint{ this->GetSize().GetWidth() - 1, bottom } };
1591 wxRect rectR = {};
1592 if (!rectO.IsEmpty()) {
1593 rectR = { wxPoint{ rectO.GetRight() + 1, top }, rectL.GetBottomRight() };
1594 rectL = { rectL.GetTopLeft(), wxPoint{ rectO.GetLeft() - 1, bottom } };
1595 }
1596
1597 DoDrawPlayRegion(&backDC, rectP, rectL, rectR);
1598 DoDrawOverlap(&backDC, rectO);
1599 DoDrawSelection(&backDC, rectS, rectL, rectR);
1600
1601 DoDrawPlayRegionLimits(&backDC, rectP);
1602
1603 DoDrawMarks(&backDC, true);
1604
1605 DoDrawEdge(&backDC);
1606
1607 DisplayBitmap(dc);
1608
1609 // Stroke extras direct to the client area,
1610 // maybe outside of the damaged area
1611 // As with TrackPanel, do not make a NEW wxClientDC or else Mac flashes badly!
1612 dc.DestroyClippingRegion();
1613 DrawOverlays(true, &dc);
1614}
1615
1616void AdornedRulerPanel::OnSize(wxSizeEvent &evt)
1617{
1618 mOuter = GetClientRect();
1619 if (mOuter.GetWidth() == 0 || mOuter.GetHeight() == 0)
1620 {
1621 return;
1622 }
1623
1624 UpdateRects();
1625
1627}
1628
1629void AdornedRulerPanel::OnLeave(wxMouseEvent& evt)
1630{
1631 evt.Skip();
1632 CallAfter([this]{
1634 });
1635}
1636
1638{
1639 if (message.appearance)
1640 return;
1642}
1643
1645{
1646 auto &selectedRegion = mViewInfo->selectedRegion;
1647 DoSelectionChange( selectedRegion );
1648}
1649
1651 const SelectedRegion &selectedRegion )
1652{
1653
1654 auto gAudioIO = AudioIOBase::Get();
1655 if ( !ViewInfo::Get( *mProject ).playRegion.Active() ) {
1656 // "Inactivated" play region follows the selection.
1657 SetPlayRegion( selectedRegion.t0(), selectedRegion.t1() );
1658 }
1659}
1660
1662{
1663 auto inner = mOuter;
1664 wxRect scrubZone;
1665 inner.x += LeftMargin;
1666 inner.width -= (LeftMargin + RightMargin);
1667
1668 auto top = &inner;
1669 auto bottom = &inner;
1670
1671 if (ShowingScrubRuler()) {
1672 scrubZone = inner;
1673 auto scrubHeight = std::min(scrubZone.height, (int)(ScrubHeight));
1674
1675 int topHeight;
1676#ifdef SCRUB_ABOVE
1677 top = &scrubZone, topHeight = scrubHeight;
1678#else
1679 auto qpHeight = scrubZone.height - scrubHeight;
1680 bottom = &scrubZone, topHeight = qpHeight;
1681 // Increase scrub zone height so that hit testing finds it and
1682 // not QP region, when on bottom 'edge'.
1683 scrubZone.height+=BottomMargin;
1684#endif
1685
1686 top->height = topHeight;
1687 bottom->height -= topHeight;
1688 bottom->y += topHeight;
1689 }
1690
1691 top->y += TopMargin;
1692 top->height -= TopMargin;
1693
1694 bottom->height -= BottomMargin;
1695
1696 if (!ShowingScrubRuler())
1697 scrubZone = inner;
1698
1699 if ( inner == mInner && scrubZone == mScrubZone )
1700 // no changes
1701 return false;
1702
1703 mInner = inner;
1704 mScrubZone = scrubZone;
1705
1706 mRuler.SetBounds(mInner.GetLeft(),
1707 mInner.GetTop(),
1708 mInner.GetRight(),
1709 mInner.GetBottom());
1710
1711 return true;
1712}
1713
1714double AdornedRulerPanel::Pos2Time(int p, bool ignoreFisheye) const
1715{
1717 , ignoreFisheye
1718 );
1719}
1720
1721int AdornedRulerPanel::Time2Pos(double t, bool ignoreFisheye) const
1722{
1724 , ignoreFisheye
1725 );
1726}
1727
1728bool AdornedRulerPanel::IsWithinMarker(int mousePosX, double markerTime)
1729{
1730 if (markerTime < 0)
1731 return false;
1732
1733 int pixelPos = Time2Pos(markerTime);
1734 int boundLeft = pixelPos - SELECT_TOLERANCE_PIXEL;
1735 int boundRight = pixelPos + SELECT_TOLERANCE_PIXEL;
1736
1737 return mousePosX >= boundLeft && mousePosX < boundRight;
1738}
1739
1740#ifdef QUICK_PLAY_HANDLE
1741auto AdornedRulerPanel::QPHandle::Click(
1742 const TrackPanelMouseEvent &event, AudacityProject *pProject) -> Result
1743{
1744 auto result = CommonRulerHandle::Click(event, pProject);
1745 if (!( result & RefreshCode::Cancelled )) {
1746 if (mClicked == Button::Left) {
1747 if (!mParent)
1749
1750 auto &scrubber = Scrubber::Get( *pProject );
1751 if(scrubber.HasMark()) {
1752 // We can't stop scrubbing yet (see comments in Bug 1391),
1753 // but we can pause it.
1754 ProjectAudioManager::Get( *pProject ).OnPause();
1755 }
1756
1757 // Store the initial play region state
1758 const auto &viewInfo = ViewInfo::Get( *pProject );
1759 const auto &playRegion = viewInfo.playRegion;
1760 mParent->mOldPlayRegion = playRegion;
1761
1762 // Save old selection, in case drag of selection is cancelled
1763 mOldSelection = ViewInfo::Get( *pProject ).selectedRegion;
1764
1765 mParent->HandleQPClick( event.event, mX );
1766 mParent->HandleQPDrag( event.event, mX );
1767 }
1768 }
1769
1770 return result;
1771}
1772
1773void AdornedRulerPanel::HandleQPClick(wxMouseEvent &evt, wxCoord mousePosX)
1774{
1775 // Temporarily inactivate play region
1776 if (mOldPlayRegion.Active() && evt.LeftDown()) {
1777 //mPlayRegionLock = true;
1779 }
1780
1783 bool isWithinStart = IsWithinMarker(mousePosX, mOldPlayRegion.GetStart());
1784 bool isWithinEnd = IsWithinMarker(mousePosX, mOldPlayRegion.GetEnd());
1785
1786 if (isWithinStart || isWithinEnd) {
1787 // If Quick-Play is playing from a point, we need to treat it as a click
1788 // not as dragging.
1789 if (mOldPlayRegion.Empty())
1791 // otherwise check which marker is nearer
1792 else {
1793 // Don't compare times, compare positions.
1794 //if (fabs(mQuickPlayPos[0] - mPlayRegionStart) < fabs(mQuickPlayPos[0] - mPlayRegionEnd))
1795 auto start = mOldPlayRegion.GetStart();
1796 auto end = mOldPlayRegion.GetEnd();
1797 if (abs(Time2Pos(mQuickPlayPos[0]) - Time2Pos(start)) <
1798 abs(Time2Pos(mQuickPlayPos[0]) - Time2Pos(end)))
1800 else
1802 }
1803 }
1804 else {
1805 // Clicked but not yet dragging
1807 }
1808}
1809
1810auto AdornedRulerPanel::QPHandle::Drag(
1811 const TrackPanelMouseEvent &event, AudacityProject *pProject) -> Result
1812{
1813 auto result = CommonRulerHandle::Drag(event, pProject);
1814 if (!( result & RefreshCode::Cancelled )) {
1815 if (mClicked == Button::Left) {
1816 if ( mParent ) {
1817 mX = event.event.m_x;
1818 mParent->UpdateQuickPlayPos( mX );
1819 mParent->HandleQPDrag( event.event, mX );
1820 }
1821 }
1822 }
1823 return result;
1824}
1825
1826void AdornedRulerPanel::HandleQPDrag(wxMouseEvent &/*event*/, wxCoord mousePosX)
1827{
1828 bool isWithinClick =
1829 (mLeftDownClickUnsnapped >= 0) &&
1831 bool isWithinStart = IsWithinMarker(mousePosX, mOldPlayRegion.GetStart());
1832 bool isWithinEnd = IsWithinMarker(mousePosX, mOldPlayRegion.GetEnd());
1833 bool canDragSel = !mOldPlayRegion.Active() && mPlayRegionDragsSelection;
1834 auto &viewInfo = ViewInfo::Get( *GetProject() );
1835 auto &playRegion = viewInfo.playRegion;
1836
1837 switch (mMouseEventState)
1838 {
1839 case mesNone:
1840 // If close to either end of play region, snap to closest
1841 if (isWithinStart || isWithinEnd) {
1844 else
1846 }
1847 break;
1849 // Don't start dragging until beyond tolerance initial playback start
1850 if (!mIsDragging && isWithinStart)
1852 else
1853 mIsDragging = true;
1854 // avoid accidental tiny selection
1855 if (isWithinEnd)
1857 playRegion.SetStart( mQuickPlayPos[0] );
1858 if (canDragSel) {
1860 }
1861 break;
1863 if (!mIsDragging && isWithinEnd) {
1865 }
1866 else
1867 mIsDragging = true;
1868 if (isWithinStart) {
1870 }
1871 playRegion.SetEnd( mQuickPlayPos[0] );
1872 if (canDragSel) {
1874 }
1875 break;
1877
1878 // Don't start dragging until mouse is beyond tolerance of initial click.
1879 if (isWithinClick || mLeftDownClick == -1) {
1881 playRegion.SetTimes(mLeftDownClick, mLeftDownClick);
1882 }
1883 else {
1885 }
1886 break;
1888 if (isWithinClick) {
1890 }
1891
1893 playRegion.SetTimes( mQuickPlayPos[0], mLeftDownClick );
1894 else
1895 playRegion.SetTimes( mLeftDownClick, mQuickPlayPos[0] );
1896 if (canDragSel) {
1898 }
1899 break;
1900 }
1901 Refresh();
1902 Update();
1903}
1904#endif
1905
1907 const TrackPanelMouseState &, AudacityProject *pProject)
1909{
1910 auto &scrubber = Scrubber::Get( *pProject );
1911 auto message = ScrubbingMessage(scrubber, mClicked == Button::Left);
1912
1913 mParent->SetNumGuides(1);
1914 return {
1915 message,
1916 {},
1917 // Tooltip is same as status message, or blank
1918 ((mParent && mParent->mTimelineToolTip) ? message : TranslatableString{}),
1919 };
1920}
1921
1922#ifdef QUICK_PLAY_HANDLE
1923auto AdornedRulerPanel::QPHandle::Preview(
1924 const TrackPanelMouseState &state, AudacityProject *pProject)
1926{
1927 mParent->SetNumGuides(1);
1928 TranslatableString tooltip;
1929 #if 0
1930 if (mParent && mParent->mTimelineToolTip) {
1931 if (!mParent->mQuickPlayEnabled)
1932 tooltip = XO("Quick-Play disabled");
1933 else
1934 tooltip = XO("Quick-Play enabled");
1935 }
1936 #endif
1937
1938 TranslatableString message;
1939 auto &scrubber = Scrubber::Get( *pProject );
1940 const bool scrubbing = scrubber.HasMark();
1941 if (scrubbing)
1942 // Don't distinguish zones
1943 message = ScrubbingMessage(scrubber, false);
1944 else
1945 // message = Insert timeline status bar message here
1946 ;
1947
1948 static wxCursor cursorHand{ wxCURSOR_HAND };
1949 static wxCursor cursorSizeWE{ wxCURSOR_SIZEWE };
1950
1951 bool showArrows = false;
1952 if (mParent)
1953 showArrows =
1954 (mClicked == Button::Left)
1955 || mParent->IsWithinMarker(
1956 state.state.m_x, mParent->mOldPlayRegion.GetStart())
1957 || mParent->IsWithinMarker(
1958 state.state.m_x, mParent->mOldPlayRegion.GetEnd());
1959
1960 return {
1961 message,
1962 showArrows ? &cursorSizeWE : &cursorHand,
1963 tooltip,
1964 };
1965}
1966
1968 const TrackPanelMouseEvent &event, AudacityProject *pProject,
1969 wxWindow *pParent)
1970 -> Result
1971{
1972 // Keep a shared pointer to self. Otherwise *this might get deleted
1973 // in HandleQPRelease on Windows! Because there is an event-loop yield
1974 // stopping playback, which caused OnCaptureLost to be called, which caused
1975 // clearing of CellularPanel targets!
1976 auto saveMe = mParent->mQPCell->mHolder.lock();
1977
1978 auto result = CommonRulerHandle::Release(event, pProject, pParent);
1979 if (!( result & RefreshCode::Cancelled )) {
1980 if (mClicked == Button::Left) {
1981 if ( mParent ) {
1982 mParent->HandleQPRelease( event.event );
1983 // Update the hot zones for cursor changes
1984 const auto &viewInfo = ViewInfo::Get( *pProject );
1985 const auto &playRegion = viewInfo.playRegion;
1986 mParent->mOldPlayRegion = playRegion;
1987 }
1988 }
1989 }
1990 return result;
1991}
1992
1993void AdornedRulerPanel::HandleQPRelease(wxMouseEvent &evt)
1994{
1995 auto &viewInfo = ViewInfo::Get( *GetProject() );
1996 auto &playRegion = viewInfo.playRegion;
1997 playRegion.Order();
1998
1999 const double t0 = mTracks->GetStartTime();
2000 const double t1 = mTracks->GetEndTime();
2001 const auto &selectedRegion = viewInfo.selectedRegion;
2002 const double sel0 = selectedRegion.t0();
2003 const double sel1 = selectedRegion.t1();
2004
2005 // We want some audio in the selection, but we allow a dragged
2006 // region to include selected white-space and space before audio start.
2007 if (evt.ShiftDown() && playRegion.Empty()) {
2008 // Looping the selection or project.
2009 // Disable if track selection is in white-space beyond end of tracks and
2010 // play position is outside of track contents.
2011 if (((sel1 < t0) || (sel0 > t1)) &&
2012 ((playRegion.GetStart() < t0) || (playRegion.GetStart() > t1))) {
2014 }
2015 }
2016 // Disable if beyond end.
2017 else if (playRegion.GetStart() >= t1) {
2019 }
2020 // Disable if empty selection before start.
2021 // (allow Quick-Play region to include 'pre-roll' white space)
2022 else if (
2023 playRegion.GetEnd() - playRegion.GetStart() > 0.0 &&
2024 playRegion.GetEnd() < t0
2025 ) {
2027 }
2028
2030 mIsDragging = false;
2031 mLeftDownClick = -1;
2032
2033 auto cleanup = finally( [&] {
2034 if (mOldPlayRegion.Active()) {
2035 // Restore Locked Play region
2036 SetPlayRegion(mOldPlayRegion.GetStart(), mOldPlayRegion.GetEnd());
2037 SelectUtilities::ActivatePlayRegion(*mProject);
2038 // and release local lock
2039 mOldPlayRegion.SetActive( false );
2040 }
2041 } );
2042
2043 StartQPPlay(!evt.ShiftDown(), evt.ControlDown());
2044}
2045
2046auto AdornedRulerPanel::QPHandle::Cancel(AudacityProject *pProject) -> Result
2047{
2048 auto result = CommonRulerHandle::Cancel(pProject);
2049
2050 if (mClicked == Button::Left) {
2051 if( mParent ) {
2052 ViewInfo::Get( *pProject ).selectedRegion = mOldSelection;
2053 mParent->mMouseEventState = mesNone;
2054 mParent->SetPlayRegion(
2055 mParent->mOldPlayRegion.GetStart(), mParent->mOldPlayRegion.GetEnd());
2056 if (mParent->mOldPlayRegion.Active()) {
2057 // Restore Locked Play region
2059 // and release local lock
2060 mParent->mOldPlayRegion.SetActive( false );
2061 }
2062 }
2063 }
2064
2065 return result;
2066}
2067#endif
2068
2070 bool newDefault, bool cutPreview, const double *pStartTime)
2071{
2072 const double t0 = mTracks->GetStartTime();
2073 const double t1 = mTracks->GetEndTime();
2074 auto &viewInfo = ViewInfo::Get( *mProject );
2075 const auto &playRegion = viewInfo.playRegion;
2076 const auto &selectedRegion = viewInfo.selectedRegion;
2077 const double sel0 = selectedRegion.t0();
2078 const double sel1 = selectedRegion.t1();
2079
2080 // Start / Restart playback on left click.
2081 bool startPlaying = true; // = (playRegion.GetStart() >= 0);
2082
2083 if (startPlaying) {
2084 bool loopEnabled = true;
2085 auto oldStart = std::max(0.0, playRegion.GetStart());
2086 double start = oldStart, end = 0;
2087
2088 if (playRegion.Empty()) {
2089 // Play either a selection or the project.
2090 if (oldStart > sel0 && oldStart < sel1) {
2091 // we are in a selection, so use the selection
2092 start = sel0;
2093 end = sel1;
2094 } // not in a selection, so use the project
2095 else {
2096 start = t0;
2097 end = t1;
2098 }
2099 }
2100 else
2101 end = std::max(start, playRegion.GetEnd());
2102
2103 // Looping a tiny selection may freeze, so just play it once.
2104 loopEnabled = ((end - start) > 0.001)? true : false;
2105
2106 newDefault = (loopEnabled && newDefault);
2107 if (newDefault)
2108 cutPreview = false;
2109 auto options = ProjectAudioIO::GetDefaultOptions(*mProject, newDefault);
2110
2111 if (!cutPreview) {
2112 if (pStartTime)
2113 options.pStartTime.emplace(*pStartTime);
2114 }
2115 else
2116 options.envelope = nullptr;
2117
2118 auto mode =
2119 cutPreview ? PlayMode::cutPreviewPlay
2120 : newDefault ? PlayMode::loopedPlay
2122
2123 // Stop only after deciding where to start again, because an event
2124 // callback may change the play region back to the selection
2125 auto &projectAudioManager = ProjectAudioManager::Get( *mProject );
2126 projectAudioManager.Stop();
2127
2128 // Don't change play region, assume caller set it as needed
2129 // playRegion.SetTimes( start, end );
2130 // Refresh();
2131
2132 projectAudioManager.PlayPlayRegion((SelectedRegion(start, end)),
2133 options, mode,
2134 false);
2135
2136 }
2137}
2138
2139#if 0
2140// This version toggles ruler state indirectly via the scrubber
2141// to ensure that all the places where the state is shown update.
2142// For example buttons and menus must update.
2143void AdornedRulerPanel::OnToggleScrubRulerFromMenu(wxCommandEvent&)
2144{
2145 auto &scrubber = Scrubber::Get( *mProject );
2146 scrubber.OnToggleScrubRuler(*mProject);
2147}
2148#endif
2149
2150
2152{
2153 const auto oldSize = GetSize();
2154 wxSize size { oldSize.GetWidth(), GetRulerHeight(ShowingScrubRuler()) };
2155 if ( size != oldSize ) {
2156 SetSize(size);
2157 SetMinSize(size);
2158 PostSizeEventToParent();
2159 return true;
2160 }
2161 else
2162 return false;
2163}
2164
2166{
2167 auto pCellularPanel =
2168 dynamic_cast<CellularPanel*>( &GetProjectPanel( *GetProject() ) );
2169 if ( !pCellularPanel ) {
2170 wxASSERT( false );
2171 }
2172 else
2173 pCellularPanel->DrawOverlays( false );
2174 DrawOverlays( false );
2175}
2176
2178{
2179 auto common = [this](
2180 AButton &button, const CommandID &commandName, const TranslatableString &label) {
2181 ComponentInterfaceSymbol command{ commandName, label };
2182 ToolBar::SetButtonToolTip( *mProject, button, &command, 1u );
2183 button.SetLabel( Verbatim( button.GetToolTipText() ) );
2184
2185 button.UpdateStatus();
2186 };
2187
2188 {
2189 // The button always reflects the pinned head preference, even though
2190 // there is also a Playback preference that may overrule it for scrubbing
2192 auto pinButton = static_cast<AButton*>(FindWindow(OnTogglePinnedStateID));
2193 if( !state )
2194 pinButton->PopUp();
2195 else
2196 pinButton->PushDown();
2197 auto gAudioIO = AudioIO::Get();
2198 pinButton->SetAlternateIdx(
2199 (gAudioIO->IsCapturing() ? 2 : 0) + (state ? 0 : 1));
2200 // Bug 1584: Tooltip now shows what clicking will do.
2201 // Bug 2357: Action of button (and hence tooltip wording) updated.
2202 const auto label = XO("Timeline Options");
2203 common(*pinButton, wxT("PinnedHead"), label);
2204 }
2205}
2206
2207void AdornedRulerPanel::OnPinnedButton(wxCommandEvent & /*event*/)
2208{
2210}
2211
2212void AdornedRulerPanel::OnTogglePinnedState(wxCommandEvent & /*event*/)
2213{
2216}
2217
2219{
2220 // Invoked for mouse-over preview events, or dragging, or scrub position
2221 // polling updates. Remember x coordinates, converted to times, for
2222 // drawing of guides.
2223
2224 // Keep Quick-Play within usable track area. (Dependent on zoom)
2225 const auto &viewInfo = ViewInfo::Get( *mProject );
2226 auto width = viewInfo.GetTracksUsableWidth();
2227 mousePosX = std::max(mousePosX, viewInfo.GetLeftOffset());
2228 mousePosX = std::min(mousePosX, viewInfo.GetLeftOffset() + width - 1);
2229 const auto time = Pos2Time(mousePosX);
2230
2231 for (size_t ii = 0; ii < mNumGuides; ++ii) {
2233 time + mQuickPlayOffset[ii];
2234 HandleSnapping(ii);
2235 }
2236}
2237
2238// Pop-up menus
2239
2240void AdornedRulerPanel::ShowMenu(const wxPoint & pos)
2241{
2242 const auto &viewInfo = ViewInfo::Get( *GetProject() );
2243 const auto &playRegion = viewInfo.playRegion;
2244 wxMenu rulerMenu;
2245
2246 {
2247 auto item = rulerMenu.AppendRadioItem(OnMinutesAndSecondsID,
2248 _("Minutes and Seconds"));
2249 item->Check(!mBeatsAndMeasures);
2250 }
2251
2252 {
2253 auto item = rulerMenu.AppendRadioItem(OnBeatsAndMeasuresID,
2254 _("Beats and Measures"));
2255 item->Check(mBeatsAndMeasures);
2256 }
2257
2258 rulerMenu.AppendSeparator();
2259
2260 auto pDrag = rulerMenu.AppendCheckItem(OnSyncQuickPlaySelID, _("Enable dragging selection"));
2261 pDrag->Check(mPlayRegionDragsSelection && playRegion.Active());
2262 pDrag->Enable(playRegion.Active());
2263
2264 rulerMenu.AppendCheckItem(OnAutoScrollID, _("Update display while playing"))->
2266
2267 {
2268 auto item = rulerMenu.AppendCheckItem(OnTogglePlayRegionID,
2270 item->Check(playRegion.Active());
2271 }
2272
2273 {
2274 auto item = rulerMenu.Append(OnClearPlayRegionID,
2275 /* i18n-hint Clear is a verb */
2276 _("Clear Loop"));
2277 }
2278
2279 {
2280 auto item = rulerMenu.Append(OnSetPlayRegionToSelectionID,
2281 _("Set Loop To Selection"));
2282 }
2283
2284 rulerMenu.AppendSeparator();
2285 rulerMenu.AppendCheckItem(OnTogglePinnedStateID, _("Pinned Play Head"))->
2287
2288 BasicMenu::Handle{ &rulerMenu }.Popup(
2290 { pos.x, pos.y }
2291 );
2292}
2293
2294void AdornedRulerPanel::ShowScrubMenu(const wxPoint & pos)
2295{
2296 auto &scrubber = Scrubber::Get( *mProject );
2297 PushEventHandler(&scrubber);
2298 auto cleanup = finally([this]{ PopEventHandler(); });
2299
2300 wxMenu rulerMenu;
2301 scrubber.PopulatePopupMenu(rulerMenu);
2302 BasicMenu::Handle{ &rulerMenu }.Popup(
2304 { pos.x, pos.y }
2305 );
2306}
2307
2309{
2310 auto &viewInfo = ViewInfo::Get( project );
2311 const auto &playRegion = viewInfo.playRegion;
2312 auto &selectedRegion = viewInfo.selectedRegion;
2313 selectedRegion.setT0(playRegion.GetStart(), false);
2314 selectedRegion.setT1(playRegion.GetEnd(), true);
2315}
2316
2318{
2319 // Play region dragging can snap to selection boundaries
2320 const auto &selectedRegion = ViewInfo::Get(*GetProject()).selectedRegion;
2321 SnapPointArray candidates;
2323 candidates = {
2324 SnapPoint{ selectedRegion.t0() },
2325 SnapPoint{ selectedRegion.t1() },
2326 };
2327 SnapManager snapManager{ *mProject, *mTracks, *mViewInfo, move(candidates) };
2328 auto results = snapManager.Snap(nullptr, mQuickPlayPos[index], false);
2329 mQuickPlayPos[index] = results.outTime;
2330 mIsSnapped[index] = results.Snapped();
2331}
2332
2334{
2335 if (mBeatsAndMeasures) {
2339 }
2340 else {
2342 }
2343 Refresh();
2344}
2345
2347{
2348 int id = event.GetId();
2349 bool changeFlag = mBeatsAndMeasures;
2350 wxASSERT(id == OnMinutesAndSecondsID || id == OnBeatsAndMeasuresID);
2352 if (mBeatsAndMeasures) {
2357 }
2358 else {
2360 }
2361 if (changeFlag != mBeatsAndMeasures)
2362 Refresh();
2363}
2364
2366{
2368 gPrefs->Write(wxT("/QuickPlay/DragSelection"), mPlayRegionDragsSelection);
2369 gPrefs->Flush();
2370}
2371
2372#if 0
2373void AdornedRulerPanel::OnTimelineToolTips(wxCommandEvent&)
2374{
2375 mTimelineToolTip = (mTimelineToolTip)? false : true;
2376 gPrefs->Write(wxT("/QuickPlay/ToolTips"), mTimelineToolTip);
2377 gPrefs->Flush();
2378}
2379#endif
2380
2381
2383{
2385 gPrefs->Write(wxT("/GUI/AutoScroll"), false);
2386 else
2387 gPrefs->Write(wxT("/GUI/AutoScroll"), true);
2388
2389 gPrefs->Flush();
2390
2392}
2393
2394
2396{
2398}
2399
2401{
2403}
2404
2406{
2408}
2409
2410
2412 MenuChoice choice, const wxPoint *pPosition)
2413{
2414 wxPoint position;
2415 if(pPosition)
2416 position = *pPosition;
2417 else
2418 {
2419 auto rect = GetRect();
2420 //Old code put menu too low down. y position applied twice.
2421 //position = { rect.GetLeft() + 1, rect.GetBottom() + 1 };
2422
2423 // The cell does not pass in the mouse or button position.
2424 // We happen to know this is the pin/unpin button
2425 // so these magic values 'fix a bug' - but really the cell should
2426 // pass more information to work with in.
2427 position = { rect.GetLeft() + 38, rect.GetHeight()/2 + 1 };
2428 }
2429
2430 switch (choice) {
2432 ShowMenu(position);
2434 break;
2435 case MenuChoice::Scrub:
2436 ShowScrubMenu(position); break;
2437 default:
2438 return;
2439 }
2440}
2441
2442using ColorId = decltype(clrTrackInfo);
2443
2445{
2446 return clrTrackInfo;
2447}
2448
2450{
2451 return clrTrackPanelText;
2452}
2453
2455{
2456 return TimelineTextColor();
2457}
2458
2460{
2461 return isActive ? clrRulerBackground : clrClipAffordanceInactiveBrush;
2462}
2463
2464static inline wxColour AlphaBlend(ColorId fg, ColorId bg, double alpha)
2465{
2466 const auto &fgc = theTheme.Colour(fg);
2467 const auto &bgc = theTheme.Colour(bg);
2468 return wxColour{
2469 wxColour::AlphaBlend(fgc.Red(), bgc.Red(), alpha),
2470 wxColour::AlphaBlend(fgc.Green(), bgc.Green(), alpha),
2471 wxColour::AlphaBlend(fgc.Blue(), bgc.Blue(), alpha)
2472 };
2473}
2474
2476{
2477 // Draw AdornedRulerPanel border
2479 dc->DrawRectangle( mInner );
2480
2481 if (ShowingScrubRuler()) {
2482 // Let's distinguish the scrubbing area by using a themable
2483 // colour and a line to set it off.
2484 AColor::UseThemeColour(dc, clrScrubRuler, TimelineTextColor() );
2485 wxRect ScrubRect = mScrubZone;
2486 ScrubRect.Inflate( 1,0 );
2487 dc->DrawRectangle(ScrubRect);
2488 }
2489}
2490
2492{
2493 wxRect r = mOuter;
2494 r.width -= RightMargin;
2495 r.height -= BottomMargin;
2496 AColor::BevelTrackInfo( *dc, true, r );
2497
2498 // Black stroke at bottom
2499 dc->SetPen( *wxBLACK_PEN );
2500 AColor::Line( *dc, mOuter.x,
2501 mOuter.y + mOuter.height - 1,
2502 mOuter.x + mOuter.width - 1 ,
2503 mOuter.y + mOuter.height - 1 );
2504}
2505
2506void AdornedRulerPanel::DoDrawMarks(wxDC * dc, bool /*text */ )
2507{
2508 const double min = Pos2Time(0);
2509 const double hiddenMin = Pos2Time(0, true);
2510 const double max = Pos2Time(mInner.width);
2511 const double hiddenMax = Pos2Time(mInner.width, true);
2512
2514 mRuler.SetRange( min, max, hiddenMin, hiddenMax );
2516 {
2517 mRuler.SetTickLengths({ 5, 3, 1 });
2518 }
2519 else
2520 {
2521 mRuler.SetTickLengths({ 4, 2, 2 });
2522 }
2523 mRuler.Draw( *dc );
2524}
2525
2527{
2528 Refresh();
2529}
2530
2532{
2533 const auto &viewInfo = ViewInfo::Get(*mProject);
2534 const auto &playRegion = viewInfo.playRegion;
2535 const auto t0 = playRegion.GetLastActiveStart(),
2536 t1 = playRegion.GetLastActiveEnd();
2537 return RegionRectangle(t0, t1);
2538}
2539
2541{
2542 const auto &viewInfo = ViewInfo::Get(*mProject);
2543 const auto &selectedRegion = viewInfo.selectedRegion;
2544 const auto t0 = selectedRegion.t0(), t1 = selectedRegion.t1();
2545 return RegionRectangle(t0, t1);
2546}
2547
2548wxRect AdornedRulerPanel::RegionRectangle(double t0, double t1) const
2549{
2550 int p0 = -1, p1 = -1;
2551 if (t0 == t1)
2552 // Make the rectangle off-screen horizontally, but set the height
2553 ;
2554 else {
2555 p0 = max(1, Time2Pos(t0));
2556 p1 = min(mInner.width, Time2Pos(t1));
2557 }
2558
2559 const int left = p0, top = mInner.y, right = p1, bottom = mInner.GetBottom();
2560 return { wxPoint{left, top}, wxPoint{right, bottom} };
2561}
2562
2564 wxDC * dc, const wxRect &rectP, const wxRect &rectL, const wxRect &rectR)
2565{
2566 const auto &viewInfo = ViewInfo::Get(*mProject);
2567 const auto &playRegion = viewInfo.playRegion;
2568 if (playRegion.IsLastActiveRegionClear())
2569 return;
2570
2571 const bool isActive = (mLastPlayRegionActive = playRegion.Active());
2572
2573 // Paint the selected region bolder if independently varying, else dim
2574 const auto color = TimelineLoopRegionColor(isActive);
2575 dc->SetBrush( wxBrush( theTheme.Colour( color )) );
2576 dc->SetPen( wxPen( theTheme.Colour( color )) );
2577
2578 dc->DrawRectangle( rectP.Intersect(rectL) );
2579 dc->DrawRectangle( rectP.Intersect(rectR) );
2580}
2581
2582void AdornedRulerPanel::DoDrawPlayRegionLimits(wxDC * dc, const wxRect &rect)
2583{
2584 // Color the edges of the play region like the ticks and numbers
2585 ADCChanger cleanup( dc );
2586 const auto edgeColour = theTheme.Colour(TimelineLimitsColor());
2587 dc->SetPen( { edgeColour } );
2588 dc->SetBrush( { edgeColour } );
2589
2590 constexpr int side = 7;
2591 constexpr int sideLessOne = side - 1;
2592
2593 // Paint two shapes, each a line plus triangle at bottom
2594 const auto left = rect.GetLeft(),
2595 right = rect.GetRight(),
2596 bottom = rect.GetBottom(),
2597 top = rect.GetTop();
2598 {
2599 wxPoint points[]{
2600 {left, bottom - sideLessOne},
2601 {left - sideLessOne, bottom},
2602 {left, bottom},
2603 {left, top},
2604 };
2605 dc->DrawPolygon( 4, points );
2606 }
2607
2608 {
2609 wxPoint points[]{
2610 {right, top},
2611 {right, bottom},
2612 {right + sideLessOne, bottom},
2613 {right, bottom - sideLessOne},
2614 };
2615 dc->DrawPolygon( 4, points );
2616 }
2617}
2618
2619constexpr double SelectionOpacity = 0.2;
2620
2621void AdornedRulerPanel::DoDrawOverlap(wxDC * dc, const wxRect &rect)
2622{
2623 dc->SetBrush( wxBrush{ AlphaBlend(
2625 SelectionOpacity) } );
2626 dc->SetPen( *wxTRANSPARENT_PEN );
2627 dc->DrawRectangle( rect );
2628}
2629
2631 wxDC * dc, const wxRect &rectS, const wxRect &rectL, const wxRect &rectR)
2632{
2633 dc->SetBrush( wxBrush{ AlphaBlend(
2635 dc->SetPen( *wxTRANSPARENT_PEN );
2636 dc->DrawRectangle( rectS.Intersect(rectL) );
2637 dc->DrawRectangle( rectS.Intersect(rectR) );
2638}
2639
2641{
2642 return ProperRulerHeight + (showScrubBar ? ScrubHeight : 0);
2643}
2644
2646{
2647 if (mLeftOffset != offset) {
2648 mLeftOffset = offset;
2649 mUpdater.SetData(mViewInfo, offset);
2651 }
2652}
2653
2654// Draws the scrubbing/seeking indicator.
2656 wxDC * dc, wxCoord xx, int width, bool scrub, bool seek)
2657{
2658 ADCChanger changer(dc); // Undo pen and brush changes at function exit
2659
2660 wxPoint tri[ 3 ];
2661 if (seek) {
2662 auto height = IndicatorHeightForWidth(width);
2663 // Make four triangles
2664 const int TriangleWidth = width * 3 / 8;
2665
2666 // Double-double headed, left-right
2667 auto yy = ShowingScrubRuler()
2668 ? mScrubZone.y
2669 : (mInner.GetBottom() + 1) - 1 /* bevel */ - height;
2670 tri[ 0 ].x = xx - IndicatorOffset;
2671 tri[ 0 ].y = yy;
2672 tri[ 1 ].x = xx - IndicatorOffset;
2673 tri[ 1 ].y = yy + height;
2674 tri[ 2 ].x = xx - TriangleWidth;
2675 tri[ 2 ].y = yy + height / 2;
2676 dc->DrawPolygon( 3, tri );
2677
2678 tri[ 0 ].x -= TriangleWidth;
2679 tri[ 1 ].x -= TriangleWidth;
2680 tri[ 2 ].x -= TriangleWidth;
2681 dc->DrawPolygon( 3, tri );
2682
2683 tri[ 0 ].x = tri[ 1 ].x = xx + IndicatorOffset;
2684 tri[ 2 ].x = xx + TriangleWidth;
2685 dc->DrawPolygon( 3, tri );
2686
2687
2688 tri[ 0 ].x += TriangleWidth;
2689 tri[ 1 ].x += TriangleWidth;
2690 tri[ 2 ].x += TriangleWidth;
2691 dc->DrawPolygon( 3, tri );
2692 }
2693 else if (scrub) {
2694 auto height = IndicatorHeightForWidth(width);
2695 const int IndicatorHalfWidth = width / 2;
2696
2697 // Double headed, left-right
2698 auto yy = ShowingScrubRuler()
2699 ? mScrubZone.y
2700 : (mInner.GetBottom() + 1) - 1 /* bevel */ - height;
2701 tri[ 0 ].x = xx - IndicatorOffset;
2702 tri[ 0 ].y = yy;
2703 tri[ 1 ].x = xx - IndicatorOffset;
2704 tri[ 1 ].y = yy + height;
2705 tri[ 2 ].x = xx - IndicatorHalfWidth;
2706 tri[ 2 ].y = yy + height / 2;
2707 dc->DrawPolygon( 3, tri );
2708 tri[ 0 ].x = tri[ 1 ].x = xx + IndicatorOffset;
2709 tri[ 2 ].x = xx + IndicatorHalfWidth;
2710 dc->DrawPolygon( 3, tri );
2711 }
2712}
2713
2715 double playRegionStart, double playRegionEnd)
2716{
2717 // This is called by AudacityProject to make the play region follow
2718 // the current selection. But while the user is selecting a play region
2719 // with the mouse directly in the ruler, changes from outside are blocked.
2721 return;
2722
2723 auto &viewInfo = ViewInfo::Get( *GetProject() );
2724 auto &playRegion = viewInfo.playRegion;
2725 playRegion.SetTimes( playRegionStart, playRegionEnd );
2726
2727 Refresh();
2728}
2729
2731{
2732 ProjectAudioManager::Get( *mProject ).Stop();
2733
2734 auto &viewInfo = ViewInfo::Get( *GetProject() );
2735 auto &playRegion = viewInfo.playRegion;
2736 playRegion.SetTimes( -1, -1 );
2737
2738 Refresh();
2739}
2740
2741void AdornedRulerPanel::GetMaxSize(wxCoord *width, wxCoord *height)
2742{
2743 mRuler.GetMaxSize(width, height);
2744}
2745
2747
2749 s_AcceptsFocus = true;
2750 return TempAllowFocus{ &s_AcceptsFocus };
2751}
2752
2754{
2755 nn = std::min(nn, MAX_GUIDES);
2756 // If increasing the number of guides, reinitialize newer ones
2757 for (size_t ii = mNumGuides; ii < nn; ++ii) {
2758 mQuickPlayOffset[ii] = 0;
2759 mQuickPlayPosUnsnapped[ii] = 0;
2760 mQuickPlayPos[ii] = 0;
2761 mIsSnapped[ii] = false;
2762 }
2763 mNumGuides = nn;
2764}
2765
2767{
2768 auto temp = TemporarilyAllowFocus();
2769 SetFocus();
2770}
2771
2772// Second-level subdivision includes quick-play region and maybe the scrub bar
2773// and also shaves little margins above and below
2775 explicit Subgroup( const AdornedRulerPanel &ruler ) : mRuler{ ruler } {}
2776 Subdivision Children( const wxRect & ) override
2777 {
2778 return { Axis::Y, ( mRuler.ShowingScrubRuler() )
2779 ? Refinement{
2780 { mRuler.mInner.GetTop(), mRuler.mQPCell },
2781 { mRuler.mScrubZone.GetTop(), mRuler.mScrubbingCell },
2782 { mRuler.mScrubZone.GetBottom() + 1, nullptr }
2783 }
2784 : Refinement{
2785 { mRuler.mInner.GetTop(), mRuler.mQPCell },
2786 { mRuler.mInner.GetBottom() + 1, nullptr }
2787 }
2788 };
2789 }
2791};
2792
2793// Top-level subdivision shaves little margins off left and right
2795 explicit MainGroup( const AdornedRulerPanel &ruler ) : mRuler{ ruler } {}
2796 Subdivision Children( const wxRect & ) override
2797 { return { Axis::X, Refinement{
2798 // Subgroup is a throwaway object
2799 { mRuler.mInner.GetLeft(), std::make_shared< Subgroup >( mRuler ) },
2800 { mRuler.mInner.GetRight() + 1, nullptr }
2801 } }; }
2803};
2804
2806{
2807 auto &scrubber = Scrubber::Get( *GetProject() );
2808 return scrubber.ShowsBar();
2809}
2810
2811// CellularPanel implementation
2812std::shared_ptr<TrackPanelNode> AdornedRulerPanel::Root()
2813{
2814 // Root is a throwaway object
2815 return std::make_shared< MainGroup >( *this );
2816}
2817
2819{
2820 return mProject;
2821}
2822
2823
2825{
2826 // No switching of focus yet to the other, scrub zone
2827 return mQPCell.get();
2828}
2829
2830
2832{
2833}
2834
2835
2837 TrackPanelCell *, TrackPanelCell *, unsigned refreshResult)
2838{
2839 if (refreshResult & RefreshCode::RefreshAll)
2840 Refresh(); // Overlays will be repainted too
2841 else if (refreshResult & RefreshCode::DrawOverlays)
2842 DrawBothOverlays(); // cheaper redrawing of guidelines only
2843}
2844
2846{
2847 ProjectStatus::Get( *GetProject() ).Set(message);
2848}
2849
2851{
2852 if (!mOverlay) {
2853 mOverlay =
2854 std::make_shared<TrackPanelGuidelineOverlay>( mProject );
2855 auto pCellularPanel =
2856 dynamic_cast<CellularPanel*>( &GetProjectPanel( *GetProject() ) );
2857 if ( !pCellularPanel ) {
2858 wxASSERT( false );
2859 }
2860 else
2861 pCellularPanel->AddOverlay( mOverlay );
2862 this->AddOverlay( mOverlay->mPartner );
2863 }
2864}
2865
2867{
2871
2872 auto &project = *mProject;
2873 // Update button image
2875
2876 auto &scrubber = Scrubber::Get( project );
2877 if (scrubber.HasMark())
2878 scrubber.SetScrollScrubbing(value);
2879}
2880
2881// Attach menu item
2882
2885#include "CommonCommandFlags.h"
2886
2887namespace {
2889{
2891}
2892
2893using namespace MenuTable;
2896 { wxT("Transport/Other/Options/Part2"), { OrderingHint::Begin, {} } },
2897 Command( wxT("PinnedHead"), XXO("Pinned Play/Record &Head (on/off)"),
2899 // Switching of scrolling on and off is permitted
2900 // even during transport
2902 Options{}.CheckTest([](const AudacityProject&){
2904};
2905}
EVT_MENU(OnSetPlayRegionToSelectionID, AdornedRulerPanel::OnSetPlayRegionToSelection) EVT_COMMAND(OnTogglePinnedStateID
ColorId TimelineLimitsColor()
decltype(clrTrackInfo) ColorId
ColorId TimelineLoopRegionColor(bool isActive)
@ IndicatorOffset
@ IndicatorSmallWidth
@ LeftMargin
@ RightMargin
@ TopMargin
@ IndicatorMediumWidth
@ BottomMargin
static wxColour AlphaBlend(ColorId fg, ColorId bg, double alpha)
int IndicatorBigHeight()
static auto handOpenCursor
ColorId TimelineBackgroundColor()
int IndicatorBigWidth()
@ OnTogglePinnedStateID
@ OnBeatsAndMeasuresID
@ OnSetPlayRegionToSelectionID
@ OnClearPlayRegionID
@ OnAutoScrollID
@ OnTogglePlayRegionID
@ OnMinutesAndSecondsID
@ OnSyncQuickPlaySelID
int IndicatorWidthForHeight(int height)
ColorId TimelineTextColor()
wxEVT_COMMAND_BUTTON_CLICKED
int IndicatorHeightForWidth(int width)
@ ProperRulerHeight
@ ScrubHeight
#define SELECT_TOLERANCE_PIXEL
constexpr double SelectionOpacity
wxT("CloseDown"))
Abstractions of menus and their items.
END_EVENT_TABLE()
IntSetting UpperTimeSignature
The upper time signature of the Beats & Measures ruler.
Definition: Beats.cpp:14
IntSetting LowerTimeSignature
The lower time signature of the Beats & Measures ruler.
Definition: Beats.cpp:15
DoubleSetting BeatsPerMinute
The tempo used for drawing the Beats & Measures ruler.
Definition: Beats.cpp:13
constexpr CommandFlag AlwaysEnabledFlag
Definition: CommandFlag.h:34
int min(int a, int b)
const wxChar * values
XO("Cut/Copy/Paste")
XXO("&Cut/Copy/Paste Toolbar")
int teBmps
#define _(s)
Definition: Internat.h:73
EVT_COMMAND(wxID_ANY, EVT_FREQUENCYTEXTCTRL_UPDATED, LabelDialog::OnFreqUpdate) LabelDialog
Definition: LabelDialog.cpp:88
#define safenew
Definition: MemoryX.h:10
FileConfig * gPrefs
Definition: Prefs.cpp:70
@ cutPreviewPlay
AUDACITY_DLL_API wxWindow & GetProjectPanel(AudacityProject &project)
Get the main sub-window of the project frame that displays track data.
AUDACITY_DLL_API AttachedWindows & GetAttachedWindows(AudacityProject &project)
accessors for certain important windows associated with each project
std::vector< SnapPoint > SnapPointArray
Definition: Snap.h:43
TranslatableString label
Definition: TagsEditor.cpp:164
THEME_API Theme theTheme
Definition: Theme.cpp:82
declares abstract base class Track, TrackList, and iterators over TrackList
std::unique_ptr< wxCursor > MakeCursor(int WXUNUSED(CursorId), const char *const pXpm[36], int HotX, int HotY)
Definition: TrackPanel.cpp:186
TranslatableString Verbatim(wxString str)
Require calls to the one-argument constructor to go through this distinct global function name.
std::shared_ptr< Subclass > AssignUIHandlePtr(std::weak_ptr< Subclass > &holder, const std::shared_ptr< Subclass > &pNew)
Definition: UIHandle.h:151
BoolSetting ScrollingPreference
Definition: ViewInfo.cpp:336
const TranslatableString LoopToggleText
Definition: ViewInfo.cpp:227
int id
A wxButton with mouse-over behaviour.
Definition: AButton.h:104
void UpdateStatus()
Definition: AButton.cpp:455
void PopUp()
Definition: AButton.cpp:585
void SetLabel(const TranslatableString &label)
Definition: AButton.cpp:189
static void IndicatorColor(wxDC *dc, bool bIsNotRecording)
Definition: AColor.cpp:472
static void Line(wxDC &dc, wxCoord x1, wxCoord y1, wxCoord x2, wxCoord y2)
Definition: AColor.cpp:187
static void SnapGuidePen(wxDC *dc)
Definition: AColor.cpp:488
static void Light(wxDC *dc, bool selected, bool highlight=false)
Definition: AColor.cpp:413
static void BevelTrackInfo(wxDC &dc, bool up, const wxRect &r, bool highlight=false)
Definition: AColor.cpp:340
static void UseThemeColour(wxDC *dc, int iBrush, int iPen=-1, int alpha=255)
Definition: AColor.cpp:372
Makes temporary drawing context changes that you back out of, RAII style.
Definition: OverlayPanel.h:72
CommonCell(AdornedRulerPanel *parent, MenuChoice menuChoice)
unsigned DoContextMenu(const wxRect &, wxWindow *, const wxPoint *pPosition, AudacityProject *) final
HitTestPreview DefaultPreview(const TrackPanelMouseState &, const AudacityProject *) override
Result Click(const TrackPanelMouseEvent &event, AudacityProject *) override
Result Drag(const TrackPanelMouseEvent &, AudacityProject *) override
double Time(AudacityProject &project) const
bool HandlesRightClick() override
Whether the handle has any special right-button handling.
CommonRulerHandle(AdornedRulerPanel *pParent, wxCoord xx, MenuChoice menuChoice)
Result Release(const TrackPanelMouseEvent &event, AudacityProject *, wxWindow *) override
wxWeakRef< AdornedRulerPanel > mParent
void StartPlay(AudacityProject &project, const wxMouseEvent &event)
Result Cancel(AudacityProject *) override
static UIHandle::Result NeedChangeHighlight(const CommonRulerHandle &oldState, const CommonRulerHandle &newState)
void Enter(bool, AudacityProject *) override
void DoStartAdjust(AudacityProject &project, double) override
MovePlayRegionHandle(AdornedRulerPanel *pParent, wxCoord xx)
void DoAdjust(AudacityProject &project) override
NewPlayRegionHandle(AdornedRulerPanel *pParent, wxCoord xx)
void DoAdjust(AudacityProject &project) override
void DoStartAdjust(AudacityProject &project, double time) override
virtual void DoAdjust(AudacityProject &)=0
virtual void DoStartAdjust(AudacityProject &, double)=0
Result Drag(const TrackPanelMouseEvent &event, AudacityProject *pProject) override
HitTestPreview Preview(const TrackPanelMouseState &state, AudacityProject *pProject) override
std::pair< double, double > SnappedTimes(AudacityProject &project)
Result Cancel(AudacityProject *pProject) override
Result Release(const TrackPanelMouseEvent &event, AudacityProject *pProject, wxWindow *pParent) override
void Unsnap(bool use, AudacityProject *pProject)
double SnappedTime(AudacityProject &project, size_t ii)
bool Escape(AudacityProject *pProject) override
PlayRegionAdjustingHandle(AdornedRulerPanel *pParent, wxCoord xx, MenuChoice menuChoice, wxCursor cursor, size_t numGuides=1)
void SavePlayRegion(AudacityProject &project)
bool HasEscape(AudacityProject *pProject) const override
std::vector< UIHandlePtr > HitTest(const TrackPanelMouseState &state, const AudacityProject *pProject) override
std::weak_ptr< NewPlayRegionHandle > mNewPlayRegionHolder
QPCell(AdornedRulerPanel *parent)
std::weak_ptr< PlayheadHandle > mPlayheadHolder
std::shared_ptr< TrackPanelCell > ContextMenuDelegate() override
std::weak_ptr< ResizePlayRegionHandle > mResizePlayRegionHolder
std::weak_ptr< MovePlayRegionHandle > mMovePlayRegionHolder
ResizePlayRegionHandle(AdornedRulerPanel *pParent, wxCoord xx, bool hitLeft)
void DoStartAdjust(AudacityProject &project, double time) override
void DoAdjust(AudacityProject &project) override
std::shared_ptr< TrackPanelCell > ContextMenuDelegate() override
ScrubbingCell(AdornedRulerPanel *parent)
std::weak_ptr< ScrubbingHandle > mHolder
std::vector< UIHandlePtr > HitTest(const TrackPanelMouseState &state, const AudacityProject *pProject) override
Result Cancel(AudacityProject *pProject) override
ScrubbingHandle(AdornedRulerPanel *pParent, wxCoord xx)
Result Drag(const TrackPanelMouseEvent &event, AudacityProject *pProject) override
HitTestPreview Preview(const TrackPanelMouseState &state, AudacityProject *pProject) override
Result Click(const TrackPanelMouseEvent &event, AudacityProject *pProject) override
Result Release(const TrackPanelMouseEvent &event, AudacityProject *pProject, wxWindow *pParent) override
std::pair< wxRect, bool > DoGetRectangle(wxSize size) override
void Draw(OverlayPanel &panel, wxDC &dc) override
unsigned SequenceNumber() const override
This number determines an ordering of overlays, so that those with higher numbers overpaint those wit...
ScrubbingRulerOverlay(TrackPanelGuidelineOverlay &partner)
void Draw(OverlayPanel &panel, wxDC &dc) override
std::shared_ptr< ScrubbingRulerOverlay > mPartner
std::pair< wxRect, bool > DoGetRectangle(wxSize size) override
unsigned SequenceNumber() const override
This number determines an ordering of overlays, so that those with higher numbers overpaint those wit...
This is an Audacity Specific ruler panel which additionally has border, selection markers,...
void OnSelectionChange(Observer::Message)
void UpdatePrefs() override
TrackPanelCell * GetFocusedCell() override
wxRect PlayRegionRectangle() const
void OnPaint(wxPaintEvent &evt)
double mQuickPlayOffset[MAX_GUIDES]
void OnAudioStartStop(AudioIOEvent)
std::shared_ptr< ScrubbingCell > mScrubbingCell
Observer::Subscription mAudioIOSubscription
void OnTogglePinnedState(wxCommandEvent &event)
void DoSelectionChange(const SelectedRegion &selectedRegion)
void SetPlayRegion(double playRegionStart, double playRegionEnd)
void ShowContextMenu(MenuChoice choice, const wxPoint *pPosition)
wxRect RegionRectangle(double t0, double t1) const
void SetFocusFromKbd() override
void OnSetPlayRegionToSelection(wxCommandEvent &evt)
void DoDrawOverlap(wxDC *dc, const wxRect &rect)
void OnSize(wxSizeEvent &evt)
void OnTimelineFormatChange(wxCommandEvent &evt)
wxWindow * mButtons[3]
AudacityProject *const mProject
void DoDrawScrubIndicator(wxDC *dc, wxCoord xx, int width, bool scrub, bool seek)
void GetMaxSize(wxCoord *width, wxCoord *height)
void OnPinnedButton(wxCommandEvent &event)
void UpdateStatusMessage(const TranslatableString &) override
void OnClearPlayRegion(wxCommandEvent &evt)
wxRect SelectedRegionRectangle() const
void OnIdle(wxIdleEvent &evt)
void ShowScrubMenu(const wxPoint &pos)
bool IsWithinMarker(int mousePosX, double markerTime)
void DoDrawMarks(wxDC *dc, bool)
std::shared_ptr< TrackPanelGuidelineOverlay > mOverlay
void OnTogglePlayRegion(wxCommandEvent &evt)
static TempAllowFocus TemporarilyAllowFocus()
std::unique_ptr< bool, Resetter > TempAllowFocus
void DoDrawPlayRegion(wxDC *dc, const wxRect &rectP, const wxRect &rectL, const wxRect &rectR)
void OnLeave(wxMouseEvent &evt)
std::pair< double, double > mLastDrawnPlayRegion
void DoDrawSelection(wxDC *dc, const wxRect &rectS, const wxRect &rectL, const wxRect &rectR)
static void DragSelection(AudacityProject &project)
void Refresh(bool eraseBackground=true, const wxRect *rect=(const wxRect *) NULL) override
static bool s_AcceptsFocus
void SetLeftOffset(int offset)
void OnAutoScroll(wxCommandEvent &evt)
void HandleQPClick(wxMouseEvent &event, wxCoord mousePosX)
std::shared_ptr< TrackPanelNode > Root() override
Observer::Subscription mPlayRegionSubscription
void HandleQPRelease(wxMouseEvent &event)
int Time2Pos(double t, bool ignoreFisheye=false) const
void ShowMenu(const wxPoint &pos)
AudacityProject * GetProject() const override
bool ShowingScrubRuler() const
void HandleQPDrag(wxMouseEvent &event, wxCoord mousePosX)
void StartQPPlay(bool newDefault, bool cutPreview, const double *pStartTime=nullptr)
Observer::Subscription mThemeChangeSubscription
MouseEventState mMouseEventState
bool mIsSnapped[MAX_GUIDES]
double mQuickPlayPos[MAX_GUIDES]
void DoDrawBackground(wxDC *dc)
std::shared_ptr< QPCell > mQPCell
void OnSyncSelToQuickPlay(wxCommandEvent &evt)
void UpdateQuickPlayPos(wxCoord &mousePosX)
void DoDrawPlayRegionLimits(wxDC *dc, const wxRect &rect)
static AdornedRulerPanel & Get(AudacityProject &project)
AdornedRulerPanel(AudacityProject *project, wxWindow *parent, wxWindowID id, const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize, ViewInfo *viewinfo=NULL)
static constexpr size_t MAX_GUIDES
void SetNumGuides(size_t nn)
BeatsFormat mBeatsFormat
void HandleSnapping(size_t index)
void DoDrawEdge(wxDC *dc)
void ProcessUIHandleResult(TrackPanelCell *pClickedTrack, TrackPanelCell *pLatestCell, unsigned refreshResult) override
double Pos2Time(int p, bool ignoreFisheye=false) const
SelectedRegion mLastDrawnSelectedRegion
static void Destroy(AudacityProject &project)
void SetFocusedCell() override
void OnThemeChange(struct ThemeChangeMessage)
LinearUpdater mUpdater
double mQuickPlayPosUnsnapped[MAX_GUIDES]
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 AudioIOBase * Get()
Definition: AudioIOBase.cpp:91
static AudioIO * Get()
Definition: AudioIO.cpp:147
wxDC & GetBackingDCForRepaint()
Definition: BackedPanel.cpp:35
void OnSize(wxSizeEvent &event)
Definition: BackedPanel.cpp:71
void DisplayBitmap(wxDC &dc)
Definition: BackedPanel.cpp:65
void Popup(const BasicUI::WindowPlacement &window, const Point &pos={})
Display the menu at pos, invoke at most one action, then hide it.
Definition: BasicMenu.cpp:209
void SetData(double bpm, int timeSigUpper, int timeSigLower)
Definition: BeatsFormat.h:30
Formerly part of TrackPanel, this abstract base class has no special knowledge of Track objects and i...
Definition: CellularPanel.h:34
ViewInfo * mViewInfo
bool CancelDragging(bool escaping)
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
CommandContext provides additional information to an 'Apply()' command. It provides the project,...
AudacityProject & project
ComponentInterfaceSymbol pairs a persistent string identifier used internally with an optional,...
virtual bool Flush(bool bCurrentOnly=false) wxOVERRIDE
Definition: FileConfig.cpp:143
The widget to the left of a ToolBar that allows it to be dragged around to NEW positions.
Definition: Grabber.h:107
void SetAsSpacer(bool bIsSpacer)
Definition: Grabber.cpp:99
void SetData(const ZoomInfo *pZoomInfo=nullptr, int leftOffset=0)
Definition: LinearUpdater.h:33
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)
void AddOverlay(const std::weak_ptr< Overlay > &pOverlay)
double GetStart() const
Definition: ViewInfo.h:128
void SetStart(double start)
Definition: ViewInfo.cpp:161
double GetEnd() const
Definition: ViewInfo.h:135
bool Empty() const
Definition: ViewInfo.h:127
void SetEnd(double end)
Definition: ViewInfo.cpp:171
bool Active() const
Definition: ViewInfo.h:124
void SetTimes(double start, double end)
Definition: ViewInfo.cpp:181
static void Broadcast(int id=0)
Call this static function to notify all PrefsListener objects.
Definition: Prefs.cpp:97
bool IsAudioActive() const
static AudioIOStartStreamOptions GetDefaultOptions(AudacityProject &project, bool newDefaults=false)
Invoke the global hook, supplying a default argument.
static ProjectAudioIO & Get(AudacityProject &project)
void Stop(bool stopStream=true)
static ProjectAudioManager & Get(AudacityProject &project)
static ProjectStatus & Get(AudacityProject &project)
void Set(const TranslatableString &msg, StatusBarField field=mainStatusBarField)
static ProjectWindow & Get(AudacityProject &project)
void SetTickColour(const wxColour &colour)
Definition: Ruler.h:129
void SetFormat(const RulerFormat *pFormat)
Definition: Ruler.cpp:103
void Draw(wxDC &dc) const
Definition: Ruler.cpp:438
void SetTickLengths(const TickLengths &tLengths)
Definition: Ruler.cpp:254
void SetLabelEdges(bool labelEdges)
Definition: Ruler.cpp:178
void GetMaxSize(wxCoord *width, wxCoord *height)
Definition: Ruler.cpp:613
void SetBounds(int left, int top, int right, int bottom)
Definition: Ruler.cpp:303
void SetRange(double min, double max)
Definition: Ruler.cpp:151
void Invalidate()
Definition: Ruler.cpp:316
void SetTwoTone(bool twoTone)
Definition: Ruler.cpp:98
bool Seeks() const
static bool ShouldScrubPinned()
Definition: Scrubbing.cpp:150
bool IsScrubbing() const
static Scrubber & Get(AudacityProject &project)
Definition: Scrubbing.cpp:187
bool HasMark() const
Definition: Scrubbing.h:89
Defines a selected portion of a project.
double t1() const
double t0() const
bool Read(T *pVar) const
overload of Read returning a boolean that is true if the value was previously defined *‍/
Definition: Prefs.h:200
Definition: Snap.h:31
wxColour & Colour(int iIndex)
wxSize ImageSize(int iIndex)
static const TimeFormat & Instance()
Definition: TimeFormat.cpp:15
static void MakeAlternateImages(AButton &button, int idx, teBmps eUp, teBmps eDown, teBmps eHilite, teBmps eDownHi, teBmps eStandardUp, teBmps eStandardDown, teBmps eDisabled, wxSize size)
Definition: ToolBar.cpp:945
static AButton * MakeButton(wxWindow *parent, teBmps eUp, teBmps eDown, teBmps eHilite, teBmps eDownHi, teBmps eStandardUp, teBmps eStandardDown, teBmps eDisabled, wxWindowID id, wxPoint placement, bool processdownevents, wxSize size)
Definition: ToolBar.cpp:875
static void MakeButtonBackgroundsSmall()
Definition: ToolBar.cpp:837
static void SetButtonToolTip(AudacityProject &project, AButton &button, const ComponentInterfaceSymbol commands[], size_t nCommands)
Definition: ToolBar.cpp:970
static void ModifyAllProjectToolbarMenus()
double GetEndTime() const
Definition: Track.cpp:1053
double GetStartTime() const
Definition: Track.cpp:1048
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:487
std::pair< Axis, Refinement > Subdivision
std::vector< Child > Refinement
static void SetPinnedHeadPreference(bool value, bool flush=false)
static bool GetPinnedHeadPreference()
static double GetPinnedHeadPositionPreference()
static void SetPinnedHeadPositionPreference(double value, bool flush=false)
Holds a msgid for the translation catalog; may also bind format arguments.
wxString Translation() const
TranslatableString Stripped(unsigned options=MenuCodes) const
non-mutating, constructs another TranslatableString object
Short-lived drawing and event-handling object associated with a TrackPanelCell.
Definition: UIHandle.h:35
Result mChangeHighlight
Definition: UIHandle.h:139
unsigned Result
Definition: UIHandle.h:38
virtual HitTestPreview Preview(const TrackPanelMouseState &state, AudacityProject *pProject)=0
bool bUpdateTrackIndicator
Definition: ViewInfo.h:242
PlayRegion playRegion
Definition: ViewInfo.h:220
NotifyingSelectedRegion selectedRegion
Definition: ViewInfo.h:219
static int UpdateScrollPrefsID()
Definition: ViewInfo.cpp:325
static ViewInfo & Get(AudacityProject &project)
Definition: ViewInfo.cpp:235
double PositionToTime(int64 position, int64 origin=0, bool ignoreFisheye=false) const
Definition: ZoomInfo.cpp:35
int64 TimeToPosition(double time, int64 origin=0, bool ignoreFisheye=false) const
STM: Converts a project time to screen x position.
Definition: ZoomInfo.cpp:45
static UIHandle::Result NeedChangeHighlight(const PlayheadHandle &oldState, const PlayheadHandle &newState)
HitTestPreview Preview(const TrackPanelMouseState &, AudacityProject *) override
static std::shared_ptr< PlayheadHandle > HitTest(const AudacityProject *pProject, AdornedRulerPanel &parent, wxCoord xx)
Result Click(const TrackPanelMouseEvent &event, AudacityProject *) override
Result Release(const TrackPanelMouseEvent &event, AudacityProject *pProject, wxWindow *) override
Result Drag(const TrackPanelMouseEvent &event, AudacityProject *pProject) override
void SetLabel(const TranslatableString &label)
void SetFocus(const WindowPlacement &focus)
Set the window that accepts keyboard input.
Definition: BasicUI.h:352
void CallAfter(Action action)
Schedule an action to be done later, and in the main thread.
Definition: BasicUI.cpp:208
bool Begin(const FilePath &dataDir)
Definition: Journal.cpp:226
void Release(wxWindow *handler)
std::unique_ptr< CommandItem > Command(const CommandID &name, const TranslatableString &label_in, void(Handler::*pmf)(const CommandContext &), CommandFlag flags, const CommandManager::Options &options={}, CommandHandlerFinder finder=FinderScope::DefaultFinder())
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
void SetPlayRegionToSelection(AudacityProject &project)
void ActivatePlayRegion(AudacityProject &project)
void InactivatePlayRegion(AudacityProject &project)
void ClearPlayRegion(AudacityProject &project)
void TogglePlayRegion(AudacityProject &project)
double GetPlayHeadFraction(const AudacityProject *pProject, wxCoord xx)
void OnTogglePinnedHead(const CommandContext &context)
const TranslatableString ScrubbingMessage(const Scrubber &scrubber, bool clicked)
const TranslatableString StartScrubbingMessage(const Scrubber &)
wxCoord GetPlayHeadX(const AudacityProject *pProject)
const TranslatableString ContinueScrubbingMessage(const Scrubber &scrubber, bool clicked)
AttachedWindows::RegisteredFactory sKey
MainGroup(const AdornedRulerPanel &ruler)
Subdivision Children(const wxRect &) override
const AdornedRulerPanel & mRuler
const AdornedRulerPanel & mRuler
Subgroup(const AdornedRulerPanel &ruler)
Subdivision Children(const wxRect &) override
bool on
Definition: AudioIO.h:77
enum AudioIOEvent::Type type
Options && CheckTest(const CheckFn &fn) &&
Default message type for Publisher.
Definition: Observer.h:26
std::optional< PreferredSystemAppearance > appearance
Definition: Theme.h:112
Window placement information for wxWidgetsBasicUI can be constructed from a wxWindow pointer.