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 "CellularPanel.h"
33#include "../images/Cursors.h"
34#include "HitTestResult.h"
35#include "Menus.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"
53#include "tracks/ui/Scrubbing.h"
54#include "tracks/ui/TrackView.h"
55#include "widgets/AButton.h"
57#include "widgets/Grabber.h"
59
60#include <wx/dcclient.h>
61#include <wx/menu.h>
62
63using std::min;
64using std::max;
65
66//#define SCRUB_ABOVE
67
68#define SELECT_TOLERANCE_PIXEL 4
69
70#define PLAY_REGION_TRIANGLE_SIZE 6
71#define PLAY_REGION_RECT_WIDTH 1
72#define PLAY_REGION_RECT_HEIGHT 3
73#define PLAY_REGION_GLOBAL_OFFSET_Y 7
74
75enum : int {
79
81 BottomMargin = 2, // for bottom bevel and bottom line
83
85};
86
87enum {
90};
91
92inline int IndicatorHeightForWidth(int width)
93{
94 return ((width / 2) * 3) / 2;
95}
96
97inline int IndicatorWidthForHeight(int height)
98{
99 // Not an exact inverse of the above, with rounding, but good enough
100 return std::max(static_cast<int>(IndicatorSmallWidth),
101 (((height) * 2) / 3) * 2
102 );
103}
104
106{
107 return std::max((int)(ScrubHeight - TopMargin),
108 (int)(IndicatorMediumWidth));
109}
110
112{
114}
115
117{
118public:
120 AdornedRulerPanel *pParent, wxCoord xx, MenuChoice menuChoice )
121 : mParent(pParent)
122 , mX( xx )
123 , mClickedX( xx )
124 , mChoice( menuChoice )
125 {}
126
127 bool Clicked() const { return mClicked != Button::None; }
128
130 const CommonRulerHandle &oldState, const CommonRulerHandle &newState)
131 {
132 if (oldState.mX != newState.mX)
134 return 0;
135 }
136
137protected:
138 bool HandlesRightClick() override { return true; }
139
141 const TrackPanelMouseEvent &event, AudacityProject *) override
142 {
143 mClicked = event.event.LeftIsDown() ? Button::Left : Button::Right;
144 mClickedX = event.event.GetX();
146 }
147
149 const TrackPanelMouseEvent &, AudacityProject *) override
150 {
152 }
153
156 wxWindow *) override
157 {
158 if ( mParent && mClicked == Button::Right ) {
159 const auto pos = event.event.GetPosition();
160 mParent->ShowContextMenu( mChoice, &pos );
161 }
163 }
164
165 double Time(AudacityProject &project) const
166 {
167 auto &viewInfo = ViewInfo::Get(project);
168 return viewInfo.PositionToTime(mX, viewInfo.GetLeftOffset());
169 }
170
171 // Danger! `this` may be deleted!
172 void StartPlay(AudacityProject &project, const wxMouseEvent &event)
173 {
174 auto &ruler = AdornedRulerPanel::Get(project);
175
176 // Keep a shared pointer to self. Otherwise *this might get deleted
177 // in HandleQPRelease on Windows! Because there is an event-loop yield
178 // stopping playback, which caused OnCaptureLost to be called, which caused
179 // clearing of CellularPanel targets!
180 auto saveMe = ruler.Target();
181
182 const auto startTime = Time(project);
183 ruler.StartQPPlay(!event.ShiftDown(), false, &startTime);
184 }
185
187 {
189 }
190
191 void Enter(bool, AudacityProject *) override
192 {
194 }
195
196 wxWeakRef<AdornedRulerPanel> mParent;
197
198 wxCoord mX;
199 wxCoord mClickedX;
200
202
203 enum class Button { None, Left, Right };
205};
206
208public:
210 AdornedRulerPanel *pParent, wxCoord xx, MenuChoice menuChoice,
211 wxCursor cursor,
212 size_t numGuides = 1)
213 : CommonRulerHandle{ pParent, xx, menuChoice }
214 , mCursor{ cursor }
215 , mNumGuides{ numGuides }
216 {}
217
219 const TrackPanelMouseState &state, AudacityProject *pProject)
220 override
221 {
222 mParent->SetNumGuides(mNumGuides);
223 const auto message = XO("Click and drag to define a looping region.");
224 return {
225 message,
226 &mCursor,
227 message
228 };
229 }
230
232 const TrackPanelMouseEvent &event, AudacityProject *pProject) override
233 {
234 using namespace RefreshCode;
235 auto result = CommonRulerHandle::Drag(event, pProject);
236 if (0 != (result & Cancelled) || mClicked != Button::Left)
237 return result;
238
239 auto &ruler = AdornedRulerPanel::Get(*pProject);
240 mX = event.event.m_x;
241 ruler.UpdateQuickPlayPos(event.event.m_x);
242
243 if (!mDragged) {
244 if (fabs(mX - mClickedX) < SELECT_TOLERANCE_PIXEL)
245 // Don't start a drag yet for a small mouse movement
246 return RefreshNone;
247 SavePlayRegion(*pProject);
248 const auto time = SnappedTime(*pProject, 0);
249 DoStartAdjust(*pProject, time);
250 mDragged = true;
251 }
252 else
253 DoAdjust(*pProject);
254
256 DragSelection(*pProject);
257
258 return RefreshAll;
259 }
260
262 const TrackPanelMouseEvent &event,
263 AudacityProject *pProject, wxWindow *pParent)
264 override
265 {
266 using namespace RefreshCode;
267 auto result = CommonRulerHandle::Release(event, pProject, pParent);
268
269 if (mClicked == Button::Left && !mDragged)
270 StartPlay(*pProject, event.event);
271
272 if (!mDragged || 0 != (result & Cancelled))
273 return result;
274
275 // Set the play region endpoints correctly even if not strictly needed
276 auto &viewInfo = ViewInfo::Get( *pProject );
277 auto &playRegion = viewInfo.playRegion;
278 playRegion.Order();
279
280 return result;
281 }
282
283 Result Cancel(AudacityProject *pProject) override
284 {
285 using namespace RefreshCode;
286 auto result = CommonRulerHandle::Cancel(pProject);
287 if (!mSaved)
288 return result;
289
291 auto &viewInfo = ViewInfo::Get(*pProject);
292 viewInfo.selectedRegion = mOldSelectedRegion;
293 auto &playRegion = viewInfo.playRegion;
294 playRegion.SetTimes(mOldStart, mOldEnd);
295 if (!mWasActive)
296 playRegion.SetActive(false);
297
298 return RefreshAll;
299 }
300
301 // Compare with class SelectHandle. Perhaps a common base class for that
302 // class too should be defined.
303 bool HasEscape(AudacityProject *pProject) const override
304 {
305 auto &ruler = AdornedRulerPanel::Get(*pProject);
306 auto values = ruler.mIsSnapped;
307 auto identity = [](auto x){ return x; }; // in the C++20 standard...
308 return std::any_of( values, values + ruler.mNumGuides, identity );
309 }
310
311 bool Escape(AudacityProject *pProject) override
312 {
313 if (HasEscape(pProject)) {
314 Unsnap(false, pProject);
315 return true;
316 }
317 return false;
318 }
319
320protected:
321
322 double SnappedTime( AudacityProject &project, size_t ii )
323 {
324 const auto &ruler = AdornedRulerPanel::Get(project);
325 bool isSnapped = ruler.mIsSnapped[ii];
326 return isSnapped
327 ? ruler.mQuickPlayPos[ii]
328 : ruler.mQuickPlayPosUnsnapped[ii];
329 }
330
331 std::pair<double, double> SnappedTimes( AudacityProject &project )
332 {
333 const auto &ruler = AdornedRulerPanel::Get(project);
334 for (size_t ii = 0; ii < ruler.mNumGuides; ++ii)
335 if (ruler.mIsSnapped[ii]) {
336 double time0 = ruler.mQuickPlayPos[ii];
337 double delta = time0 - ruler.mQuickPlayPosUnsnapped[ii];
338 double time1 = ruler.mQuickPlayPosUnsnapped[1 - ii] + delta;
339 if (ii == 1)
340 return { time1, time0 };
341 else
342 return { time0, time1 };
343 }
344 // No snap
345 return { ruler.mQuickPlayPos[0], ruler.mQuickPlayPos[1] };
346 }
347
348 void Unsnap(bool use, AudacityProject *pProject)
349 {
350 auto &ruler = AdornedRulerPanel::Get(*pProject);
351 std::fill( ruler.mIsSnapped, ruler.mIsSnapped + ruler.mNumGuides, false );
352 // Repaint to turn the snap lines on or off
354 if (Clicked())
355 DoAdjust(*pProject);
356 }
357
358 virtual void DoStartAdjust(AudacityProject &, double) = 0;
359 virtual void DoAdjust(AudacityProject &) = 0;
360
362 {
363 auto &viewInfo = ViewInfo::Get(project);
364 mOldSelectedRegion = viewInfo.selectedRegion;
365 auto &playRegion = viewInfo.playRegion;
366 mWasActive = playRegion.Active();
367 mOldStart = playRegion.GetLastActiveStart();
368 mOldEnd = playRegion.GetLastActiveEnd();
369 if (!mWasActive)
370 playRegion.SetActive(true);
371 mSaved = true;
372 }
373
374private:
375 // wxCursor is a cheaply copied reference-counting handle to a resource
376 wxCursor mCursor;
378
380 double mOldStart = 0.0, mOldEnd = 0.0;
381 bool mWasActive = false;
382 bool mSaved = false;
383 bool mDragged = false;
384};
385
386/**********************************************************************
387
388ScrubbingRulerOverlay.
389Graphical helper for AdornedRulerPanel.
390
391**********************************************************************/
392
394
395// This is an overlay drawn on the ruler.
397{
398public:
400
403
404 bool mNewScrub {};
405 bool mNewSeek {};
406
407 void Update();
408
409private:
411
412 unsigned SequenceNumber() const override;
413
414 std::pair<wxRect, bool> DoGetRectangle(wxSize size) override;
415 void Draw(OverlayPanel &panel, wxDC &dc) override;
416
418
419 // Used by this only
421 bool mOldScrub {};
422 bool mOldSeek {};
423};
424
425/**********************************************************************
426
427 TrackPanelGuidelineOverlay.
428 Updated for mouse events in AdornedRulerPanel, but draws on the TrackPanel.
429
430 **********************************************************************/
431
432// This is an overlay drawn on a different window, the track panel.
433// It draws the pale guide line that follows mouse movement.
435{
438
439public:
441
442private:
443 void Update();
444
445 unsigned SequenceNumber() const override;
446 std::pair<wxRect, bool> DoGetRectangle(wxSize size) override;
447 void Draw(OverlayPanel &panel, wxDC &dc) override;
448
450
451 std::shared_ptr<ScrubbingRulerOverlay> mPartner
452 { std::make_shared<ScrubbingRulerOverlay>(*this) };
453
455
459};
460
461/**********************************************************************
462
463 Implementation of ScrubbingRulerOverlay.
464
465 **********************************************************************/
466
469: mPartner(partner)
470{
471}
472
474{
475 return &Get( *mPartner.mProject );
476}
477
479{
480 const auto project = mPartner.mProject;
481 auto &scrubber = Scrubber::Get( *project );
482 auto ruler = GetRuler();
483
484 bool scrubbing = (scrubber.IsScrubbing()
485 && !scrubber.IsSpeedPlaying()
486 && !scrubber.IsKeyboardScrubbing());
487
488 // Hide during transport, or if mouse is not in the ruler, unless scrubbing
489 if ((!ruler->LastCell() || ProjectAudioIO::Get( *project ).IsAudioActive())
490 && !scrubbing)
491 mNewQPIndicatorPos = -1;
492 else {
493 const auto &selectedRegion = ViewInfo::Get( *project ).selectedRegion;
494 double latestEnd =
495 std::max(ruler->mTracks->GetEndTime(), selectedRegion.t1());
496 // This will determine the x coordinate of the line and of the
497 // ruler indicator
498
499 // Test all snap points
500 mNewIndicatorSnapped = -1;
501 for (size_t ii = 0;
502 mNewIndicatorSnapped == -1 && ii < ruler->mNumGuides; ++ii) {
503 if (ruler->mIsSnapped[ii]) {
504 mNewIndicatorSnapped = ii;
505 }
506 }
507 mNewQPIndicatorPos = ruler->Time2Pos(
508 ruler->mQuickPlayPos[std::max(0, mNewIndicatorSnapped)]);
509
510 // These determine which shape is drawn on the ruler, and whether
511 // in the scrub or the qp zone
512 mNewScrub =
513 !ruler->IsMouseCaptured() && // not doing some other drag in the ruler
514 (ruler->LastCell() == ruler->mScrubbingCell ||
515 (scrubber.HasMark()));
516 mNewSeek = mNewScrub &&
517 (scrubber.Seeks() || scrubber.TemporarilySeeks());
518 }
519}
520
521unsigned
523{
524 return 30;
525}
526
527std::pair<wxRect, bool>
529{
530 Update();
531
532 const auto x = mOldQPIndicatorPos;
533 if (x >= 0) {
534 // These dimensions are always sufficient, even if a little
535 // excessive for the small triangle:
536 const int width = IndicatorBigWidth() * 3 / 2;
537 //const auto height = IndicatorHeightForWidth(width);
538
539 const int indsize = width / 2;
540
541 auto xx = x - indsize;
542 auto yy = 0;
543 return {
544 { xx, yy,
545 indsize * 2 + 1,
546 GetRuler()->GetSize().GetHeight() },
547 (x != mNewQPIndicatorPos
548 || (mOldScrub != mNewScrub)
549 || (mOldSeek != mNewSeek) )
550 };
551 }
552 else
553 return { {}, mNewQPIndicatorPos >= 0 };
554}
555
557 OverlayPanel & /*panel*/, wxDC &dc)
558{
559 mOldQPIndicatorPos = mNewQPIndicatorPos;
560 mOldScrub = mNewScrub;
561 mOldSeek = mNewSeek;
562 if (mOldQPIndicatorPos >= 0) {
563 auto ruler = GetRuler();
564 auto width = mOldScrub ? IndicatorBigWidth() : IndicatorSmallWidth;
565 ruler->DoDrawScrubIndicator(
566 &dc, mOldQPIndicatorPos, width, mOldScrub, mOldSeek);
567 }
568}
569
570/**********************************************************************
571
572 Implementation of TrackPanelGuidelineOverlay.
573
574 **********************************************************************/
575
577 AudacityProject *project)
578 : mProject(project)
579{
580}
581
582unsigned
584{
585 return 30;
586}
587
589{
590 const auto project = mProject;
591 auto &scrubber = Scrubber::Get( *project );
592 const auto ruler = &Get( *project );
593
594 // Determine the color of the line stroked over
595 // the track panel, green for scrub or yellow for snapped or white
596 mNewPreviewingScrub =
597 ruler->LastCell() == ruler->mScrubbingCell &&
598 !scrubber.IsScrubbing();
599}
600
601std::pair<wxRect, bool>
603{
604 Update();
605
606 wxRect rect(mOldQPIndicatorPos, 0, 1, size.GetHeight());
607 return std::make_pair(
608 rect,
609 (mOldQPIndicatorPos != mPartner->mNewQPIndicatorPos ||
610 mOldIndicatorSnapped != mPartner->mNewIndicatorSnapped ||
611 mOldPreviewingScrub != mNewPreviewingScrub)
612 );
613}
614
616 OverlayPanel &panel, wxDC &dc)
617{
618 mOldQPIndicatorPos = mPartner->mNewQPIndicatorPos;
619 mOldIndicatorSnapped = mPartner->mNewIndicatorSnapped;
620 mOldPreviewingScrub = mNewPreviewingScrub;
621
622 if (mOldQPIndicatorPos >= 0) {
623 if (!mOldPreviewingScrub && mOldIndicatorSnapped < 0) {
625 if (auto pHandle =
626 dynamic_cast<PlayRegionAdjustingHandle*>(ruler.Target().get());
627 pHandle != nullptr && pHandle->Clicked())
628 // Do not draw the quick-play guideline
629 return;
630 }
631
632 mOldPreviewingScrub
633 ? AColor::IndicatorColor(&dc, true) // Draw green line for preview.
634 : (mOldIndicatorSnapped >= 0)
635 ? AColor::SnapGuidePen(&dc) // Yellow snap guideline
636 : AColor::Light(&dc, false);
637
638 // Draw indicator in all visible tracks
639 auto pCellularPanel = dynamic_cast<CellularPanel*>( &panel );
640 if ( !pCellularPanel ) {
641 wxASSERT( false );
642 return;
643 }
644 pCellularPanel
645 ->VisitCells( [&]( const wxRect &rect, TrackPanelCell &cell ) {
646 const auto pTrackView = dynamic_cast<TrackView*>(&cell);
647 if (!pTrackView)
648 return;
649
650 // Draw the NEW indicator in its NEW location
651 AColor::Line(dc,
652 mOldQPIndicatorPos,
653 rect.GetTop(),
654 mOldQPIndicatorPos,
655 rect.GetBottom());
656 } );
657 }
658}
659
660/**********************************************************************
661
662 Implementation of AdornedRulerPanel.
663 Either we find a way to make this more generic, Or it will move
664 out of the widgets subdirectory into its own source file.
665
666**********************************************************************/
667
668enum {
675};
676
677BEGIN_EVENT_TABLE(AdornedRulerPanel, CellularPanel)
678 EVT_IDLE( AdornedRulerPanel::OnIdle )
681 EVT_LEAVE_WINDOW(AdornedRulerPanel::OnLeave)
682
683 // Context menu commands
691
695
697
699{
700public:
701 explicit
703 : mParent{ parent }
704 , mMenuChoice{ menuChoice }
705 {}
706
708 const TrackPanelMouseState &, const AudacityProject *)
709 override
710 {
711 // May come here when recording is in progress, so hit tests are turned
712 // off.
713 TranslatableString tooltip;
714 if (mParent->mTimelineToolTip)
715 tooltip = XO("Timeline actions disabled during recording");
716
717 static wxCursor cursor{ wxCURSOR_DEFAULT };
718 return {
719 {},
720 &cursor,
721 tooltip,
722 };
723 }
724
726 const wxRect &,
727 wxWindow *, const wxPoint *pPosition, AudacityProject*) final
728 {
729 mParent->ShowContextMenu(mMenuChoice, pPosition);
730 return 0;
731 }
732
733protected:
736};
737
738#undef QUICK_PLAY_HANDLE
739#ifdef QUICK_PLAY_HANDLE
740class AdornedRulerPanel::QPHandle final : public CommonRulerHandle
741{
742public:
743 explicit
744 QPHandle( AdornedRulerPanel *pParent, wxCoord xx )
746 {
747 }
748
749private:
751 const TrackPanelMouseEvent &event, AudacityProject *pProject) override;
752
753 Result Drag(
754 const TrackPanelMouseEvent &event, AudacityProject *pProject) override;
755
757 const TrackPanelMouseState &state, AudacityProject *pProject)
758 override;
759
761 const TrackPanelMouseEvent &event, AudacityProject *pProject,
762 wxWindow *pParent) override;
763
764 Result Cancel(AudacityProject *pProject) override;
765
766 SelectedRegion mOldSelection;
767};
768#endif
769
770static auto handOpenCursor =
771 MakeCursor(wxCURSOR_HAND, RearrangeCursorXpm, 16, 16);
772
774public:
776 : PlayRegionAdjustingHandle( pParent, xx, MenuChoice::QuickPlay, *handOpenCursor, 2 )
777 {
778 }
779
781 {
782 mParent->mQuickPlayOffset[0] = 0;
783 mParent->mQuickPlayOffset[1] = 0;
784 }
785
786private:
787 void DoStartAdjust(AudacityProject &project, double) override
788 {
789 // Ignore the snapping of the time at the mouse
790 const auto time = Time(project);
791 auto &playRegion = ViewInfo::Get(project).playRegion;
792 mParent->mQuickPlayOffset[0] = playRegion.GetStart() - time;
793 mParent->mQuickPlayOffset[1] = playRegion.GetEnd() - time;
794 }
795
796 void DoAdjust(AudacityProject &project) override
797 {
798 // Move the whole play region rigidly (usually)
799 // though the length might change slightly if only one edge snaps
800 const auto times = SnappedTimes(project);
801
802 auto &playRegion = ViewInfo::Get(project).playRegion;
803 playRegion.SetTimes(times.first, times.second);
804 }
805};
806
808public:
810 AdornedRulerPanel *pParent, wxCoord xx, bool hitLeft )
811 : PlayRegionAdjustingHandle( pParent, xx, MenuChoice::QuickPlay, {wxCURSOR_SIZEWE} )
812 , mHitLeft{ hitLeft }
813 {
814 }
815
816private:
817 void DoStartAdjust(AudacityProject &project, double time) override
818 {
819 }
820
821 void DoAdjust(AudacityProject &project) override
822 {
823 const auto time = SnappedTime(project, 0);
824
825 // Change the play region
826 // The check whether this new time should be start or end isn't
827 // important. The accessors for PlayRegion use min and max of stored
828 // values.
829 auto &playRegion = ViewInfo::Get(project).playRegion;
830 if (mHitLeft)
831 playRegion.SetStart(time);
832 else
833 playRegion.SetEnd(time);
834 }
835
836 bool mHitLeft = false;
837};
838
840public:
842 : PlayRegionAdjustingHandle( pParent, xx, MenuChoice::QuickPlay, {wxCURSOR_DEFAULT} )
843 {
844 }
845
846private:
847 void DoStartAdjust(AudacityProject &project, double time) override
848 {
849 auto &playRegion = ViewInfo::Get(project).playRegion;
850 playRegion.SetTimes(time, time);
851 }
852
853 void DoAdjust(AudacityProject &project) override
854 {
855 const auto time = SnappedTime(project, 0);
856
857 // Change the play region
858 // The check whether this new time should be start or end isn't
859 // important. The accessors for PlayRegion use min and max of stored
860 // values.
861 auto &playRegion = ViewInfo::Get(project).playRegion;
862 playRegion.SetEnd(time);
863 }
864};
865
866namespace
867{
868
869wxCoord GetPlayHeadX( const AudacityProject *pProject )
870{
871 const auto &viewInfo = ViewInfo::Get( *pProject );
872 auto width = viewInfo.GetTracksUsableWidth();
873 return viewInfo.GetLeftOffset()
875}
876
877double GetPlayHeadFraction( const AudacityProject *pProject, wxCoord xx )
878{
879 const auto &viewInfo = ViewInfo::Get( *pProject );
880 auto width = viewInfo.GetTracksUsableWidth();
881 auto fraction = (xx - viewInfo.GetLeftOffset()) / double(width);
882 return std::max(0.0, std::min(1.0, fraction));
883}
884
885// Handle for dragging the pinned play head, which so far does not need
886// to be a friend of the AdornedRulerPanel class, so we don't make it nested.
888{
889public:
890 PlayheadHandle( AdornedRulerPanel &parent, wxCoord xx )
891 : mpParent{ &parent }
892 , mX( xx )
893 {}
894
896 const PlayheadHandle &oldState, const PlayheadHandle &newState)
897 {
898 if (oldState.mX != newState.mX)
900 return 0;
901 }
902
903 static std::shared_ptr<PlayheadHandle>
905 const AudacityProject *pProject, AdornedRulerPanel &parent, wxCoord xx )
906 {
907 if( Scrubber::Get( *pProject )
908 .IsTransportingPinned() &&
909 ProjectAudioIO::Get( *pProject ).IsAudioActive() )
910 {
911 const auto targetX = GetPlayHeadX( pProject );
912 if ( abs( xx - targetX ) <= SELECT_TOLERANCE_PIXEL )
913 return std::make_shared<PlayheadHandle>( parent, xx );
914 }
915 return {};
916 }
917
918protected:
920 const TrackPanelMouseEvent &event, AudacityProject *) override
921 {
922 if (event.event.LeftDClick()) {
923 // Restore default position on double click
925
927 // Do not start a drag
929 }
930 // Fix for Bug 2357
931 if (!event.event.LeftIsDown())
933
935 return 0;
936 }
937
939 const TrackPanelMouseEvent &event, AudacityProject *pProject) override
940 {
941
942 auto value = GetPlayHeadFraction(pProject, event.event.m_x );
945 }
946
949 override
950 {
951 mpParent->SetNumGuides(1);
952 static wxCursor cursor{ wxCURSOR_SIZEWE };
953 return {
954 XO( "Click and drag to adjust, double-click to reset" ),
955 &cursor,
956 /* i18n-hint: This text is a tooltip on the icon (of a pin) representing
957 the temporal position in the audio. */
958 XO( "Record/Play head" )
959 };
960 }
961
963 const TrackPanelMouseEvent &event, AudacityProject *pProject,
964 wxWindow *) override
965 {
966 auto value = GetPlayHeadFraction(pProject, event.event.m_x );
969 }
970
972 {
975 }
976
977 void Enter(bool, AudacityProject *) override
978 {
979 mChangeHighlight = RefreshCode::DrawOverlays;
980 }
981
983 wxCoord mX;
984 double mOrigPreference {};
985};
986
987}
988
990{
991public:
992 explicit
994 : AdornedRulerPanel::CommonCell{ parent, MenuChoice::QuickPlay }
995 {}
996
997 std::vector<UIHandlePtr> HitTest(
998 const TrackPanelMouseState &state,
999 const AudacityProject *pProject) override;
1000
1001 // Return shared_ptr to self, stored in parent
1002 std::shared_ptr<TrackPanelCell> ContextMenuDelegate() override
1003 { return mParent->mQPCell; }
1004
1005 bool Clicked() const {
1006#ifdef QUICK_PLAY_HANDLE
1007 if (auto ptr = mHolder.lock())
1008 return ptr->Clicked();
1009#endif
1010 return false;
1011 }
1012
1013#ifdef QUICK_PLAY_HANDLE
1014 std::weak_ptr<QPHandle> mHolder;
1015#endif
1016
1017 std::weak_ptr<ResizePlayRegionHandle> mResizePlayRegionHolder;
1018 std::weak_ptr<MovePlayRegionHandle> mMovePlayRegionHolder;
1019 std::weak_ptr<NewPlayRegionHandle> mNewPlayRegionHolder;
1020 std::weak_ptr<PlayheadHandle> mPlayheadHolder;
1021};
1022
1023std::vector<UIHandlePtr> AdornedRulerPanel::QPCell::HitTest(
1024 const TrackPanelMouseState &state,
1025 const AudacityProject *pProject)
1026{
1027 // Creation of overlays on demand here -- constructor of AdornedRulerPanel
1028 // is too early to do it
1029 mParent->CreateOverlays();
1030
1031 std::vector<UIHandlePtr> results;
1032 auto xx = state.state.m_x;
1033
1034#ifdef EXPERIMENTAL_DRAGGABLE_PLAY_HEAD
1035 {
1036 // Allow click and drag on the play head even while recording
1037 // Make this handle more prominent then the quick play handle
1038 auto result = PlayheadHandle::HitTest( pProject, *mParent, xx );
1039 if (result) {
1040 result = AssignUIHandlePtr( mPlayheadHolder, result );
1041 results.push_back( result );
1042 }
1043 }
1044#endif
1045
1046 // Disable mouse actions on Timeline while recording.
1047 if (!mParent->mIsRecording) {
1048 mParent->UpdateQuickPlayPos( xx );
1049
1050 #if 0
1051 auto result = std::make_shared<QPHandle>( mParent, xx );
1052 result = AssignUIHandlePtr( mHolder, result );
1053 results.push_back( result );
1054 #endif
1055 }
1056
1057 // High priority hit is a handle to change the existing play region
1058 bool hitLeft = false;
1059 const auto &playRegion = ViewInfo::Get(*pProject).playRegion;
1060 if ((hitLeft =
1061 mParent->IsWithinMarker(xx, playRegion.GetLastActiveStart())) ||
1062 mParent->IsWithinMarker(xx, playRegion.GetLastActiveEnd()))
1063 {
1064 auto result =
1065 std::make_shared<ResizePlayRegionHandle>( mParent, xx, hitLeft );
1066 result = AssignUIHandlePtr( mResizePlayRegionHolder, result );
1067 results.push_back(result);
1068 }
1069
1070 // Middle priority hit is a handle to change the existing play region at
1071 // both ends, but only when the play region is active
1072 if (auto time = mParent->Pos2Time(xx);
1073 playRegion.Active() &&
1074 time >= playRegion.GetStart() &&
1075 time <= playRegion.GetEnd())
1076 {
1077 auto result =
1078 std::make_shared<MovePlayRegionHandle>( mParent, xx );
1079 result = AssignUIHandlePtr( mMovePlayRegionHolder, result );
1080 results.push_back(result);
1081 }
1082
1083 // Lowest priority hit is a handle to drag a completely new play region
1084 {
1085 auto result = std::make_shared<NewPlayRegionHandle>( mParent, xx );
1086 result = AssignUIHandlePtr( mNewPlayRegionHolder, result );
1087 results.push_back(result);
1088 }
1089
1090 return results;
1091}
1092
1094{
1095public:
1096 explicit
1097 ScrubbingHandle( AdornedRulerPanel *pParent, wxCoord xx )
1098 : CommonRulerHandle( pParent, xx, MenuChoice::Scrub )
1099 {
1100 }
1101
1102private:
1104 const TrackPanelMouseEvent &event, AudacityProject *pProject) override
1105 {
1106 auto result = CommonRulerHandle::Click(event, pProject);
1107 if (!( result & RefreshCode::Cancelled )) {
1108 if (mClicked == Button::Left) {
1109 auto &scrubber = Scrubber::Get( *pProject );
1110 // only if scrubbing is allowed now
1111 bool canScrub =
1112 scrubber.CanScrub() &&
1113 mParent &&
1114 mParent->ShowingScrubRuler();
1115
1116 if (!canScrub)
1118 if (!scrubber.HasMark()) {
1119 // Asynchronous scrub poller gets activated here
1120 scrubber.MarkScrubStart(
1121 event.event.m_x, Scrubber::ShouldScrubPinned(), false);
1122 }
1123 }
1124 }
1125 return result;
1126 }
1127
1129 const TrackPanelMouseEvent &event, AudacityProject *pProject) override
1130 {
1131 auto result = CommonRulerHandle::Drag(event, pProject);
1132 if (!( result & RefreshCode::Cancelled )) {
1133 // Nothing needed here. The scrubber works by polling mouse state
1134 // after the start has been marked.
1135 }
1136 return result;
1137 }
1138
1140 const TrackPanelMouseState &state, AudacityProject *pProject)
1141 override;
1142
1144 const TrackPanelMouseEvent &event, AudacityProject *pProject,
1145 wxWindow *pParent) override {
1146 auto result = CommonRulerHandle::Release(event, pProject, pParent);
1147 if (!( result & RefreshCode::Cancelled )) {
1148 // Nothing needed here either. The scrub poller may have decided to
1149 // seek because a drag happened before button up, or it may decide
1150 // to start a scrub, as it watches mouse movement after the button up.
1151 }
1152 return result;
1153 }
1154
1155 Result Cancel(AudacityProject *pProject) override
1156 {
1157 auto result = CommonRulerHandle::Cancel(pProject);
1158
1159 if (mClicked == Button::Left) {
1160 auto &scrubber = Scrubber::Get( *pProject );
1161 scrubber.Cancel();
1162
1163 ProjectAudioManager::Get( *pProject ).Stop();
1164 }
1165
1166 return result;
1167 }
1168};
1169
1171{
1172public:
1173 explicit
1175 : AdornedRulerPanel::CommonCell{ parent, MenuChoice::Scrub }
1176 {}
1177
1178 std::vector<UIHandlePtr> HitTest(
1179 const TrackPanelMouseState &state,
1180 const AudacityProject *pProject) override;
1181
1182 // Return shared_ptr to self, stored in parent
1183 std::shared_ptr<TrackPanelCell> ContextMenuDelegate() override
1184 { return mParent->mScrubbingCell; }
1185
1186 bool Hit() const { return !mHolder.expired(); }
1187 bool Clicked() const {
1188 if (auto ptr = mHolder.lock())
1189 return ptr->Clicked();
1190 return false;
1191 }
1192
1193private:
1194 std::weak_ptr<ScrubbingHandle> mHolder;
1195};
1196
1198 const TrackPanelMouseState &state, const AudacityProject *)
1199{
1200 // Creation of overlays on demand here -- constructor of AdornedRulerPanel
1201 // is too early to do it
1202 mParent->CreateOverlays();
1203
1204 std::vector<UIHandlePtr> results;
1205
1206 // Disable mouse actions on Timeline while recording.
1207 if (!mParent->mIsRecording) {
1208 auto xx = state.state.m_x;
1209 mParent->UpdateQuickPlayPos( xx );
1210 auto result = std::make_shared<ScrubbingHandle>( mParent, xx );
1211 result = AssignUIHandlePtr( mHolder, result );
1212 results.push_back( result );
1213 }
1214
1215 return results;
1216}
1217
1218namespace{
1219AttachedWindows::RegisteredFactory sKey{
1220[]( AudacityProject &project ) -> wxWeakRef< wxWindow > {
1221 auto &viewInfo = ViewInfo::Get( project );
1222 auto &window = ProjectWindow::Get( project );
1223
1224 return safenew AdornedRulerPanel( &project, window.GetTrackListWindow(),
1225 wxID_ANY,
1226 wxDefaultPosition,
1227 wxSize( -1, AdornedRulerPanel::GetRulerHeight(false) ),
1228 &viewInfo );
1229}
1230};
1231}
1232
1234{
1235 return GetAttachedWindows(project).Get< AdornedRulerPanel >( sKey );
1236}
1237
1239 const AudacityProject &project )
1240{
1241 return Get( const_cast< AudacityProject & >( project ) );
1242}
1243
1245{
1246 auto *pPanel = GetAttachedWindows(project).Find( sKey );
1247 if (pPanel) {
1248 pPanel->wxWindow::Destroy();
1249 GetAttachedWindows(project).Assign( sKey, nullptr );
1250 }
1251}
1252
1254 wxWindow *parent,
1255 wxWindowID id,
1256 const wxPoint& pos,
1257 const wxSize& size,
1258 ViewInfo *viewinfo)
1259: CellularPanel(parent, id, pos, size, viewinfo)
1260, mProject(project)
1261{
1262 SetLayoutDirection(wxLayout_LeftToRight);
1263
1264 mQPCell = std::make_shared<QPCell>( this );
1265 mScrubbingCell = std::make_shared<ScrubbingCell>( this );
1266
1267 for (auto &button : mButtons)
1268 button = nullptr;
1269
1270 SetLabel( XO("Timeline") );
1271 SetName();
1272 SetBackgroundStyle(wxBG_STYLE_PAINT);
1273
1274 mLeftOffset = 0;
1275 mIndTime = -1;
1276
1277 mLeftDownClick = -1;
1279 mIsDragging = false;
1280
1281 mOuter = GetClientRect();
1282
1284 mRuler.SetLabelEdges( false );
1286
1287 mTracks = &TrackList::Get( *project );
1288
1289 mIsRecording = false;
1290
1291 mTimelineToolTip = !!gPrefs->Read(wxT("/QuickPlay/ToolTips"), 1L);
1292 mPlayRegionDragsSelection = (gPrefs->Read(wxT("/QuickPlay/DragSelection"), 0L) == 1)? true : false;
1293
1294#if wxUSE_TOOLTIPS
1295 wxToolTip::Enable(true);
1296#endif
1297
1300
1301 // Delay until after CommandManager has been populated:
1303
1306
1307 // Bind event that updates the play region
1310
1311 // And call it once to initialize it
1313}
1314
1316{
1317}
1318
1319void AdornedRulerPanel::Refresh( bool eraseBackground, const wxRect *rect )
1320{
1321 CellularPanel::Refresh( eraseBackground, rect );
1323}
1324
1326{
1327 if (mNeedButtonUpdate) {
1328 // Visit this block once only in the lifetime of this panel
1329 mNeedButtonUpdate = false;
1330 // Do this first time setting of button status texts
1331 // when we are sure the CommandManager is initialized.
1333 }
1334
1335 // Update button texts for language change
1337
1338 mTimelineToolTip = !!gPrefs->Read(wxT("/QuickPlay/ToolTips"), 1L);
1339
1340#ifdef EXPERIMENTAL_SCROLLING_LIMITS
1341#ifdef EXPERIMENTAL_TWO_TONE_TIME_RULER
1342 {
1343 auto scrollBeyondZero = ScrollingPreference.Read();
1344 mRuler.SetTwoTone(scrollBeyondZero);
1345 }
1346#endif
1347#endif
1348}
1349
1351{
1352 // TODO: Should we do this to destroy the grabber??
1353 // Get rid of any children we may have
1354 // DestroyChildren();
1355
1357 SetBackgroundColour(theTheme.Colour( clrMedium ));
1358
1359 for (auto & button : mButtons) {
1360 if (button)
1361 button->Destroy();
1362 button = nullptr;
1363 }
1364
1365 size_t iButton = 0;
1366 // Make the short row of time ruler pushbottons.
1367 // Don't bother with sizers. Their sizes and positions are fixed.
1368 // Add a grabber converted to a spacer.
1369 // This makes it visually clearer that the button is a button.
1370
1371 wxPoint position( 1, 0 );
1372
1373 Grabber * pGrabber = safenew Grabber(this, this->GetId());
1374 pGrabber->SetAsSpacer( true );
1375 //pGrabber->SetSize( 10, 27 ); // default is 10,27
1376 pGrabber->SetPosition( position );
1377
1378 position.x = 12;
1379
1380 auto size = theTheme.ImageSize( bmpRecoloredUpSmall );
1381 size.y = std::min(size.y, GetRulerHeight(false));
1382
1383 auto buttonMaker = [&]
1384 (wxWindowID id, teBmps bitmap, bool toggle)
1385 {
1386 const auto button =
1388 this,
1389 bmpRecoloredUpSmall, bmpRecoloredDownSmall,
1390 bmpRecoloredUpHiliteSmall, bmpRecoloredHiliteSmall,
1391 bitmap, bitmap, bitmap,
1392 id, position, toggle, size
1393 );
1394
1395 position.x += size.GetWidth();
1396 mButtons[iButton++] = button;
1397 return button;
1398 };
1399 auto button = buttonMaker(OnTogglePinnedStateID, bmpPlayPointerPinned, true);
1401 *button, 3,
1402 bmpRecoloredUpSmall, bmpRecoloredDownSmall,
1403 bmpRecoloredUpHiliteSmall, bmpRecoloredHiliteSmall,
1404 //bmpUnpinnedPlayHead, bmpUnpinnedPlayHead, bmpUnpinnedPlayHead,
1405 bmpRecordPointer, bmpRecordPointer, bmpRecordPointer,
1406 size);
1408 *button, 2,
1409 bmpRecoloredUpSmall, bmpRecoloredDownSmall,
1410 bmpRecoloredUpHiliteSmall, bmpRecoloredHiliteSmall,
1411 //bmpUnpinnedPlayHead, bmpUnpinnedPlayHead, bmpUnpinnedPlayHead,
1412 bmpRecordPointerPinned, bmpRecordPointerPinned, bmpRecordPointerPinned,
1413 size);
1415 *button, 1,
1416 bmpRecoloredUpSmall, bmpRecoloredDownSmall,
1417 bmpRecoloredUpHiliteSmall, bmpRecoloredHiliteSmall,
1418 //bmpUnpinnedPlayHead, bmpUnpinnedPlayHead, bmpUnpinnedPlayHead,
1419 bmpPlayPointer, bmpPlayPointer, bmpPlayPointer,
1420 size);
1421
1423}
1424
1426{
1428}
1429
1430namespace {
1432 {
1433#if 0
1434 if(scrubber.Seeks())
1435 /* i18n-hint: These commands assist the user in finding a sound by ear. ...
1436 "Scrubbing" is variable-speed playback, ...
1437 "Seeking" is normal speed playback but with skips
1438 */
1439 return XO("Click or drag to begin Seek");
1440 else
1441 /* i18n-hint: These commands assist the user in finding a sound by ear. ...
1442 "Scrubbing" is variable-speed playback, ...
1443 "Seeking" is normal speed playback but with skips
1444 */
1445 return XO("Click or drag to begin Scrub");
1446#else
1447 /* i18n-hint: These commands assist the user in finding a sound by ear. ...
1448 "Scrubbing" is variable-speed playback, ...
1449 "Seeking" is normal speed playback but with skips
1450 */
1451 return XO("Click & move to Scrub. Click & drag to Seek.");
1452#endif
1453 }
1454
1456 const Scrubber &scrubber, bool clicked)
1457 {
1458#if 0
1459 if(scrubber.Seeks())
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("Move to Seek");
1465 else
1466 /* i18n-hint: These commands assist the user in finding a sound by ear. ...
1467 "Scrubbing" is variable-speed playback, ...
1468 "Seeking" is normal speed playback but with skips
1469 */
1470 return XO("Move to Scrub");
1471#else
1472 if( clicked ) {
1473 // Since mouse is down, mention dragging first.
1474 // IsScrubbing is true if Scrubbing OR seeking.
1475 if( scrubber.IsScrubbing() )
1476 // User is dragging already, explain.
1477 return XO("Drag to Seek. Release to stop seeking.");
1478 else
1479 // User has clicked but not yet moved or released.
1480 return XO("Drag to Seek. Release and move to Scrub.");
1481 }
1482 // Since mouse is up, mention moving first.
1483 return XO("Move to Scrub. Drag to Seek.");
1484#endif
1485 }
1486
1487 const TranslatableString ScrubbingMessage(const Scrubber &scrubber, bool clicked)
1488 {
1489 if (scrubber.HasMark())
1490 return ContinueScrubbingMessage(scrubber, clicked);
1491 else
1492 return StartScrubbingMessage(scrubber);
1493 }
1494}
1495
1496void AdornedRulerPanel::OnIdle( wxIdleEvent &evt )
1497{
1498 evt.Skip();
1499 DoIdle();
1500}
1501
1503{
1504 bool changed = UpdateRects();
1505 changed = SetPanelSize() || changed;
1506
1507 auto &project = *mProject;
1508 auto &viewInfo = ViewInfo::Get( project );
1509 const auto &selectedRegion = viewInfo.selectedRegion;
1510 const auto &playRegion = viewInfo.playRegion;
1511
1512 changed = changed
1513 || mLastDrawnSelectedRegion != selectedRegion
1514 || mLastDrawnPlayRegion != std::pair{
1515 playRegion.GetLastActiveStart(), playRegion.GetLastActiveEnd() }
1516 || mLastDrawnH != viewInfo.h
1517 || mLastDrawnZoom != viewInfo.GetZoom()
1518 || mLastPlayRegionActive != viewInfo.playRegion.Active()
1519 ;
1520 if (changed)
1521 // Cause ruler redraw anyway, because we may be zooming or scrolling,
1522 // showing or hiding the scrub bar, etc.
1523 Refresh();
1524}
1525
1527{
1528 if (evt.type == AudioIOEvent::MONITOR)
1529 return;
1530 if ( evt.type == AudioIOEvent::CAPTURE ) {
1531 if (evt.on)
1532 {
1533 mIsRecording = true;
1534 this->CellularPanel::CancelDragging( false );
1536
1538 }
1539 else {
1540 mIsRecording = false;
1542 }
1543 }
1544
1545 if ( !evt.on )
1546 // So that the play region is updated
1548}
1549
1550void AdornedRulerPanel::OnPaint(wxPaintEvent & WXUNUSED(evt))
1551{
1552 const auto &viewInfo = ViewInfo::Get( *GetProject() );
1553 const auto &playRegion = viewInfo.playRegion;
1554 const auto playRegionBounds = std::pair{
1555 playRegion.GetLastActiveStart(), playRegion.GetLastActiveEnd() };
1556 mLastDrawnH = viewInfo.h;
1557 mLastDrawnZoom = viewInfo.GetZoom();
1558 mLastDrawnPlayRegion = playRegionBounds;
1559 mLastDrawnSelectedRegion = viewInfo.selectedRegion;
1560 // To do, note other fisheye state when we have that
1561
1562 wxPaintDC dc(this);
1563
1564 auto &backDC = GetBackingDCForRepaint();
1565
1566 DoDrawBackground(&backDC);
1567
1568 // Find play region rectangle, selected rectangle, and their overlap
1569 const auto rectP = PlayRegionRectangle(),
1570 rectS = SelectedRegionRectangle(),
1571 rectO = rectP.Intersect(rectS);
1572
1573 // What's left and right of the overlap? Assume same tops and bottoms
1574 const auto top = rectP.GetTop(),
1575 bottom = rectP.GetBottom();
1576 wxRect rectL{
1577 wxPoint{ 0, top }, wxPoint{ this->GetSize().GetWidth() - 1, bottom } };
1578 wxRect rectR = {};
1579 if (!rectO.IsEmpty()) {
1580 rectR = { wxPoint{ rectO.GetRight() + 1, top }, rectL.GetBottomRight() };
1581 rectL = { rectL.GetTopLeft(), wxPoint{ rectO.GetLeft() - 1, bottom } };
1582 }
1583
1584 DoDrawPlayRegion(&backDC, rectP, rectL, rectR);
1585 DoDrawOverlap(&backDC, rectO);
1586 DoDrawSelection(&backDC, rectS, rectL, rectR);
1587
1588 DoDrawPlayRegionLimits(&backDC, rectP);
1589
1590 DoDrawMarks(&backDC, true);
1591
1592 DoDrawEdge(&backDC);
1593
1594 DisplayBitmap(dc);
1595
1596 // Stroke extras direct to the client area,
1597 // maybe outside of the damaged area
1598 // As with TrackPanel, do not make a NEW wxClientDC or else Mac flashes badly!
1599 dc.DestroyClippingRegion();
1600 DrawOverlays(true, &dc);
1601}
1602
1603void AdornedRulerPanel::OnSize(wxSizeEvent &evt)
1604{
1605 mOuter = GetClientRect();
1606 if (mOuter.GetWidth() == 0 || mOuter.GetHeight() == 0)
1607 {
1608 return;
1609 }
1610
1611 UpdateRects();
1612
1614}
1615
1616void AdornedRulerPanel::OnLeave(wxMouseEvent& evt)
1617{
1618 evt.Skip();
1619 CallAfter([this]{
1621 });
1622}
1623
1625{
1626 if (message.appearance)
1627 return;
1629}
1630
1632{
1633 auto &selectedRegion = mViewInfo->selectedRegion;
1634 DoSelectionChange( selectedRegion );
1635}
1636
1638 const SelectedRegion &selectedRegion )
1639{
1640
1641 auto gAudioIO = AudioIOBase::Get();
1642 if ( !ViewInfo::Get( *mProject ).playRegion.Active() ) {
1643 // "Inactivated" play region follows the selection.
1644 SetPlayRegion( selectedRegion.t0(), selectedRegion.t1() );
1645 }
1646}
1647
1649{
1650 auto inner = mOuter;
1651 wxRect scrubZone;
1652 inner.x += LeftMargin;
1653 inner.width -= (LeftMargin + RightMargin);
1654
1655 auto top = &inner;
1656 auto bottom = &inner;
1657
1658 if (ShowingScrubRuler()) {
1659 scrubZone = inner;
1660 auto scrubHeight = std::min(scrubZone.height, (int)(ScrubHeight));
1661
1662 int topHeight;
1663#ifdef SCRUB_ABOVE
1664 top = &scrubZone, topHeight = scrubHeight;
1665#else
1666 auto qpHeight = scrubZone.height - scrubHeight;
1667 bottom = &scrubZone, topHeight = qpHeight;
1668 // Increase scrub zone height so that hit testing finds it and
1669 // not QP region, when on bottom 'edge'.
1670 scrubZone.height+=BottomMargin;
1671#endif
1672
1673 top->height = topHeight;
1674 bottom->height -= topHeight;
1675 bottom->y += topHeight;
1676 }
1677
1678 top->y += TopMargin;
1679 top->height -= TopMargin;
1680
1681 bottom->height -= BottomMargin;
1682
1683 if (!ShowingScrubRuler())
1684 scrubZone = inner;
1685
1686 if ( inner == mInner && scrubZone == mScrubZone )
1687 // no changes
1688 return false;
1689
1690 mInner = inner;
1691 mScrubZone = scrubZone;
1692
1693 mRuler.SetBounds(mInner.GetLeft(),
1694 mInner.GetTop(),
1695 mInner.GetRight(),
1696 mInner.GetBottom());
1697
1698 return true;
1699}
1700
1701double AdornedRulerPanel::Pos2Time(int p, bool ignoreFisheye) const
1702{
1704 , ignoreFisheye
1705 );
1706}
1707
1708int AdornedRulerPanel::Time2Pos(double t, bool ignoreFisheye) const
1709{
1711 , ignoreFisheye
1712 );
1713}
1714
1715bool AdornedRulerPanel::IsWithinMarker(int mousePosX, double markerTime)
1716{
1717 if (markerTime < 0)
1718 return false;
1719
1720 int pixelPos = Time2Pos(markerTime);
1721 int boundLeft = pixelPos - SELECT_TOLERANCE_PIXEL;
1722 int boundRight = pixelPos + SELECT_TOLERANCE_PIXEL;
1723
1724 return mousePosX >= boundLeft && mousePosX < boundRight;
1725}
1726
1727#ifdef QUICK_PLAY_HANDLE
1728auto AdornedRulerPanel::QPHandle::Click(
1729 const TrackPanelMouseEvent &event, AudacityProject *pProject) -> Result
1730{
1731 auto result = CommonRulerHandle::Click(event, pProject);
1732 if (!( result & RefreshCode::Cancelled )) {
1733 if (mClicked == Button::Left) {
1734 if (!mParent)
1736
1737 auto &scrubber = Scrubber::Get( *pProject );
1738 if(scrubber.HasMark()) {
1739 // We can't stop scrubbing yet (see comments in Bug 1391),
1740 // but we can pause it.
1741 ProjectAudioManager::Get( *pProject ).OnPause();
1742 }
1743
1744 // Store the initial play region state
1745 const auto &viewInfo = ViewInfo::Get( *pProject );
1746 const auto &playRegion = viewInfo.playRegion;
1747 mParent->mOldPlayRegion = playRegion;
1748
1749 // Save old selection, in case drag of selection is cancelled
1750 mOldSelection = ViewInfo::Get( *pProject ).selectedRegion;
1751
1752 mParent->HandleQPClick( event.event, mX );
1753 mParent->HandleQPDrag( event.event, mX );
1754 }
1755 }
1756
1757 return result;
1758}
1759
1760void AdornedRulerPanel::HandleQPClick(wxMouseEvent &evt, wxCoord mousePosX)
1761{
1762 // Temporarily inactivate play region
1763 if (mOldPlayRegion.Active() && evt.LeftDown()) {
1764 //mPlayRegionLock = true;
1766 }
1767
1770 bool isWithinStart = IsWithinMarker(mousePosX, mOldPlayRegion.GetStart());
1771 bool isWithinEnd = IsWithinMarker(mousePosX, mOldPlayRegion.GetEnd());
1772
1773 if (isWithinStart || isWithinEnd) {
1774 // If Quick-Play is playing from a point, we need to treat it as a click
1775 // not as dragging.
1776 if (mOldPlayRegion.Empty())
1778 // otherwise check which marker is nearer
1779 else {
1780 // Don't compare times, compare positions.
1781 //if (fabs(mQuickPlayPos[0] - mPlayRegionStart) < fabs(mQuickPlayPos[0] - mPlayRegionEnd))
1782 auto start = mOldPlayRegion.GetStart();
1783 auto end = mOldPlayRegion.GetEnd();
1784 if (abs(Time2Pos(mQuickPlayPos[0]) - Time2Pos(start)) <
1785 abs(Time2Pos(mQuickPlayPos[0]) - Time2Pos(end)))
1787 else
1789 }
1790 }
1791 else {
1792 // Clicked but not yet dragging
1794 }
1795}
1796
1797auto AdornedRulerPanel::QPHandle::Drag(
1798 const TrackPanelMouseEvent &event, AudacityProject *pProject) -> Result
1799{
1800 auto result = CommonRulerHandle::Drag(event, pProject);
1801 if (!( result & RefreshCode::Cancelled )) {
1802 if (mClicked == Button::Left) {
1803 if ( mParent ) {
1804 mX = event.event.m_x;
1805 mParent->UpdateQuickPlayPos( mX );
1806 mParent->HandleQPDrag( event.event, mX );
1807 }
1808 }
1809 }
1810 return result;
1811}
1812
1813void AdornedRulerPanel::HandleQPDrag(wxMouseEvent &/*event*/, wxCoord mousePosX)
1814{
1815 bool isWithinClick =
1816 (mLeftDownClickUnsnapped >= 0) &&
1818 bool isWithinStart = IsWithinMarker(mousePosX, mOldPlayRegion.GetStart());
1819 bool isWithinEnd = IsWithinMarker(mousePosX, mOldPlayRegion.GetEnd());
1820 bool canDragSel = !mOldPlayRegion.Active() && mPlayRegionDragsSelection;
1821 auto &viewInfo = ViewInfo::Get( *GetProject() );
1822 auto &playRegion = viewInfo.playRegion;
1823
1824 switch (mMouseEventState)
1825 {
1826 case mesNone:
1827 // If close to either end of play region, snap to closest
1828 if (isWithinStart || isWithinEnd) {
1831 else
1833 }
1834 break;
1836 // Don't start dragging until beyond tolerance initial playback start
1837 if (!mIsDragging && isWithinStart)
1839 else
1840 mIsDragging = true;
1841 // avoid accidental tiny selection
1842 if (isWithinEnd)
1844 playRegion.SetStart( mQuickPlayPos[0] );
1845 if (canDragSel) {
1847 }
1848 break;
1850 if (!mIsDragging && isWithinEnd) {
1852 }
1853 else
1854 mIsDragging = true;
1855 if (isWithinStart) {
1857 }
1858 playRegion.SetEnd( mQuickPlayPos[0] );
1859 if (canDragSel) {
1861 }
1862 break;
1864
1865 // Don't start dragging until mouse is beyond tolerance of initial click.
1866 if (isWithinClick || mLeftDownClick == -1) {
1868 playRegion.SetTimes(mLeftDownClick, mLeftDownClick);
1869 }
1870 else {
1872 }
1873 break;
1875 if (isWithinClick) {
1877 }
1878
1880 playRegion.SetTimes( mQuickPlayPos[0], mLeftDownClick );
1881 else
1882 playRegion.SetTimes( mLeftDownClick, mQuickPlayPos[0] );
1883 if (canDragSel) {
1885 }
1886 break;
1887 }
1888 Refresh();
1889 Update();
1890}
1891#endif
1892
1894 const TrackPanelMouseState &, AudacityProject *pProject)
1896{
1897 auto &scrubber = Scrubber::Get( *pProject );
1898 auto message = ScrubbingMessage(scrubber, mClicked == Button::Left);
1899
1900 mParent->SetNumGuides(1);
1901 return {
1902 message,
1903 {},
1904 // Tooltip is same as status message, or blank
1905 ((mParent && mParent->mTimelineToolTip) ? message : TranslatableString{}),
1906 };
1907}
1908
1909#ifdef QUICK_PLAY_HANDLE
1910auto AdornedRulerPanel::QPHandle::Preview(
1911 const TrackPanelMouseState &state, AudacityProject *pProject)
1913{
1914 mParent->SetNumGuides(1);
1915 TranslatableString tooltip;
1916 #if 0
1917 if (mParent && mParent->mTimelineToolTip) {
1918 if (!mParent->mQuickPlayEnabled)
1919 tooltip = XO("Quick-Play disabled");
1920 else
1921 tooltip = XO("Quick-Play enabled");
1922 }
1923 #endif
1924
1925 TranslatableString message;
1926 auto &scrubber = Scrubber::Get( *pProject );
1927 const bool scrubbing = scrubber.HasMark();
1928 if (scrubbing)
1929 // Don't distinguish zones
1930 message = ScrubbingMessage(scrubber, false);
1931 else
1932 // message = Insert timeline status bar message here
1933 ;
1934
1935 static wxCursor cursorHand{ wxCURSOR_HAND };
1936 static wxCursor cursorSizeWE{ wxCURSOR_SIZEWE };
1937
1938 bool showArrows = false;
1939 if (mParent)
1940 showArrows =
1941 (mClicked == Button::Left)
1942 || mParent->IsWithinMarker(
1943 state.state.m_x, mParent->mOldPlayRegion.GetStart())
1944 || mParent->IsWithinMarker(
1945 state.state.m_x, mParent->mOldPlayRegion.GetEnd());
1946
1947 return {
1948 message,
1949 showArrows ? &cursorSizeWE : &cursorHand,
1950 tooltip,
1951 };
1952}
1953
1955 const TrackPanelMouseEvent &event, AudacityProject *pProject,
1956 wxWindow *pParent)
1957 -> Result
1958{
1959 // Keep a shared pointer to self. Otherwise *this might get deleted
1960 // in HandleQPRelease on Windows! Because there is an event-loop yield
1961 // stopping playback, which caused OnCaptureLost to be called, which caused
1962 // clearing of CellularPanel targets!
1963 auto saveMe = mParent->mQPCell->mHolder.lock();
1964
1965 auto result = CommonRulerHandle::Release(event, pProject, pParent);
1966 if (!( result & RefreshCode::Cancelled )) {
1967 if (mClicked == Button::Left) {
1968 if ( mParent ) {
1969 mParent->HandleQPRelease( event.event );
1970 // Update the hot zones for cursor changes
1971 const auto &viewInfo = ViewInfo::Get( *pProject );
1972 const auto &playRegion = viewInfo.playRegion;
1973 mParent->mOldPlayRegion = playRegion;
1974 }
1975 }
1976 }
1977 return result;
1978}
1979
1980void AdornedRulerPanel::HandleQPRelease(wxMouseEvent &evt)
1981{
1982 auto &viewInfo = ViewInfo::Get( *GetProject() );
1983 auto &playRegion = viewInfo.playRegion;
1984 playRegion.Order();
1985
1986 const double t0 = mTracks->GetStartTime();
1987 const double t1 = mTracks->GetEndTime();
1988 const auto &selectedRegion = viewInfo.selectedRegion;
1989 const double sel0 = selectedRegion.t0();
1990 const double sel1 = selectedRegion.t1();
1991
1992 // We want some audio in the selection, but we allow a dragged
1993 // region to include selected white-space and space before audio start.
1994 if (evt.ShiftDown() && playRegion.Empty()) {
1995 // Looping the selection or project.
1996 // Disable if track selection is in white-space beyond end of tracks and
1997 // play position is outside of track contents.
1998 if (((sel1 < t0) || (sel0 > t1)) &&
1999 ((playRegion.GetStart() < t0) || (playRegion.GetStart() > t1))) {
2001 }
2002 }
2003 // Disable if beyond end.
2004 else if (playRegion.GetStart() >= t1) {
2006 }
2007 // Disable if empty selection before start.
2008 // (allow Quick-Play region to include 'pre-roll' white space)
2009 else if (
2010 playRegion.GetEnd() - playRegion.GetStart() > 0.0 &&
2011 playRegion.GetEnd() < t0
2012 ) {
2014 }
2015
2017 mIsDragging = false;
2018 mLeftDownClick = -1;
2019
2020 auto cleanup = finally( [&] {
2021 if (mOldPlayRegion.Active()) {
2022 // Restore Locked Play region
2023 SetPlayRegion(mOldPlayRegion.GetStart(), mOldPlayRegion.GetEnd());
2024 SelectUtilities::ActivatePlayRegion(*mProject);
2025 // and release local lock
2026 mOldPlayRegion.SetActive( false );
2027 }
2028 } );
2029
2030 StartQPPlay(!evt.ShiftDown(), evt.ControlDown());
2031}
2032
2033auto AdornedRulerPanel::QPHandle::Cancel(AudacityProject *pProject) -> Result
2034{
2035 auto result = CommonRulerHandle::Cancel(pProject);
2036
2037 if (mClicked == Button::Left) {
2038 if( mParent ) {
2039 ViewInfo::Get( *pProject ).selectedRegion = mOldSelection;
2040 mParent->mMouseEventState = mesNone;
2041 mParent->SetPlayRegion(
2042 mParent->mOldPlayRegion.GetStart(), mParent->mOldPlayRegion.GetEnd());
2043 if (mParent->mOldPlayRegion.Active()) {
2044 // Restore Locked Play region
2046 // and release local lock
2047 mParent->mOldPlayRegion.SetActive( false );
2048 }
2049 }
2050 }
2051
2052 return result;
2053}
2054#endif
2055
2057 bool newDefault, bool cutPreview, const double *pStartTime)
2058{
2059 const double t0 = mTracks->GetStartTime();
2060 const double t1 = mTracks->GetEndTime();
2061 auto &viewInfo = ViewInfo::Get( *mProject );
2062 const auto &playRegion = viewInfo.playRegion;
2063 const auto &selectedRegion = viewInfo.selectedRegion;
2064 const double sel0 = selectedRegion.t0();
2065 const double sel1 = selectedRegion.t1();
2066
2067 // Start / Restart playback on left click.
2068 bool startPlaying = true; // = (playRegion.GetStart() >= 0);
2069
2070 if (startPlaying) {
2071 bool loopEnabled = true;
2072 auto oldStart = std::max(0.0, playRegion.GetStart());
2073 double start = oldStart, end = 0;
2074
2075 if (playRegion.Empty()) {
2076 // Play either a selection or the project.
2077 if (oldStart > sel0 && oldStart < sel1) {
2078 // we are in a selection, so use the selection
2079 start = sel0;
2080 end = sel1;
2081 } // not in a selection, so use the project
2082 else {
2083 start = t0;
2084 end = t1;
2085 }
2086 }
2087 else
2088 end = std::max(start, playRegion.GetEnd());
2089
2090 // Looping a tiny selection may freeze, so just play it once.
2091 loopEnabled = ((end - start) > 0.001)? true : false;
2092
2093 newDefault = (loopEnabled && newDefault);
2094 if (newDefault)
2095 cutPreview = false;
2096 auto options = DefaultPlayOptions( *mProject, newDefault );
2097
2098 if (!cutPreview) {
2099 if (pStartTime)
2100 options.pStartTime.emplace(*pStartTime);
2101 }
2102 else
2103 options.envelope = nullptr;
2104
2105 auto mode =
2106 cutPreview ? PlayMode::cutPreviewPlay
2107 : newDefault ? PlayMode::loopedPlay
2109
2110 // Stop only after deciding where to start again, because an event
2111 // callback may change the play region back to the selection
2112 auto &projectAudioManager = ProjectAudioManager::Get( *mProject );
2113 projectAudioManager.Stop();
2114
2115 // Don't change play region, assume caller set it as needed
2116 // playRegion.SetTimes( start, end );
2117 // Refresh();
2118
2119 projectAudioManager.PlayPlayRegion((SelectedRegion(start, end)),
2120 options, mode,
2121 false);
2122
2123 }
2124}
2125
2126#if 0
2127// This version toggles ruler state indirectly via the scrubber
2128// to ensure that all the places where the state is shown update.
2129// For example buttons and menus must update.
2130void AdornedRulerPanel::OnToggleScrubRulerFromMenu(wxCommandEvent&)
2131{
2132 auto &scrubber = Scrubber::Get( *mProject );
2133 scrubber.OnToggleScrubRuler(*mProject);
2134}
2135#endif
2136
2137
2139{
2140 const auto oldSize = GetSize();
2141 wxSize size { oldSize.GetWidth(), GetRulerHeight(ShowingScrubRuler()) };
2142 if ( size != oldSize ) {
2143 SetSize(size);
2144 SetMinSize(size);
2145 PostSizeEventToParent();
2146 return true;
2147 }
2148 else
2149 return false;
2150}
2151
2153{
2154 auto pCellularPanel =
2155 dynamic_cast<CellularPanel*>( &GetProjectPanel( *GetProject() ) );
2156 if ( !pCellularPanel ) {
2157 wxASSERT( false );
2158 }
2159 else
2160 pCellularPanel->DrawOverlays( false );
2161 DrawOverlays( false );
2162}
2163
2165{
2166 auto common = [this](
2167 AButton &button, const CommandID &commandName, const TranslatableString &label) {
2168 ComponentInterfaceSymbol command{ commandName, label };
2169 ToolBar::SetButtonToolTip( *mProject, button, &command, 1u );
2170 button.SetLabel( Verbatim( button.GetToolTipText() ) );
2171
2172 button.UpdateStatus();
2173 };
2174
2175 {
2176 // The button always reflects the pinned head preference, even though
2177 // there is also a Playback preference that may overrule it for scrubbing
2179 auto pinButton = static_cast<AButton*>(FindWindow(OnTogglePinnedStateID));
2180 if( !state )
2181 pinButton->PopUp();
2182 else
2183 pinButton->PushDown();
2184 auto gAudioIO = AudioIO::Get();
2185 pinButton->SetAlternateIdx(
2186 (gAudioIO->IsCapturing() ? 2 : 0) + (state ? 0 : 1));
2187 // Bug 1584: Tooltip now shows what clicking will do.
2188 // Bug 2357: Action of button (and hence tooltip wording) updated.
2189 const auto label = XO("Timeline Options");
2190 common(*pinButton, wxT("PinnedHead"), label);
2191 }
2192}
2193
2194void AdornedRulerPanel::OnPinnedButton(wxCommandEvent & /*event*/)
2195{
2197}
2198
2199void AdornedRulerPanel::OnTogglePinnedState(wxCommandEvent & /*event*/)
2200{
2203}
2204
2206{
2207 // Invoked for mouse-over preview events, or dragging, or scrub position
2208 // polling updates. Remember x coordinates, converted to times, for
2209 // drawing of guides.
2210
2211 // Keep Quick-Play within usable track area. (Dependent on zoom)
2212 const auto &viewInfo = ViewInfo::Get( *mProject );
2213 auto width = viewInfo.GetTracksUsableWidth();
2214 mousePosX = std::max(mousePosX, viewInfo.GetLeftOffset());
2215 mousePosX = std::min(mousePosX, viewInfo.GetLeftOffset() + width - 1);
2216 const auto time = Pos2Time(mousePosX);
2217
2218 for (size_t ii = 0; ii < mNumGuides; ++ii) {
2220 time + mQuickPlayOffset[ii];
2221 HandleSnapping(ii);
2222 }
2223}
2224
2225// Pop-up menus
2226
2227void AdornedRulerPanel::ShowMenu(const wxPoint & pos)
2228{
2229 const auto &viewInfo = ViewInfo::Get( *GetProject() );
2230 const auto &playRegion = viewInfo.playRegion;
2231 wxMenu rulerMenu;
2232
2233 auto pDrag = rulerMenu.AppendCheckItem(OnSyncQuickPlaySelID, _("Enable dragging selection"));
2234 pDrag->Check(mPlayRegionDragsSelection && playRegion.Active());
2235 pDrag->Enable(playRegion.Active());
2236
2237 rulerMenu.AppendCheckItem(OnAutoScrollID, _("Update display while playing"))->
2239
2240 {
2241 auto item = rulerMenu.AppendCheckItem(OnTogglePlayRegionID,
2243 item->Check(playRegion.Active());
2244 }
2245
2246 {
2247 auto item = rulerMenu.Append(OnClearPlayRegionID,
2248 /* i18n-hint Clear is a verb */
2249 _("Clear Loop"));
2250 }
2251
2252 {
2253 auto item = rulerMenu.Append(OnSetPlayRegionToSelectionID,
2254 _("Set Loop To Selection"));
2255 }
2256
2257 rulerMenu.AppendSeparator();
2258 rulerMenu.AppendCheckItem(OnTogglePinnedStateID, _("Pinned Play Head"))->
2260
2261 BasicMenu::Handle{ &rulerMenu }.Popup(
2263 { pos.x, pos.y }
2264 );
2265}
2266
2267void AdornedRulerPanel::ShowScrubMenu(const wxPoint & pos)
2268{
2269 auto &scrubber = Scrubber::Get( *mProject );
2270 PushEventHandler(&scrubber);
2271 auto cleanup = finally([this]{ PopEventHandler(); });
2272
2273 wxMenu rulerMenu;
2274 scrubber.PopulatePopupMenu(rulerMenu);
2275 BasicMenu::Handle{ &rulerMenu }.Popup(
2277 { pos.x, pos.y }
2278 );
2279}
2280
2282{
2284 gPrefs->Write(wxT("/QuickPlay/DragSelection"), mPlayRegionDragsSelection);
2285 gPrefs->Flush();
2286}
2287
2289{
2290 auto &viewInfo = ViewInfo::Get( project );
2291 const auto &playRegion = viewInfo.playRegion;
2292 auto &selectedRegion = viewInfo.selectedRegion;
2293 selectedRegion.setT0(playRegion.GetStart(), false);
2294 selectedRegion.setT1(playRegion.GetEnd(), true);
2295}
2296
2298{
2299 // Play region dragging can snap to selection boundaries
2300 const auto &selectedRegion = ViewInfo::Get(*GetProject()).selectedRegion;
2301 SnapPointArray candidates;
2303 candidates = {
2304 SnapPoint{ selectedRegion.t0() },
2305 SnapPoint{ selectedRegion.t1() },
2306 };
2307 SnapManager snapManager{ *mProject, *mTracks, *mViewInfo, move(candidates) };
2308 auto results = snapManager.Snap(nullptr, mQuickPlayPos[index], false);
2309 mQuickPlayPos[index] = results.outTime;
2310 mIsSnapped[index] = results.Snapped();
2311}
2312
2313#if 0
2314void AdornedRulerPanel::OnTimelineToolTips(wxCommandEvent&)
2315{
2316 mTimelineToolTip = (mTimelineToolTip)? false : true;
2317 gPrefs->Write(wxT("/QuickPlay/ToolTips"), mTimelineToolTip);
2318 gPrefs->Flush();
2319}
2320#endif
2321
2323{
2325 gPrefs->Write(wxT("/GUI/AutoScroll"), false);
2326 else
2327 gPrefs->Write(wxT("/GUI/AutoScroll"), true);
2328
2329 gPrefs->Flush();
2330
2332}
2333
2334
2336{
2338}
2339
2341{
2343}
2344
2346{
2348}
2349
2350
2352 MenuChoice choice, const wxPoint *pPosition)
2353{
2354 wxPoint position;
2355 if(pPosition)
2356 position = *pPosition;
2357 else
2358 {
2359 auto rect = GetRect();
2360 //Old code put menu too low down. y position applied twice.
2361 //position = { rect.GetLeft() + 1, rect.GetBottom() + 1 };
2362
2363 // The cell does not pass in the mouse or button position.
2364 // We happen to know this is the pin/unpin button
2365 // so these magic values 'fix a bug' - but really the cell should
2366 // pass more information to work with in.
2367 position = { rect.GetLeft() + 38, rect.GetHeight()/2 + 1 };
2368 }
2369
2370 switch (choice) {
2372 ShowMenu(position);
2374 break;
2375 case MenuChoice::Scrub:
2376 ShowScrubMenu(position); break;
2377 default:
2378 return;
2379 }
2380}
2381
2382using ColorId = decltype(clrTrackInfo);
2383
2385{
2386 return clrTrackInfo;
2387}
2388
2390{
2391 return clrTrackPanelText;
2392}
2393
2395{
2396 return TimelineTextColor();
2397}
2398
2400{
2401 return isActive ? clrRulerBackground : clrClipAffordanceInactiveBrush;
2402}
2403
2404static inline wxColour AlphaBlend(ColorId fg, ColorId bg, double alpha)
2405{
2406 const auto &fgc = theTheme.Colour(fg);
2407 const auto &bgc = theTheme.Colour(bg);
2408 return wxColour{
2409 wxColour::AlphaBlend(fgc.Red(), bgc.Red(), alpha),
2410 wxColour::AlphaBlend(fgc.Green(), bgc.Green(), alpha),
2411 wxColour::AlphaBlend(fgc.Blue(), bgc.Blue(), alpha)
2412 };
2413}
2414
2416{
2417 // Draw AdornedRulerPanel border
2419 dc->DrawRectangle( mInner );
2420
2421 if (ShowingScrubRuler()) {
2422 // Let's distinguish the scrubbing area by using a themable
2423 // colour and a line to set it off.
2424 AColor::UseThemeColour(dc, clrScrubRuler, TimelineTextColor() );
2425 wxRect ScrubRect = mScrubZone;
2426 ScrubRect.Inflate( 1,0 );
2427 dc->DrawRectangle(ScrubRect);
2428 }
2429}
2430
2432{
2433 wxRect r = mOuter;
2434 r.width -= RightMargin;
2435 r.height -= BottomMargin;
2436 AColor::BevelTrackInfo( *dc, true, r );
2437
2438 // Black stroke at bottom
2439 dc->SetPen( *wxBLACK_PEN );
2440 AColor::Line( *dc, mOuter.x,
2441 mOuter.y + mOuter.height - 1,
2442 mOuter.x + mOuter.width - 1 ,
2443 mOuter.y + mOuter.height - 1 );
2444}
2445
2446void AdornedRulerPanel::DoDrawMarks(wxDC * dc, bool /*text */ )
2447{
2448 const double min = Pos2Time(0);
2449 const double hiddenMin = Pos2Time(0, true);
2450 const double max = Pos2Time(mInner.width);
2451 const double hiddenMax = Pos2Time(mInner.width, true);
2452
2454 mRuler.SetRange( min, max, hiddenMin, hiddenMax );
2455 mRuler.Draw( *dc );
2456}
2457
2459{
2460 Refresh();
2461}
2462
2464{
2465 const auto &viewInfo = ViewInfo::Get(*mProject);
2466 const auto &playRegion = viewInfo.playRegion;
2467 const auto t0 = playRegion.GetLastActiveStart(),
2468 t1 = playRegion.GetLastActiveEnd();
2469 return RegionRectangle(t0, t1);
2470}
2471
2473{
2474 const auto &viewInfo = ViewInfo::Get(*mProject);
2475 const auto &selectedRegion = viewInfo.selectedRegion;
2476 const auto t0 = selectedRegion.t0(), t1 = selectedRegion.t1();
2477 return RegionRectangle(t0, t1);
2478}
2479
2480wxRect AdornedRulerPanel::RegionRectangle(double t0, double t1) const
2481{
2482 int p0 = -1, p1 = -1;
2483 if (t0 == t1)
2484 // Make the rectangle off-screen horizontally, but set the height
2485 ;
2486 else {
2487 p0 = max(1, Time2Pos(t0));
2488 p1 = min(mInner.width, Time2Pos(t1));
2489 }
2490
2491 const int left = p0, top = mInner.y, right = p1, bottom = mInner.GetBottom();
2492 return { wxPoint{left, top}, wxPoint{right, bottom} };
2493}
2494
2496 wxDC * dc, const wxRect &rectP, const wxRect &rectL, const wxRect &rectR)
2497{
2498 const auto &viewInfo = ViewInfo::Get(*mProject);
2499 const auto &playRegion = viewInfo.playRegion;
2500 if (playRegion.IsLastActiveRegionClear())
2501 return;
2502
2503 const bool isActive = (mLastPlayRegionActive = playRegion.Active());
2504
2505 // Paint the selected region bolder if independently varying, else dim
2506 const auto color = TimelineLoopRegionColor(isActive);
2507 dc->SetBrush( wxBrush( theTheme.Colour( color )) );
2508 dc->SetPen( wxPen( theTheme.Colour( color )) );
2509
2510 dc->DrawRectangle( rectP.Intersect(rectL) );
2511 dc->DrawRectangle( rectP.Intersect(rectR) );
2512}
2513
2514void AdornedRulerPanel::DoDrawPlayRegionLimits(wxDC * dc, const wxRect &rect)
2515{
2516 // Color the edges of the play region like the ticks and numbers
2517 ADCChanger cleanup( dc );
2518 const auto edgeColour = theTheme.Colour(TimelineLimitsColor());
2519 dc->SetPen( { edgeColour } );
2520 dc->SetBrush( { edgeColour } );
2521
2522 constexpr int side = 7;
2523 constexpr int sideLessOne = side - 1;
2524
2525 // Paint two shapes, each a line plus triangle at bottom
2526 const auto left = rect.GetLeft(),
2527 right = rect.GetRight(),
2528 bottom = rect.GetBottom(),
2529 top = rect.GetTop();
2530 {
2531 wxPoint points[]{
2532 {left, bottom - sideLessOne},
2533 {left - sideLessOne, bottom},
2534 {left, bottom},
2535 {left, top},
2536 };
2537 dc->DrawPolygon( 4, points );
2538 }
2539
2540 {
2541 wxPoint points[]{
2542 {right, top},
2543 {right, bottom},
2544 {right + sideLessOne, bottom},
2545 {right, bottom - sideLessOne},
2546 };
2547 dc->DrawPolygon( 4, points );
2548 }
2549}
2550
2551constexpr double SelectionOpacity = 0.2;
2552
2553void AdornedRulerPanel::DoDrawOverlap(wxDC * dc, const wxRect &rect)
2554{
2555 dc->SetBrush( wxBrush{ AlphaBlend(
2557 SelectionOpacity) } );
2558 dc->SetPen( *wxTRANSPARENT_PEN );
2559 dc->DrawRectangle( rect );
2560}
2561
2563 wxDC * dc, const wxRect &rectS, const wxRect &rectL, const wxRect &rectR)
2564{
2565 dc->SetBrush( wxBrush{ AlphaBlend(
2567 dc->SetPen( *wxTRANSPARENT_PEN );
2568 dc->DrawRectangle( rectS.Intersect(rectL) );
2569 dc->DrawRectangle( rectS.Intersect(rectR) );
2570}
2571
2573{
2574 return ProperRulerHeight + (showScrubBar ? ScrubHeight : 0);
2575}
2576
2578{
2579 mLeftOffset = offset;
2581}
2582
2583// Draws the scrubbing/seeking indicator.
2585 wxDC * dc, wxCoord xx, int width, bool scrub, bool seek)
2586{
2587 ADCChanger changer(dc); // Undo pen and brush changes at function exit
2588
2589 wxPoint tri[ 3 ];
2590 if (seek) {
2591 auto height = IndicatorHeightForWidth(width);
2592 // Make four triangles
2593 const int TriangleWidth = width * 3 / 8;
2594
2595 // Double-double headed, left-right
2596 auto yy = ShowingScrubRuler()
2597 ? mScrubZone.y
2598 : (mInner.GetBottom() + 1) - 1 /* bevel */ - height;
2599 tri[ 0 ].x = xx - IndicatorOffset;
2600 tri[ 0 ].y = yy;
2601 tri[ 1 ].x = xx - IndicatorOffset;
2602 tri[ 1 ].y = yy + height;
2603 tri[ 2 ].x = xx - TriangleWidth;
2604 tri[ 2 ].y = yy + height / 2;
2605 dc->DrawPolygon( 3, tri );
2606
2607 tri[ 0 ].x -= TriangleWidth;
2608 tri[ 1 ].x -= TriangleWidth;
2609 tri[ 2 ].x -= TriangleWidth;
2610 dc->DrawPolygon( 3, tri );
2611
2612 tri[ 0 ].x = tri[ 1 ].x = xx + IndicatorOffset;
2613 tri[ 2 ].x = xx + TriangleWidth;
2614 dc->DrawPolygon( 3, tri );
2615
2616
2617 tri[ 0 ].x += TriangleWidth;
2618 tri[ 1 ].x += TriangleWidth;
2619 tri[ 2 ].x += TriangleWidth;
2620 dc->DrawPolygon( 3, tri );
2621 }
2622 else if (scrub) {
2623 auto height = IndicatorHeightForWidth(width);
2624 const int IndicatorHalfWidth = width / 2;
2625
2626 // Double headed, left-right
2627 auto yy = ShowingScrubRuler()
2628 ? mScrubZone.y
2629 : (mInner.GetBottom() + 1) - 1 /* bevel */ - height;
2630 tri[ 0 ].x = xx - IndicatorOffset;
2631 tri[ 0 ].y = yy;
2632 tri[ 1 ].x = xx - IndicatorOffset;
2633 tri[ 1 ].y = yy + height;
2634 tri[ 2 ].x = xx - IndicatorHalfWidth;
2635 tri[ 2 ].y = yy + height / 2;
2636 dc->DrawPolygon( 3, tri );
2637 tri[ 0 ].x = tri[ 1 ].x = xx + IndicatorOffset;
2638 tri[ 2 ].x = xx + IndicatorHalfWidth;
2639 dc->DrawPolygon( 3, tri );
2640 }
2641}
2642
2644 double playRegionStart, double playRegionEnd)
2645{
2646 // This is called by AudacityProject to make the play region follow
2647 // the current selection. But while the user is selecting a play region
2648 // with the mouse directly in the ruler, changes from outside are blocked.
2650 return;
2651
2652 auto &viewInfo = ViewInfo::Get( *GetProject() );
2653 auto &playRegion = viewInfo.playRegion;
2654 playRegion.SetTimes( playRegionStart, playRegionEnd );
2655
2656 Refresh();
2657}
2658
2660{
2661 ProjectAudioManager::Get( *mProject ).Stop();
2662
2663 auto &viewInfo = ViewInfo::Get( *GetProject() );
2664 auto &playRegion = viewInfo.playRegion;
2665 playRegion.SetTimes( -1, -1 );
2666
2667 Refresh();
2668}
2669
2670void AdornedRulerPanel::GetMaxSize(wxCoord *width, wxCoord *height)
2671{
2672 mRuler.GetMaxSize(width, height);
2673}
2674
2676
2678 s_AcceptsFocus = true;
2679 return TempAllowFocus{ &s_AcceptsFocus };
2680}
2681
2683{
2684 nn = std::min(nn, MAX_GUIDES);
2685 // If increasing the number of guides, reinitialize newer ones
2686 for (size_t ii = mNumGuides; ii < nn; ++ii) {
2687 mQuickPlayOffset[ii] = 0;
2688 mQuickPlayPosUnsnapped[ii] = 0;
2689 mQuickPlayPos[ii] = 0;
2690 mIsSnapped[ii] = false;
2691 }
2692 mNumGuides = nn;
2693}
2694
2696{
2697 auto temp = TemporarilyAllowFocus();
2698 SetFocus();
2699}
2700
2701// Second-level subdivision includes quick-play region and maybe the scrub bar
2702// and also shaves little margins above and below
2704 explicit Subgroup( const AdornedRulerPanel &ruler ) : mRuler{ ruler } {}
2705 Subdivision Children( const wxRect & ) override
2706 {
2707 return { Axis::Y, ( mRuler.ShowingScrubRuler() )
2708 ? Refinement{
2709 { mRuler.mInner.GetTop(), mRuler.mQPCell },
2710 { mRuler.mScrubZone.GetTop(), mRuler.mScrubbingCell },
2711 { mRuler.mScrubZone.GetBottom() + 1, nullptr }
2712 }
2713 : Refinement{
2714 { mRuler.mInner.GetTop(), mRuler.mQPCell },
2715 { mRuler.mInner.GetBottom() + 1, nullptr }
2716 }
2717 };
2718 }
2720};
2721
2722// Top-level subdivision shaves little margins off left and right
2724 explicit MainGroup( const AdornedRulerPanel &ruler ) : mRuler{ ruler } {}
2725 Subdivision Children( const wxRect & ) override
2726 { return { Axis::X, Refinement{
2727 // Subgroup is a throwaway object
2728 { mRuler.mInner.GetLeft(), std::make_shared< Subgroup >( mRuler ) },
2729 { mRuler.mInner.GetRight() + 1, nullptr }
2730 } }; }
2732};
2733
2735{
2736 auto &scrubber = Scrubber::Get( *GetProject() );
2737 return scrubber.ShowsBar();
2738}
2739
2740// CellularPanel implementation
2741std::shared_ptr<TrackPanelNode> AdornedRulerPanel::Root()
2742{
2743 // Root is a throwaway object
2744 return std::make_shared< MainGroup >( *this );
2745}
2746
2748{
2749 return mProject;
2750}
2751
2752
2754{
2755 // No switching of focus yet to the other, scrub zone
2756 return mQPCell.get();
2757}
2758
2759
2761{
2762}
2763
2764
2766 TrackPanelCell *, TrackPanelCell *, unsigned refreshResult)
2767{
2768 if (refreshResult & RefreshCode::RefreshAll)
2769 Refresh(); // Overlays will be repainted too
2770 else if (refreshResult & RefreshCode::DrawOverlays)
2771 DrawBothOverlays(); // cheaper redrawing of guidelines only
2772}
2773
2775{
2776 ProjectStatus::Get( *GetProject() ).Set(message);
2777}
2778
2780{
2781 if (!mOverlay) {
2782 mOverlay =
2783 std::make_shared<TrackPanelGuidelineOverlay>( mProject );
2784 auto pCellularPanel =
2785 dynamic_cast<CellularPanel*>( &GetProjectPanel( *GetProject() ) );
2786 if ( !pCellularPanel ) {
2787 wxASSERT( false );
2788 }
2789 else
2790 pCellularPanel->AddOverlay( mOverlay );
2791 this->AddOverlay( mOverlay->mPartner );
2792 }
2793}
2794
2796{
2800
2801 auto &project = *mProject;
2802 // Update button image
2804
2805 auto &scrubber = Scrubber::Get( project );
2806 if (scrubber.HasMark())
2807 scrubber.SetScrollScrubbing(value);
2808}
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
@ OnSetPlayRegionToSelectionID
@ OnClearPlayRegionID
@ OnAutoScrollID
@ OnTogglePlayRegionID
@ 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()
int min(int a, int b)
const wxChar * values
int teBmps
#define XO(s)
Definition: Internat.h:31
#define _(s)
Definition: Internat.h:75
EVT_COMMAND(wxID_ANY, EVT_FREQUENCYTEXTCTRL_UPDATED, LabelDialog::OnFreqUpdate) LabelDialog
Definition: LabelDialog.cpp:92
#define safenew
Definition: MemoryX.h:10
FileConfig * gPrefs
Definition: Prefs.cpp:71
AudioIOStartStreamOptions DefaultPlayOptions(AudacityProject &project, bool newDefault)
@ 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:163
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:185
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:338
const TranslatableString LoopToggleText
Definition: ViewInfo.cpp:227
int id
A wxButton with mouse-over behaviour.
Definition: AButton.h:104
void UpdateStatus()
Definition: AButton.cpp:458
void PopUp()
Definition: AButton.cpp:588
void SetLabel(const TranslatableString &label)
Definition: AButton.cpp:192
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)
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)
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)
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:89
static AudioIOBase * Get()
Definition: AudioIOBase.cpp:91
static AudioIO * Get()
Definition: AudioIO.cpp:133
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
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
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:103
void SetAsSpacer(bool bIsSpacer)
Definition: Grabber.cpp:100
static void ModifyAllProjectToolbarMenus()
Definition: Menus.cpp:592
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:127
void SetStart(double start)
Definition: ViewInfo.cpp:161
double GetEnd() const
Definition: ViewInfo.h:134
bool Empty() const
Definition: ViewInfo.h:126
void SetEnd(double end)
Definition: ViewInfo.cpp:171
bool Active() const
Definition: ViewInfo.h:123
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:98
bool IsAudioActive() const
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:148
void SetUseZoomInfo(int leftOffset, const ZoomInfo *zoomInfo)
Definition: Ruler.cpp:1680
void Draw(wxDC &dc) const
Definition: Ruler.cpp:1428
void SetLabelEdges(bool labelEdges)
Definition: Ruler.cpp:226
void GetMaxSize(wxCoord *width, wxCoord *height)
Definition: Ruler.cpp:1603
void SetBounds(int left, int top, int right, int bottom)
Definition: Ruler.cpp:332
void SetRange(double min, double max)
Definition: Ruler.cpp:188
void SetFormat(RulerFormat format)
Definition: Ruler.cpp:131
void Invalidate()
Definition: Ruler.cpp:345
@ TimeFormat
Definition: Ruler.h:33
void SetTwoTone(bool twoTone)
Definition: Ruler.cpp:126
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 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:931
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:861
static void MakeButtonBackgroundsSmall()
Definition: ToolBar.cpp:823
static void SetButtonToolTip(AudacityProject &project, AButton &button, const ComponentInterfaceSymbol commands[], size_t nCommands)
Definition: ToolBar.cpp:956
double GetEndTime() const
Definition: Track.cpp:1052
double GetStartTime() const
Definition: Track.cpp:1047
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:486
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:239
PlayRegion playRegion
Definition: ViewInfo.h:217
NotifyingSelectedRegion selectedRegion
Definition: ViewInfo.h:216
static int UpdateScrollPrefsID()
Definition: ViewInfo.cpp:327
static ViewInfo & Get(AudacityProject &project)
Definition: ViewInfo.cpp:235
double PositionToTime(wxInt64 position, wxInt64 origin=0, bool ignoreFisheye=false) const
Definition: ZoomInfo.cpp:41
wxInt64 TimeToPosition(double time, wxInt64 origin=0, bool ignoreFisheye=false) const
STM: Converts a project time to screen x position.
Definition: ZoomInfo.cpp:51
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 CallAfter(Action action)
Schedule an action to be done later, and in the main thread.
Definition: BasicUI.cpp:206
void Release(wxWindow *handler)
auto end(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:159
Namespace containing an enum 'what to do on a refresh?'.
Definition: RefreshCode.h:16
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)
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:75
enum AudioIOEvent::Type type
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.