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
1304 wxTheApp->Bind(EVT_THEME_CHANGE, &AdornedRulerPanel::OnThemeChange, this);
1305
1306 // Bind event that updates the play region
1309
1310 // And call it once to initialize it
1312}
1313
1315{
1316}
1317
1318void AdornedRulerPanel::Refresh( bool eraseBackground, const wxRect *rect )
1319{
1320 CellularPanel::Refresh( eraseBackground, rect );
1322}
1323
1325{
1326 if (mNeedButtonUpdate) {
1327 // Visit this block once only in the lifetime of this panel
1328 mNeedButtonUpdate = false;
1329 // Do this first time setting of button status texts
1330 // when we are sure the CommandManager is initialized.
1332 }
1333
1334 // Update button texts for language change
1336
1337 mTimelineToolTip = !!gPrefs->Read(wxT("/QuickPlay/ToolTips"), 1L);
1338
1339#ifdef EXPERIMENTAL_SCROLLING_LIMITS
1340#ifdef EXPERIMENTAL_TWO_TONE_TIME_RULER
1341 {
1342 auto scrollBeyondZero = ScrollingPreference.Read();
1343 mRuler.SetTwoTone(scrollBeyondZero);
1344 }
1345#endif
1346#endif
1347}
1348
1350{
1351 // TODO: Should we do this to destroy the grabber??
1352 // Get rid of any children we may have
1353 // DestroyChildren();
1354
1356 SetBackgroundColour(theTheme.Colour( clrMedium ));
1357
1358 for (auto & button : mButtons) {
1359 if (button)
1360 button->Destroy();
1361 button = nullptr;
1362 }
1363
1364 size_t iButton = 0;
1365 // Make the short row of time ruler pushbottons.
1366 // Don't bother with sizers. Their sizes and positions are fixed.
1367 // Add a grabber converted to a spacer.
1368 // This makes it visually clearer that the button is a button.
1369
1370 wxPoint position( 1, 0 );
1371
1372 Grabber * pGrabber = safenew Grabber(this, this->GetId());
1373 pGrabber->SetAsSpacer( true );
1374 //pGrabber->SetSize( 10, 27 ); // default is 10,27
1375 pGrabber->SetPosition( position );
1376
1377 position.x = 12;
1378
1379 auto size = theTheme.ImageSize( bmpRecoloredUpSmall );
1380 size.y = std::min(size.y, GetRulerHeight(false));
1381
1382 auto buttonMaker = [&]
1383 (wxWindowID id, teBmps bitmap, bool toggle)
1384 {
1385 const auto button =
1387 this,
1388 bmpRecoloredUpSmall, bmpRecoloredDownSmall,
1389 bmpRecoloredUpHiliteSmall, bmpRecoloredHiliteSmall,
1390 bitmap, bitmap, bitmap,
1391 id, position, toggle, size
1392 );
1393
1394 position.x += size.GetWidth();
1395 mButtons[iButton++] = button;
1396 return button;
1397 };
1398 auto button = buttonMaker(OnTogglePinnedStateID, bmpPlayPointerPinned, true);
1400 *button, 3,
1401 bmpRecoloredUpSmall, bmpRecoloredDownSmall,
1402 bmpRecoloredUpHiliteSmall, bmpRecoloredHiliteSmall,
1403 //bmpUnpinnedPlayHead, bmpUnpinnedPlayHead, bmpUnpinnedPlayHead,
1404 bmpRecordPointer, bmpRecordPointer, bmpRecordPointer,
1405 size);
1407 *button, 2,
1408 bmpRecoloredUpSmall, bmpRecoloredDownSmall,
1409 bmpRecoloredUpHiliteSmall, bmpRecoloredHiliteSmall,
1410 //bmpUnpinnedPlayHead, bmpUnpinnedPlayHead, bmpUnpinnedPlayHead,
1411 bmpRecordPointerPinned, bmpRecordPointerPinned, bmpRecordPointerPinned,
1412 size);
1414 *button, 1,
1415 bmpRecoloredUpSmall, bmpRecoloredDownSmall,
1416 bmpRecoloredUpHiliteSmall, bmpRecoloredHiliteSmall,
1417 //bmpUnpinnedPlayHead, bmpUnpinnedPlayHead, bmpUnpinnedPlayHead,
1418 bmpPlayPointer, bmpPlayPointer, bmpPlayPointer,
1419 size);
1420
1422}
1423
1425{
1427}
1428
1429namespace {
1431 {
1432#if 0
1433 if(scrubber.Seeks())
1434 /* i18n-hint: These commands assist the user in finding a sound by ear. ...
1435 "Scrubbing" is variable-speed playback, ...
1436 "Seeking" is normal speed playback but with skips
1437 */
1438 return XO("Click or drag to begin Seek");
1439 else
1440 /* i18n-hint: These commands assist the user in finding a sound by ear. ...
1441 "Scrubbing" is variable-speed playback, ...
1442 "Seeking" is normal speed playback but with skips
1443 */
1444 return XO("Click or drag to begin Scrub");
1445#else
1446 /* i18n-hint: These commands assist the user in finding a sound by ear. ...
1447 "Scrubbing" is variable-speed playback, ...
1448 "Seeking" is normal speed playback but with skips
1449 */
1450 return XO("Click & move to Scrub. Click & drag to Seek.");
1451#endif
1452 }
1453
1455 const Scrubber &scrubber, bool clicked)
1456 {
1457#if 0
1458 if(scrubber.Seeks())
1459 /* i18n-hint: These commands assist the user in finding a sound by ear. ...
1460 "Scrubbing" is variable-speed playback, ...
1461 "Seeking" is normal speed playback but with skips
1462 */
1463 return XO("Move to Seek");
1464 else
1465 /* i18n-hint: These commands assist the user in finding a sound by ear. ...
1466 "Scrubbing" is variable-speed playback, ...
1467 "Seeking" is normal speed playback but with skips
1468 */
1469 return XO("Move to Scrub");
1470#else
1471 if( clicked ) {
1472 // Since mouse is down, mention dragging first.
1473 // IsScrubbing is true if Scrubbing OR seeking.
1474 if( scrubber.IsScrubbing() )
1475 // User is dragging already, explain.
1476 return XO("Drag to Seek. Release to stop seeking.");
1477 else
1478 // User has clicked but not yet moved or released.
1479 return XO("Drag to Seek. Release and move to Scrub.");
1480 }
1481 // Since mouse is up, mention moving first.
1482 return XO("Move to Scrub. Drag to Seek.");
1483#endif
1484 }
1485
1486 const TranslatableString ScrubbingMessage(const Scrubber &scrubber, bool clicked)
1487 {
1488 if (scrubber.HasMark())
1489 return ContinueScrubbingMessage(scrubber, clicked);
1490 else
1491 return StartScrubbingMessage(scrubber);
1492 }
1493}
1494
1495void AdornedRulerPanel::OnIdle( wxIdleEvent &evt )
1496{
1497 evt.Skip();
1498 DoIdle();
1499}
1500
1502{
1503 bool changed = UpdateRects();
1504 changed = SetPanelSize() || changed;
1505
1506 auto &project = *mProject;
1507 auto &viewInfo = ViewInfo::Get( project );
1508 const auto &selectedRegion = viewInfo.selectedRegion;
1509 const auto &playRegion = viewInfo.playRegion;
1510
1511 changed = changed
1512 || mLastDrawnSelectedRegion != selectedRegion
1513 || mLastDrawnPlayRegion != std::pair{
1514 playRegion.GetLastActiveStart(), playRegion.GetLastActiveEnd() }
1515 || mLastDrawnH != viewInfo.h
1516 || mLastDrawnZoom != viewInfo.GetZoom()
1517 || mLastPlayRegionActive != viewInfo.playRegion.Active()
1518 ;
1519 if (changed)
1520 // Cause ruler redraw anyway, because we may be zooming or scrolling,
1521 // showing or hiding the scrub bar, etc.
1522 Refresh();
1523}
1524
1526{
1527 if (evt.type == AudioIOEvent::MONITOR)
1528 return;
1529 if ( evt.type == AudioIOEvent::CAPTURE ) {
1530 if (evt.on)
1531 {
1532 mIsRecording = true;
1533 this->CellularPanel::CancelDragging( false );
1535
1537 }
1538 else {
1539 mIsRecording = false;
1541 }
1542 }
1543
1544 if ( !evt.on )
1545 // So that the play region is updated
1547}
1548
1549void AdornedRulerPanel::OnPaint(wxPaintEvent & WXUNUSED(evt))
1550{
1551 const auto &viewInfo = ViewInfo::Get( *GetProject() );
1552 const auto &playRegion = viewInfo.playRegion;
1553 const auto playRegionBounds = std::pair{
1554 playRegion.GetLastActiveStart(), playRegion.GetLastActiveEnd() };
1555 mLastDrawnH = viewInfo.h;
1556 mLastDrawnZoom = viewInfo.GetZoom();
1557 mLastDrawnPlayRegion = playRegionBounds;
1558 mLastDrawnSelectedRegion = viewInfo.selectedRegion;
1559 // To do, note other fisheye state when we have that
1560
1561 wxPaintDC dc(this);
1562
1563 auto &backDC = GetBackingDCForRepaint();
1564
1565 DoDrawBackground(&backDC);
1566
1567 // Find play region rectangle, selected rectangle, and their overlap
1568 const auto rectP = PlayRegionRectangle(),
1569 rectS = SelectedRegionRectangle(),
1570 rectO = rectP.Intersect(rectS);
1571
1572 // What's left and right of the overlap? Assume same tops and bottoms
1573 const auto top = rectP.GetTop(),
1574 bottom = rectP.GetBottom();
1575 wxRect rectL{
1576 wxPoint{ 0, top }, wxPoint{ this->GetSize().GetWidth() - 1, bottom } };
1577 wxRect rectR = {};
1578 if (!rectO.IsEmpty()) {
1579 rectR = { wxPoint{ rectO.GetRight() + 1, top }, rectL.GetBottomRight() };
1580 rectL = { rectL.GetTopLeft(), wxPoint{ rectO.GetLeft() - 1, bottom } };
1581 }
1582
1583 DoDrawPlayRegion(&backDC, rectP, rectL, rectR);
1584 DoDrawOverlap(&backDC, rectO);
1585 DoDrawSelection(&backDC, rectS, rectL, rectR);
1586
1587 DoDrawPlayRegionLimits(&backDC, rectP);
1588
1589 DoDrawMarks(&backDC, true);
1590
1591 DoDrawEdge(&backDC);
1592
1593 DisplayBitmap(dc);
1594
1595 // Stroke extras direct to the client area,
1596 // maybe outside of the damaged area
1597 // As with TrackPanel, do not make a NEW wxClientDC or else Mac flashes badly!
1598 dc.DestroyClippingRegion();
1599 DrawOverlays(true, &dc);
1600}
1601
1602void AdornedRulerPanel::OnSize(wxSizeEvent &evt)
1603{
1604 mOuter = GetClientRect();
1605 if (mOuter.GetWidth() == 0 || mOuter.GetHeight() == 0)
1606 {
1607 return;
1608 }
1609
1610 UpdateRects();
1611
1613}
1614
1615void AdornedRulerPanel::OnLeave(wxMouseEvent& evt)
1616{
1617 evt.Skip();
1618 CallAfter([this]{
1620 });
1621}
1622
1623void AdornedRulerPanel::OnThemeChange(wxCommandEvent& evt)
1624{
1625 evt.Skip();
1627}
1628
1630{
1631 auto &selectedRegion = mViewInfo->selectedRegion;
1632 DoSelectionChange( selectedRegion );
1633}
1634
1636 const SelectedRegion &selectedRegion )
1637{
1638
1639 auto gAudioIO = AudioIOBase::Get();
1640 if ( !ViewInfo::Get( *mProject ).playRegion.Active() ) {
1641 // "Inactivated" play region follows the selection.
1642 SetPlayRegion( selectedRegion.t0(), selectedRegion.t1() );
1643 }
1644}
1645
1647{
1648 auto inner = mOuter;
1649 wxRect scrubZone;
1650 inner.x += LeftMargin;
1651 inner.width -= (LeftMargin + RightMargin);
1652
1653 auto top = &inner;
1654 auto bottom = &inner;
1655
1656 if (ShowingScrubRuler()) {
1657 scrubZone = inner;
1658 auto scrubHeight = std::min(scrubZone.height, (int)(ScrubHeight));
1659
1660 int topHeight;
1661#ifdef SCRUB_ABOVE
1662 top = &scrubZone, topHeight = scrubHeight;
1663#else
1664 auto qpHeight = scrubZone.height - scrubHeight;
1665 bottom = &scrubZone, topHeight = qpHeight;
1666 // Increase scrub zone height so that hit testing finds it and
1667 // not QP region, when on bottom 'edge'.
1668 scrubZone.height+=BottomMargin;
1669#endif
1670
1671 top->height = topHeight;
1672 bottom->height -= topHeight;
1673 bottom->y += topHeight;
1674 }
1675
1676 top->y += TopMargin;
1677 top->height -= TopMargin;
1678
1679 bottom->height -= BottomMargin;
1680
1681 if (!ShowingScrubRuler())
1682 scrubZone = inner;
1683
1684 if ( inner == mInner && scrubZone == mScrubZone )
1685 // no changes
1686 return false;
1687
1688 mInner = inner;
1689 mScrubZone = scrubZone;
1690
1691 mRuler.SetBounds(mInner.GetLeft(),
1692 mInner.GetTop(),
1693 mInner.GetRight(),
1694 mInner.GetBottom());
1695
1696 return true;
1697}
1698
1699double AdornedRulerPanel::Pos2Time(int p, bool ignoreFisheye) const
1700{
1702 , ignoreFisheye
1703 );
1704}
1705
1706int AdornedRulerPanel::Time2Pos(double t, bool ignoreFisheye) const
1707{
1709 , ignoreFisheye
1710 );
1711}
1712
1713bool AdornedRulerPanel::IsWithinMarker(int mousePosX, double markerTime)
1714{
1715 if (markerTime < 0)
1716 return false;
1717
1718 int pixelPos = Time2Pos(markerTime);
1719 int boundLeft = pixelPos - SELECT_TOLERANCE_PIXEL;
1720 int boundRight = pixelPos + SELECT_TOLERANCE_PIXEL;
1721
1722 return mousePosX >= boundLeft && mousePosX < boundRight;
1723}
1724
1725#ifdef QUICK_PLAY_HANDLE
1726auto AdornedRulerPanel::QPHandle::Click(
1727 const TrackPanelMouseEvent &event, AudacityProject *pProject) -> Result
1728{
1729 auto result = CommonRulerHandle::Click(event, pProject);
1730 if (!( result & RefreshCode::Cancelled )) {
1731 if (mClicked == Button::Left) {
1732 if (!mParent)
1734
1735 auto &scrubber = Scrubber::Get( *pProject );
1736 if(scrubber.HasMark()) {
1737 // We can't stop scrubbing yet (see comments in Bug 1391),
1738 // but we can pause it.
1739 ProjectAudioManager::Get( *pProject ).OnPause();
1740 }
1741
1742 // Store the initial play region state
1743 const auto &viewInfo = ViewInfo::Get( *pProject );
1744 const auto &playRegion = viewInfo.playRegion;
1745 mParent->mOldPlayRegion = playRegion;
1746
1747 // Save old selection, in case drag of selection is cancelled
1748 mOldSelection = ViewInfo::Get( *pProject ).selectedRegion;
1749
1750 mParent->HandleQPClick( event.event, mX );
1751 mParent->HandleQPDrag( event.event, mX );
1752 }
1753 }
1754
1755 return result;
1756}
1757
1758void AdornedRulerPanel::HandleQPClick(wxMouseEvent &evt, wxCoord mousePosX)
1759{
1760 // Temporarily inactivate play region
1761 if (mOldPlayRegion.Active() && evt.LeftDown()) {
1762 //mPlayRegionLock = true;
1764 }
1765
1768 bool isWithinStart = IsWithinMarker(mousePosX, mOldPlayRegion.GetStart());
1769 bool isWithinEnd = IsWithinMarker(mousePosX, mOldPlayRegion.GetEnd());
1770
1771 if (isWithinStart || isWithinEnd) {
1772 // If Quick-Play is playing from a point, we need to treat it as a click
1773 // not as dragging.
1774 if (mOldPlayRegion.Empty())
1776 // otherwise check which marker is nearer
1777 else {
1778 // Don't compare times, compare positions.
1779 //if (fabs(mQuickPlayPos[0] - mPlayRegionStart) < fabs(mQuickPlayPos[0] - mPlayRegionEnd))
1780 auto start = mOldPlayRegion.GetStart();
1781 auto end = mOldPlayRegion.GetEnd();
1782 if (abs(Time2Pos(mQuickPlayPos[0]) - Time2Pos(start)) <
1783 abs(Time2Pos(mQuickPlayPos[0]) - Time2Pos(end)))
1785 else
1787 }
1788 }
1789 else {
1790 // Clicked but not yet dragging
1792 }
1793}
1794
1795auto AdornedRulerPanel::QPHandle::Drag(
1796 const TrackPanelMouseEvent &event, AudacityProject *pProject) -> Result
1797{
1798 auto result = CommonRulerHandle::Drag(event, pProject);
1799 if (!( result & RefreshCode::Cancelled )) {
1800 if (mClicked == Button::Left) {
1801 if ( mParent ) {
1802 mX = event.event.m_x;
1803 mParent->UpdateQuickPlayPos( mX );
1804 mParent->HandleQPDrag( event.event, mX );
1805 }
1806 }
1807 }
1808 return result;
1809}
1810
1811void AdornedRulerPanel::HandleQPDrag(wxMouseEvent &/*event*/, wxCoord mousePosX)
1812{
1813 bool isWithinClick =
1814 (mLeftDownClickUnsnapped >= 0) &&
1816 bool isWithinStart = IsWithinMarker(mousePosX, mOldPlayRegion.GetStart());
1817 bool isWithinEnd = IsWithinMarker(mousePosX, mOldPlayRegion.GetEnd());
1818 bool canDragSel = !mOldPlayRegion.Active() && mPlayRegionDragsSelection;
1819 auto &viewInfo = ViewInfo::Get( *GetProject() );
1820 auto &playRegion = viewInfo.playRegion;
1821
1822 switch (mMouseEventState)
1823 {
1824 case mesNone:
1825 // If close to either end of play region, snap to closest
1826 if (isWithinStart || isWithinEnd) {
1829 else
1831 }
1832 break;
1834 // Don't start dragging until beyond tolerance initial playback start
1835 if (!mIsDragging && isWithinStart)
1837 else
1838 mIsDragging = true;
1839 // avoid accidental tiny selection
1840 if (isWithinEnd)
1842 playRegion.SetStart( mQuickPlayPos[0] );
1843 if (canDragSel) {
1845 }
1846 break;
1848 if (!mIsDragging && isWithinEnd) {
1850 }
1851 else
1852 mIsDragging = true;
1853 if (isWithinStart) {
1855 }
1856 playRegion.SetEnd( mQuickPlayPos[0] );
1857 if (canDragSel) {
1859 }
1860 break;
1862
1863 // Don't start dragging until mouse is beyond tolerance of initial click.
1864 if (isWithinClick || mLeftDownClick == -1) {
1866 playRegion.SetTimes(mLeftDownClick, mLeftDownClick);
1867 }
1868 else {
1870 }
1871 break;
1873 if (isWithinClick) {
1875 }
1876
1878 playRegion.SetTimes( mQuickPlayPos[0], mLeftDownClick );
1879 else
1880 playRegion.SetTimes( mLeftDownClick, mQuickPlayPos[0] );
1881 if (canDragSel) {
1883 }
1884 break;
1885 }
1886 Refresh();
1887 Update();
1888}
1889#endif
1890
1892 const TrackPanelMouseState &, AudacityProject *pProject)
1894{
1895 auto &scrubber = Scrubber::Get( *pProject );
1896 auto message = ScrubbingMessage(scrubber, mClicked == Button::Left);
1897
1898 mParent->SetNumGuides(1);
1899 return {
1900 message,
1901 {},
1902 // Tooltip is same as status message, or blank
1903 ((mParent && mParent->mTimelineToolTip) ? message : TranslatableString{}),
1904 };
1905}
1906
1907#ifdef QUICK_PLAY_HANDLE
1908auto AdornedRulerPanel::QPHandle::Preview(
1909 const TrackPanelMouseState &state, AudacityProject *pProject)
1911{
1912 mParent->SetNumGuides(1);
1913 TranslatableString tooltip;
1914 #if 0
1915 if (mParent && mParent->mTimelineToolTip) {
1916 if (!mParent->mQuickPlayEnabled)
1917 tooltip = XO("Quick-Play disabled");
1918 else
1919 tooltip = XO("Quick-Play enabled");
1920 }
1921 #endif
1922
1923 TranslatableString message;
1924 auto &scrubber = Scrubber::Get( *pProject );
1925 const bool scrubbing = scrubber.HasMark();
1926 if (scrubbing)
1927 // Don't distinguish zones
1928 message = ScrubbingMessage(scrubber, false);
1929 else
1930 // message = Insert timeline status bar message here
1931 ;
1932
1933 static wxCursor cursorHand{ wxCURSOR_HAND };
1934 static wxCursor cursorSizeWE{ wxCURSOR_SIZEWE };
1935
1936 bool showArrows = false;
1937 if (mParent)
1938 showArrows =
1939 (mClicked == Button::Left)
1940 || mParent->IsWithinMarker(
1941 state.state.m_x, mParent->mOldPlayRegion.GetStart())
1942 || mParent->IsWithinMarker(
1943 state.state.m_x, mParent->mOldPlayRegion.GetEnd());
1944
1945 return {
1946 message,
1947 showArrows ? &cursorSizeWE : &cursorHand,
1948 tooltip,
1949 };
1950}
1951
1953 const TrackPanelMouseEvent &event, AudacityProject *pProject,
1954 wxWindow *pParent)
1955 -> Result
1956{
1957 // Keep a shared pointer to self. Otherwise *this might get deleted
1958 // in HandleQPRelease on Windows! Because there is an event-loop yield
1959 // stopping playback, which caused OnCaptureLost to be called, which caused
1960 // clearing of CellularPanel targets!
1961 auto saveMe = mParent->mQPCell->mHolder.lock();
1962
1963 auto result = CommonRulerHandle::Release(event, pProject, pParent);
1964 if (!( result & RefreshCode::Cancelled )) {
1965 if (mClicked == Button::Left) {
1966 if ( mParent ) {
1967 mParent->HandleQPRelease( event.event );
1968 // Update the hot zones for cursor changes
1969 const auto &viewInfo = ViewInfo::Get( *pProject );
1970 const auto &playRegion = viewInfo.playRegion;
1971 mParent->mOldPlayRegion = playRegion;
1972 }
1973 }
1974 }
1975 return result;
1976}
1977
1978void AdornedRulerPanel::HandleQPRelease(wxMouseEvent &evt)
1979{
1980 auto &viewInfo = ViewInfo::Get( *GetProject() );
1981 auto &playRegion = viewInfo.playRegion;
1982 playRegion.Order();
1983
1984 const double t0 = mTracks->GetStartTime();
1985 const double t1 = mTracks->GetEndTime();
1986 const auto &selectedRegion = viewInfo.selectedRegion;
1987 const double sel0 = selectedRegion.t0();
1988 const double sel1 = selectedRegion.t1();
1989
1990 // We want some audio in the selection, but we allow a dragged
1991 // region to include selected white-space and space before audio start.
1992 if (evt.ShiftDown() && playRegion.Empty()) {
1993 // Looping the selection or project.
1994 // Disable if track selection is in white-space beyond end of tracks and
1995 // play position is outside of track contents.
1996 if (((sel1 < t0) || (sel0 > t1)) &&
1997 ((playRegion.GetStart() < t0) || (playRegion.GetStart() > t1))) {
1999 }
2000 }
2001 // Disable if beyond end.
2002 else if (playRegion.GetStart() >= t1) {
2004 }
2005 // Disable if empty selection before start.
2006 // (allow Quick-Play region to include 'pre-roll' white space)
2007 else if (
2008 playRegion.GetEnd() - playRegion.GetStart() > 0.0 &&
2009 playRegion.GetEnd() < t0
2010 ) {
2012 }
2013
2015 mIsDragging = false;
2016 mLeftDownClick = -1;
2017
2018 auto cleanup = finally( [&] {
2019 if (mOldPlayRegion.Active()) {
2020 // Restore Locked Play region
2021 SetPlayRegion(mOldPlayRegion.GetStart(), mOldPlayRegion.GetEnd());
2022 SelectUtilities::ActivatePlayRegion(*mProject);
2023 // and release local lock
2024 mOldPlayRegion.SetActive( false );
2025 }
2026 } );
2027
2028 StartQPPlay(!evt.ShiftDown(), evt.ControlDown());
2029}
2030
2031auto AdornedRulerPanel::QPHandle::Cancel(AudacityProject *pProject) -> Result
2032{
2033 auto result = CommonRulerHandle::Cancel(pProject);
2034
2035 if (mClicked == Button::Left) {
2036 if( mParent ) {
2037 ViewInfo::Get( *pProject ).selectedRegion = mOldSelection;
2038 mParent->mMouseEventState = mesNone;
2039 mParent->SetPlayRegion(
2040 mParent->mOldPlayRegion.GetStart(), mParent->mOldPlayRegion.GetEnd());
2041 if (mParent->mOldPlayRegion.Active()) {
2042 // Restore Locked Play region
2044 // and release local lock
2045 mParent->mOldPlayRegion.SetActive( false );
2046 }
2047 }
2048 }
2049
2050 return result;
2051}
2052#endif
2053
2055 bool newDefault, bool cutPreview, const double *pStartTime)
2056{
2057 const double t0 = mTracks->GetStartTime();
2058 const double t1 = mTracks->GetEndTime();
2059 auto &viewInfo = ViewInfo::Get( *mProject );
2060 const auto &playRegion = viewInfo.playRegion;
2061 const auto &selectedRegion = viewInfo.selectedRegion;
2062 const double sel0 = selectedRegion.t0();
2063 const double sel1 = selectedRegion.t1();
2064
2065 // Start / Restart playback on left click.
2066 bool startPlaying = true; // = (playRegion.GetStart() >= 0);
2067
2068 if (startPlaying) {
2069 bool loopEnabled = true;
2070 auto oldStart = std::max(0.0, playRegion.GetStart());
2071 double start = oldStart, end = 0;
2072
2073 if (playRegion.Empty()) {
2074 // Play either a selection or the project.
2075 if (oldStart > sel0 && oldStart < sel1) {
2076 // we are in a selection, so use the selection
2077 start = sel0;
2078 end = sel1;
2079 } // not in a selection, so use the project
2080 else {
2081 start = t0;
2082 end = t1;
2083 }
2084 }
2085 else
2086 end = std::max(start, playRegion.GetEnd());
2087
2088 // Looping a tiny selection may freeze, so just play it once.
2089 loopEnabled = ((end - start) > 0.001)? true : false;
2090
2091 newDefault = (loopEnabled && newDefault);
2092 if (newDefault)
2093 cutPreview = false;
2094 auto options = DefaultPlayOptions( *mProject, newDefault );
2095
2096 if (!cutPreview) {
2097 if (pStartTime)
2098 options.pStartTime.emplace(*pStartTime);
2099 }
2100 else
2101 options.envelope = nullptr;
2102
2103 auto mode =
2104 cutPreview ? PlayMode::cutPreviewPlay
2105 : newDefault ? PlayMode::loopedPlay
2107
2108 // Stop only after deciding where to start again, because an event
2109 // callback may change the play region back to the selection
2110 auto &projectAudioManager = ProjectAudioManager::Get( *mProject );
2111 projectAudioManager.Stop();
2112
2113 // Don't change play region, assume caller set it as needed
2114 // playRegion.SetTimes( start, end );
2115 // Refresh();
2116
2117 projectAudioManager.PlayPlayRegion((SelectedRegion(start, end)),
2118 options, mode,
2119 false);
2120
2121 }
2122}
2123
2124#if 0
2125// This version toggles ruler state indirectly via the scrubber
2126// to ensure that all the places where the state is shown update.
2127// For example buttons and menus must update.
2128void AdornedRulerPanel::OnToggleScrubRulerFromMenu(wxCommandEvent&)
2129{
2130 auto &scrubber = Scrubber::Get( *mProject );
2131 scrubber.OnToggleScrubRuler(*mProject);
2132}
2133#endif
2134
2135
2137{
2138 const auto oldSize = GetSize();
2139 wxSize size { oldSize.GetWidth(), GetRulerHeight(ShowingScrubRuler()) };
2140 if ( size != oldSize ) {
2141 SetSize(size);
2142 SetMinSize(size);
2143 GetParent()->PostSizeEventToParent();
2144 return true;
2145 }
2146 else
2147 return false;
2148}
2149
2151{
2152 auto pCellularPanel =
2153 dynamic_cast<CellularPanel*>( &GetProjectPanel( *GetProject() ) );
2154 if ( !pCellularPanel ) {
2155 wxASSERT( false );
2156 }
2157 else
2158 pCellularPanel->DrawOverlays( false );
2159 DrawOverlays( false );
2160}
2161
2163{
2164 auto common = [this](
2165 AButton &button, const CommandID &commandName, const TranslatableString &label) {
2166 ComponentInterfaceSymbol command{ commandName, label };
2167 ToolBar::SetButtonToolTip( *mProject, button, &command, 1u );
2168 button.SetLabel( Verbatim( button.GetToolTipText() ) );
2169
2170 button.UpdateStatus();
2171 };
2172
2173 {
2174 // The button always reflects the pinned head preference, even though
2175 // there is also a Playback preference that may overrule it for scrubbing
2177 auto pinButton = static_cast<AButton*>(FindWindow(OnTogglePinnedStateID));
2178 if( !state )
2179 pinButton->PopUp();
2180 else
2181 pinButton->PushDown();
2182 auto gAudioIO = AudioIO::Get();
2183 pinButton->SetAlternateIdx(
2184 (gAudioIO->IsCapturing() ? 2 : 0) + (state ? 0 : 1));
2185 // Bug 1584: Tooltip now shows what clicking will do.
2186 // Bug 2357: Action of button (and hence tooltip wording) updated.
2187 const auto label = XO("Timeline Options");
2188 common(*pinButton, wxT("PinnedHead"), label);
2189 }
2190}
2191
2192void AdornedRulerPanel::OnPinnedButton(wxCommandEvent & /*event*/)
2193{
2195}
2196
2197void AdornedRulerPanel::OnTogglePinnedState(wxCommandEvent & /*event*/)
2198{
2201}
2202
2204{
2205 // Invoked for mouse-over preview events, or dragging, or scrub position
2206 // polling updates. Remember x coordinates, converted to times, for
2207 // drawing of guides.
2208
2209 // Keep Quick-Play within usable track area. (Dependent on zoom)
2210 const auto &viewInfo = ViewInfo::Get( *mProject );
2211 auto width = viewInfo.GetTracksUsableWidth();
2212 mousePosX = std::max(mousePosX, viewInfo.GetLeftOffset());
2213 mousePosX = std::min(mousePosX, viewInfo.GetLeftOffset() + width - 1);
2214 const auto time = Pos2Time(mousePosX);
2215
2216 for (size_t ii = 0; ii < mNumGuides; ++ii) {
2218 time + mQuickPlayOffset[ii];
2219 HandleSnapping(ii);
2220 }
2221}
2222
2223// Pop-up menus
2224
2225void AdornedRulerPanel::ShowMenu(const wxPoint & pos)
2226{
2227 const auto &viewInfo = ViewInfo::Get( *GetProject() );
2228 const auto &playRegion = viewInfo.playRegion;
2229 wxMenu rulerMenu;
2230
2231 auto pDrag = rulerMenu.AppendCheckItem(OnSyncQuickPlaySelID, _("Enable dragging selection"));
2232 pDrag->Check(mPlayRegionDragsSelection && playRegion.Active());
2233 pDrag->Enable(playRegion.Active());
2234
2235 rulerMenu.AppendCheckItem(OnAutoScrollID, _("Update display while playing"))->
2237
2238 {
2239 auto item = rulerMenu.AppendCheckItem(OnTogglePlayRegionID,
2241 item->Check(playRegion.Active());
2242 }
2243
2244 {
2245 auto item = rulerMenu.Append(OnClearPlayRegionID,
2246 /* i18n-hint Clear is a verb */
2247 _("Clear Looping Region"));
2248 }
2249
2250 {
2251 auto item = rulerMenu.Append(OnSetPlayRegionToSelectionID,
2252 _("Set Loop To Selection"));
2253 }
2254
2255 rulerMenu.AppendSeparator();
2256 rulerMenu.AppendCheckItem(OnTogglePinnedStateID, _("Pinned Play Head"))->
2258
2259 BasicMenu::Handle{ &rulerMenu }.Popup(
2261 { pos.x, pos.y }
2262 );
2263}
2264
2265void AdornedRulerPanel::ShowScrubMenu(const wxPoint & pos)
2266{
2267 auto &scrubber = Scrubber::Get( *mProject );
2268 PushEventHandler(&scrubber);
2269 auto cleanup = finally([this]{ PopEventHandler(); });
2270
2271 wxMenu rulerMenu;
2272 scrubber.PopulatePopupMenu(rulerMenu);
2273 BasicMenu::Handle{ &rulerMenu }.Popup(
2275 { pos.x, pos.y }
2276 );
2277}
2278
2280{
2282 gPrefs->Write(wxT("/QuickPlay/DragSelection"), mPlayRegionDragsSelection);
2283 gPrefs->Flush();
2284}
2285
2287{
2288 auto &viewInfo = ViewInfo::Get( project );
2289 const auto &playRegion = viewInfo.playRegion;
2290 auto &selectedRegion = viewInfo.selectedRegion;
2291 selectedRegion.setT0(playRegion.GetStart(), false);
2292 selectedRegion.setT1(playRegion.GetEnd(), true);
2293}
2294
2296{
2297 // Play region dragging can snap to selection boundaries
2298 const auto &selectedRegion = ViewInfo::Get(*GetProject()).selectedRegion;
2299 SnapPointArray candidates;
2301 candidates = {
2302 SnapPoint{ selectedRegion.t0() },
2303 SnapPoint{ selectedRegion.t1() },
2304 };
2305 SnapManager snapManager{ *mProject, *mTracks, *mViewInfo, move(candidates) };
2306 auto results = snapManager.Snap(nullptr, mQuickPlayPos[index], false);
2307 mQuickPlayPos[index] = results.outTime;
2308 mIsSnapped[index] = results.Snapped();
2309}
2310
2311#if 0
2312void AdornedRulerPanel::OnTimelineToolTips(wxCommandEvent&)
2313{
2314 mTimelineToolTip = (mTimelineToolTip)? false : true;
2315 gPrefs->Write(wxT("/QuickPlay/ToolTips"), mTimelineToolTip);
2316 gPrefs->Flush();
2317}
2318#endif
2319
2321{
2323 gPrefs->Write(wxT("/GUI/AutoScroll"), false);
2324 else
2325 gPrefs->Write(wxT("/GUI/AutoScroll"), true);
2326
2327 gPrefs->Flush();
2328
2330}
2331
2332
2334{
2336}
2337
2339{
2341}
2342
2344{
2346}
2347
2348
2350 MenuChoice choice, const wxPoint *pPosition)
2351{
2352 wxPoint position;
2353 if(pPosition)
2354 position = *pPosition;
2355 else
2356 {
2357 auto rect = GetRect();
2358 //Old code put menu too low down. y position applied twice.
2359 //position = { rect.GetLeft() + 1, rect.GetBottom() + 1 };
2360
2361 // The cell does not pass in the mouse or button position.
2362 // We happen to know this is the pin/unpin button
2363 // so these magic values 'fix a bug' - but really the cell should
2364 // pass more information to work with in.
2365 position = { rect.GetLeft() + 38, rect.GetHeight()/2 + 1 };
2366 }
2367
2368 switch (choice) {
2370 ShowMenu(position);
2372 break;
2373 case MenuChoice::Scrub:
2374 ShowScrubMenu(position); break;
2375 default:
2376 return;
2377 }
2378}
2379
2380using ColorId = decltype(clrTrackInfo);
2381
2383{
2384 return clrTrackInfo;
2385}
2386
2388{
2389 return clrTrackPanelText;
2390}
2391
2393{
2394 return TimelineTextColor();
2395}
2396
2398{
2399 return isActive ? clrRulerBackground : clrClipAffordanceInactiveBrush;
2400}
2401
2402static inline wxColour AlphaBlend(ColorId fg, ColorId bg, double alpha)
2403{
2404 const auto &fgc = theTheme.Colour(fg);
2405 const auto &bgc = theTheme.Colour(bg);
2406 return wxColour{
2407 wxColour::AlphaBlend(fgc.Red(), bgc.Red(), alpha),
2408 wxColour::AlphaBlend(fgc.Green(), bgc.Green(), alpha),
2409 wxColour::AlphaBlend(fgc.Blue(), bgc.Blue(), alpha)
2410 };
2411}
2412
2414{
2415 // Draw AdornedRulerPanel border
2417 dc->DrawRectangle( mInner );
2418
2419 if (ShowingScrubRuler()) {
2420 // Let's distinguish the scrubbing area by using a themable
2421 // colour and a line to set it off.
2422 AColor::UseThemeColour(dc, clrScrubRuler, TimelineTextColor() );
2423 wxRect ScrubRect = mScrubZone;
2424 ScrubRect.Inflate( 1,0 );
2425 dc->DrawRectangle(ScrubRect);
2426 }
2427}
2428
2430{
2431 wxRect r = mOuter;
2432 r.width -= RightMargin;
2433 r.height -= BottomMargin;
2434 AColor::BevelTrackInfo( *dc, true, r );
2435
2436 // Black stroke at bottom
2437 dc->SetPen( *wxBLACK_PEN );
2438 AColor::Line( *dc, mOuter.x,
2439 mOuter.y + mOuter.height - 1,
2440 mOuter.x + mOuter.width - 1 ,
2441 mOuter.y + mOuter.height - 1 );
2442}
2443
2444void AdornedRulerPanel::DoDrawMarks(wxDC * dc, bool /*text */ )
2445{
2446 const double min = Pos2Time(0);
2447 const double hiddenMin = Pos2Time(0, true);
2448 const double max = Pos2Time(mInner.width);
2449 const double hiddenMax = Pos2Time(mInner.width, true);
2450
2452 mRuler.SetRange( min, max, hiddenMin, hiddenMax );
2453 mRuler.Draw( *dc );
2454}
2455
2457{
2458 Refresh();
2459}
2460
2462{
2463 const auto &viewInfo = ViewInfo::Get(*mProject);
2464 const auto &playRegion = viewInfo.playRegion;
2465 const auto t0 = playRegion.GetLastActiveStart(),
2466 t1 = playRegion.GetLastActiveEnd();
2467 return RegionRectangle(t0, t1);
2468}
2469
2471{
2472 const auto &viewInfo = ViewInfo::Get(*mProject);
2473 const auto &selectedRegion = viewInfo.selectedRegion;
2474 const auto t0 = selectedRegion.t0(), t1 = selectedRegion.t1();
2475 return RegionRectangle(t0, t1);
2476}
2477
2478wxRect AdornedRulerPanel::RegionRectangle(double t0, double t1) const
2479{
2480 int p0 = -1, p1 = -1;
2481 if (t0 == t1)
2482 // Make the rectangle off-screen horizontally, but set the height
2483 ;
2484 else {
2485 p0 = max(1, Time2Pos(t0));
2486 p1 = min(mInner.width, Time2Pos(t1));
2487 }
2488
2489 const int left = p0, top = mInner.y, right = p1, bottom = mInner.GetBottom();
2490 return { wxPoint{left, top}, wxPoint{right, bottom} };
2491}
2492
2494 wxDC * dc, const wxRect &rectP, const wxRect &rectL, const wxRect &rectR)
2495{
2496 const auto &viewInfo = ViewInfo::Get(*mProject);
2497 const auto &playRegion = viewInfo.playRegion;
2498 if (playRegion.IsLastActiveRegionClear())
2499 return;
2500
2501 const bool isActive = (mLastPlayRegionActive = playRegion.Active());
2502
2503 // Paint the selected region bolder if independently varying, else dim
2504 const auto color = TimelineLoopRegionColor(isActive);
2505 dc->SetBrush( wxBrush( theTheme.Colour( color )) );
2506 dc->SetPen( wxPen( theTheme.Colour( color )) );
2507
2508 dc->DrawRectangle( rectP.Intersect(rectL) );
2509 dc->DrawRectangle( rectP.Intersect(rectR) );
2510}
2511
2512void AdornedRulerPanel::DoDrawPlayRegionLimits(wxDC * dc, const wxRect &rect)
2513{
2514 // Color the edges of the play region like the ticks and numbers
2515 ADCChanger cleanup( dc );
2516 const auto edgeColour = theTheme.Colour(TimelineLimitsColor());
2517 dc->SetPen( { edgeColour } );
2518 dc->SetBrush( { edgeColour } );
2519
2520 constexpr int side = 7;
2521 constexpr int sideLessOne = side - 1;
2522
2523 // Paint two shapes, each a line plus triangle at bottom
2524 const auto left = rect.GetLeft(),
2525 right = rect.GetRight(),
2526 bottom = rect.GetBottom(),
2527 top = rect.GetTop();
2528 {
2529 wxPoint points[]{
2530 {left, bottom - sideLessOne},
2531 {left - sideLessOne, bottom},
2532 {left, bottom},
2533 {left, top},
2534 };
2535 dc->DrawPolygon( 4, points );
2536 }
2537
2538 {
2539 wxPoint points[]{
2540 {right, top},
2541 {right, bottom},
2542 {right + sideLessOne, bottom},
2543 {right, bottom - sideLessOne},
2544 };
2545 dc->DrawPolygon( 4, points );
2546 }
2547}
2548
2549constexpr double SelectionOpacity = 0.2;
2550
2551void AdornedRulerPanel::DoDrawOverlap(wxDC * dc, const wxRect &rect)
2552{
2553 dc->SetBrush( wxBrush{ AlphaBlend(
2555 SelectionOpacity) } );
2556 dc->SetPen( *wxTRANSPARENT_PEN );
2557 dc->DrawRectangle( rect );
2558}
2559
2561 wxDC * dc, const wxRect &rectS, const wxRect &rectL, const wxRect &rectR)
2562{
2563 dc->SetBrush( wxBrush{ AlphaBlend(
2565 dc->SetPen( *wxTRANSPARENT_PEN );
2566 dc->DrawRectangle( rectS.Intersect(rectL) );
2567 dc->DrawRectangle( rectS.Intersect(rectR) );
2568}
2569
2571{
2572 return ProperRulerHeight + (showScrubBar ? ScrubHeight : 0);
2573}
2574
2576{
2577 mLeftOffset = offset;
2579}
2580
2581// Draws the scrubbing/seeking indicator.
2583 wxDC * dc, wxCoord xx, int width, bool scrub, bool seek)
2584{
2585 ADCChanger changer(dc); // Undo pen and brush changes at function exit
2586
2587 wxPoint tri[ 3 ];
2588 if (seek) {
2589 auto height = IndicatorHeightForWidth(width);
2590 // Make four triangles
2591 const int TriangleWidth = width * 3 / 8;
2592
2593 // Double-double headed, left-right
2594 auto yy = ShowingScrubRuler()
2595 ? mScrubZone.y
2596 : (mInner.GetBottom() + 1) - 1 /* bevel */ - height;
2597 tri[ 0 ].x = xx - IndicatorOffset;
2598 tri[ 0 ].y = yy;
2599 tri[ 1 ].x = xx - IndicatorOffset;
2600 tri[ 1 ].y = yy + height;
2601 tri[ 2 ].x = xx - TriangleWidth;
2602 tri[ 2 ].y = yy + height / 2;
2603 dc->DrawPolygon( 3, tri );
2604
2605 tri[ 0 ].x -= TriangleWidth;
2606 tri[ 1 ].x -= TriangleWidth;
2607 tri[ 2 ].x -= TriangleWidth;
2608 dc->DrawPolygon( 3, tri );
2609
2610 tri[ 0 ].x = tri[ 1 ].x = xx + IndicatorOffset;
2611 tri[ 2 ].x = xx + TriangleWidth;
2612 dc->DrawPolygon( 3, tri );
2613
2614
2615 tri[ 0 ].x += TriangleWidth;
2616 tri[ 1 ].x += TriangleWidth;
2617 tri[ 2 ].x += TriangleWidth;
2618 dc->DrawPolygon( 3, tri );
2619 }
2620 else if (scrub) {
2621 auto height = IndicatorHeightForWidth(width);
2622 const int IndicatorHalfWidth = width / 2;
2623
2624 // Double headed, left-right
2625 auto yy = ShowingScrubRuler()
2626 ? mScrubZone.y
2627 : (mInner.GetBottom() + 1) - 1 /* bevel */ - height;
2628 tri[ 0 ].x = xx - IndicatorOffset;
2629 tri[ 0 ].y = yy;
2630 tri[ 1 ].x = xx - IndicatorOffset;
2631 tri[ 1 ].y = yy + height;
2632 tri[ 2 ].x = xx - IndicatorHalfWidth;
2633 tri[ 2 ].y = yy + height / 2;
2634 dc->DrawPolygon( 3, tri );
2635 tri[ 0 ].x = tri[ 1 ].x = xx + IndicatorOffset;
2636 tri[ 2 ].x = xx + IndicatorHalfWidth;
2637 dc->DrawPolygon( 3, tri );
2638 }
2639}
2640
2642 double playRegionStart, double playRegionEnd)
2643{
2644 // This is called by AudacityProject to make the play region follow
2645 // the current selection. But while the user is selecting a play region
2646 // with the mouse directly in the ruler, changes from outside are blocked.
2648 return;
2649
2650 auto &viewInfo = ViewInfo::Get( *GetProject() );
2651 auto &playRegion = viewInfo.playRegion;
2652 playRegion.SetTimes( playRegionStart, playRegionEnd );
2653
2654 Refresh();
2655}
2656
2658{
2659 ProjectAudioManager::Get( *mProject ).Stop();
2660
2661 auto &viewInfo = ViewInfo::Get( *GetProject() );
2662 auto &playRegion = viewInfo.playRegion;
2663 playRegion.SetTimes( -1, -1 );
2664
2665 Refresh();
2666}
2667
2668void AdornedRulerPanel::GetMaxSize(wxCoord *width, wxCoord *height)
2669{
2670 mRuler.GetMaxSize(width, height);
2671}
2672
2674
2676 s_AcceptsFocus = true;
2677 return TempAllowFocus{ &s_AcceptsFocus };
2678}
2679
2681{
2682 nn = std::min(nn, MAX_GUIDES);
2683 // If increasing the number of guides, reinitialize newer ones
2684 for (size_t ii = mNumGuides; ii < nn; ++ii) {
2685 mQuickPlayOffset[ii] = 0;
2686 mQuickPlayPosUnsnapped[ii] = 0;
2687 mQuickPlayPos[ii] = 0;
2688 mIsSnapped[ii] = false;
2689 }
2690 mNumGuides = nn;
2691}
2692
2694{
2695 auto temp = TemporarilyAllowFocus();
2696 SetFocus();
2697}
2698
2699// Second-level subdivision includes quick-play region and maybe the scrub bar
2700// and also shaves little margins above and below
2702 explicit Subgroup( const AdornedRulerPanel &ruler ) : mRuler{ ruler } {}
2703 Subdivision Children( const wxRect & ) override
2704 {
2705 return { Axis::Y, ( mRuler.ShowingScrubRuler() )
2706 ? Refinement{
2707 { mRuler.mInner.GetTop(), mRuler.mQPCell },
2708 { mRuler.mScrubZone.GetTop(), mRuler.mScrubbingCell },
2709 { mRuler.mScrubZone.GetBottom() + 1, nullptr }
2710 }
2711 : Refinement{
2712 { mRuler.mInner.GetTop(), mRuler.mQPCell },
2713 { mRuler.mInner.GetBottom() + 1, nullptr }
2714 }
2715 };
2716 }
2718};
2719
2720// Top-level subdivision shaves little margins off left and right
2722 explicit MainGroup( const AdornedRulerPanel &ruler ) : mRuler{ ruler } {}
2723 Subdivision Children( const wxRect & ) override
2724 { return { Axis::X, Refinement{
2725 // Subgroup is a throwaway object
2726 { mRuler.mInner.GetLeft(), std::make_shared< Subgroup >( mRuler ) },
2727 { mRuler.mInner.GetRight() + 1, nullptr }
2728 } }; }
2730};
2731
2733{
2734 auto &scrubber = Scrubber::Get( *GetProject() );
2735 return scrubber.ShowsBar();
2736}
2737
2738// CellularPanel implementation
2739std::shared_ptr<TrackPanelNode> AdornedRulerPanel::Root()
2740{
2741 // Root is a throwaway object
2742 return std::make_shared< MainGroup >( *this );
2743}
2744
2746{
2747 return mProject;
2748}
2749
2750
2752{
2753 // No switching of focus yet to the other, scrub zone
2754 return mQPCell.get();
2755}
2756
2757
2759{
2760}
2761
2762
2764 TrackPanelCell *, TrackPanelCell *, unsigned refreshResult)
2765{
2766 if (refreshResult & RefreshCode::RefreshAll)
2767 Refresh(); // Overlays will be repainted too
2768 else if (refreshResult & RefreshCode::DrawOverlays)
2769 DrawBothOverlays(); // cheaper redrawing of guidelines only
2770}
2771
2773{
2774 ProjectStatus::Get( *GetProject() ).Set(message);
2775}
2776
2778{
2779 if (!mOverlay) {
2780 mOverlay =
2781 std::make_shared<TrackPanelGuidelineOverlay>( mProject );
2782 auto pCellularPanel =
2783 dynamic_cast<CellularPanel*>( &GetProjectPanel( *GetProject() ) );
2784 if ( !pCellularPanel ) {
2785 wxASSERT( false );
2786 }
2787 else
2788 pCellularPanel->AddOverlay( mOverlay );
2789 this->AddOverlay( mOverlay->mPartner );
2790 }
2791}
2792
2794{
2798
2799 auto &project = *mProject;
2800 // Update button image
2802
2803 auto &scrubber = Scrubber::Get( project );
2804 if (scrubber.HasMark())
2805 scrubber.SetScrollScrubbing(value);
2806}
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
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:182
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:25
void UpdateStatus()
Definition: AButton.cpp:495
void PopUp()
Definition: AButton.cpp:605
void SetLabel(const TranslatableString &label)
Definition: AButton.cpp:274
static void IndicatorColor(wxDC *dc, bool bIsNotRecording)
Definition: AColor.cpp:449
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:465
static void Light(wxDC *dc, bool selected, bool highlight=false)
Definition: AColor.cpp:390
static void BevelTrackInfo(wxDC &dc, bool up, const wxRect &r, bool highlight=false)
Definition: AColor.cpp:317
static void UseThemeColour(wxDC *dc, int iBrush, int iPen=-1, int alpha=255)
Definition: AColor.cpp:349
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)
void OnThemeChange(wxCommandEvent &evt)
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
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:89
static AudioIO * Get()
Definition: AudioIO.cpp:140
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:207
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:165
bool IsScrubbing() const
static Scrubber & Get(AudacityProject &project)
Definition: Scrubbing.cpp:202
bool HasMark() const
Definition: Scrubbing.h:90
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:185
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:873
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:840
static void MakeButtonBackgroundsSmall()
Definition: ToolBar.cpp:802
static void SetButtonToolTip(AudacityProject &project, AButton &button, const ComponentInterfaceSymbol commands[], size_t nCommands)
Definition: ToolBar.cpp:898
double GetEndTime() const
Definition: Track.cpp:1029
double GetStartTime() const
Definition: Track.cpp:1024
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:467
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:38
void Release(wxWindow *handler)
auto end(const Ptr< Type, BaseDeleter > &p)
Enables range-for, if Traits<Type>::iterated_type is defined.
Definition: PackedArray.h:126
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:73
enum AudioIOEvent::Type type
Default message type for Publisher.
Definition: Observer.h:26
Window placement information for wxWidgetsBasicUI can be constructed from a wxWindow pointer.