Audacity 3.2.0
AdornedRulerPanel.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 AdornedRulerPanel.cpp
6
7 Dominic Mazzoni
8
9*******************************************************************//******************************************************************/
20
21
22#include "AdornedRulerPanel.h"
23
24#include <wx/app.h>
25#include <wx/setup.h> // for wxUSE_* macros
26#include <wx/tooltip.h>
27
28#include "AColor.h"
29#include "AllThemeResources.h"
30#include "AudioIO.h"
31#include "widgets/BasicMenu.h"
32#include "Beats.h"
33#include "CellularPanel.h"
34#include "../images/Cursors.h"
35#include "HitTestResult.h"
36#include "Prefs.h"
37#include "Project.h"
38#include "ProjectAudioIO.h"
39#include "ProjectAudioManager.h"
40#include "ProjectWindows.h"
41#include "ProjectStatus.h"
42#include "ProjectTimeRuler.h"
44#include "ProjectWindow.h"
45#include "RefreshCode.h"
46#include "SelectUtilities.h"
47#include "Snap.h"
48#include "Track.h"
50#include "UIHandle.h"
51#include "ViewInfo.h"
52#include "prefs/TracksPrefs.h"
53#include "prefs/ThemePrefs.h"
54#include "toolbars/ToolBar.h"
56#include "tracks/ui/Scrubbing.h"
58#include "widgets/AButton.h"
59#include "AudacityMessageBox.h"
60#include "widgets/Grabber.h"
62#include "widgets/BeatsFormat.h"
63#include "widgets/TimeFormat.h"
65
66#include <wx/dcclient.h>
67#include <wx/menu.h>
68
69using std::min;
70using std::max;
71
72//#define SCRUB_ABOVE
73
74#define SELECT_TOLERANCE_PIXEL 4
75
76#define PLAY_REGION_TRIANGLE_SIZE 7
77#define PLAY_REGION_RECT_WIDTH 1
78#define PLAY_REGION_RECT_HEIGHT 3
79#define PLAY_REGION_GLOBAL_OFFSET_Y 7
80
81enum : int {
85
87 BottomMargin = 1, // for bottom bevel and bottom line
89
91};
92
93enum {
96};
97
98inline int IndicatorHeightForWidth(int width)
99{
100 return ((width / 2) * 3) / 2;
101}
102
103inline int IndicatorWidthForHeight(int height)
104{
105 // Not an exact inverse of the above, with rounding, but good enough
106 return std::max(static_cast<int>(IndicatorSmallWidth),
107 (((height) * 2) / 3) * 2
108 );
109}
110
112{
113 return std::max((int)(ScrubHeight - TopMargin),
114 (int)(IndicatorMediumWidth));
115}
116
118{
120}
121
123{
124public:
126 AdornedRulerPanel *pParent, wxCoord xx, MenuChoice menuChoice )
127 : mParent(pParent)
128 , mX( xx )
129 , mClickedX( xx )
130 , mChoice( menuChoice )
131 {}
132
133 std::shared_ptr<const Track> FindTrack() const override
134 { return nullptr; }
135
136 bool Clicked() const { return mClicked != Button::None; }
137
139 const CommonRulerHandle &oldState, const CommonRulerHandle &newState)
140 {
141 if (oldState.mX != newState.mX)
143 return 0;
144 }
145
146protected:
147 bool HandlesRightClick() override { return true; }
148
150 const TrackPanelMouseEvent &event, AudacityProject *) override
151 {
152 mClicked = event.event.LeftIsDown() ? Button::Left : Button::Right;
153 mClickedX = event.event.GetX();
155 }
156
158 const TrackPanelMouseEvent &, AudacityProject *) override
159 {
161 }
162
165 wxWindow *) override
166 {
167 if ( mParent && mClicked == Button::Right ) {
168 const auto pos = event.event.GetPosition();
169 mParent->ShowContextMenu( mChoice, &pos );
170 }
172 }
173
175 {
176 auto &viewInfo = ViewInfo::Get(project);
177 return viewInfo.PositionToTime(mX, viewInfo.GetLeftOffset());
178 }
179
180 // Danger! `this` may be deleted!
181 void StartPlay(AudacityProject &project, const wxMouseEvent &event)
182 {
184
185 // Keep a shared pointer to self. Otherwise *this might get deleted
186 // in HandleQPRelease on Windows! Because there is an event-loop yield
187 // stopping playback, which caused OnCaptureLost to be called, which caused
188 // clearing of CellularPanel targets!
189 auto saveMe = ruler.Target();
190
191 const auto startTime = Time(project);
192 ruler.StartQPPlay(!event.ShiftDown(), false, &startTime);
193 }
194
196 {
198 }
199
200 void Enter(bool, AudacityProject *) override
201 {
203 }
204
205 wxWeakRef<AdornedRulerPanel> mParent;
206
207 wxCoord mX;
208 wxCoord mClickedX;
209
211
212 enum class Button { None, Left, Right };
214};
215
217public:
219 AdornedRulerPanel *pParent, wxCoord xx, MenuChoice menuChoice,
220 wxCursor *cursor,
221 size_t numGuides = 1)
222 : CommonRulerHandle{ pParent, xx, menuChoice }
223 , mNumGuides{ numGuides }
224 {
225 if (cursor) {
226 mCursor = *cursor;
227 }
228 }
229
231 AdornedRulerPanel *pParent, wxCoord xx, MenuChoice menuChoice,
232 wxCursor cursor,
233 size_t numGuides = 1)
234 : CommonRulerHandle{ pParent, xx, menuChoice }
235 , mCursor{ cursor }
236 , mNumGuides{ numGuides }
237 {}
238
239 void SetCursor(wxCursor cursor)
240 {
241 mCursor = cursor;
242 }
243
245 const TrackPanelMouseState &state, AudacityProject *pProject)
246 override
247 {
248 mParent->SetNumGuides(mNumGuides);
249 const auto message = XO("Click and drag to define a looping region.");
250 return {
251 message,
252 &mCursor,
253 message
254 };
255 }
256
258 const TrackPanelMouseEvent &event, AudacityProject *pProject) override
259 {
260 using namespace RefreshCode;
261 auto result = CommonRulerHandle::Drag(event, pProject);
262 if (0 != (result & Cancelled) || mClicked != Button::Left)
263 return result;
264
265 auto &ruler = AdornedRulerPanel::Get(*pProject);
266 mX = event.event.m_x;
267 ruler.UpdateQuickPlayPos(event.event.m_x);
268
269 if (!mDragged) {
270 if (fabs(mX - mClickedX) < SELECT_TOLERANCE_PIXEL)
271 // Don't start a drag yet for a small mouse movement
272 return RefreshNone;
273 SavePlayRegion(*pProject);
274 const auto time = SnappedTime(*pProject, 0);
275 DoStartAdjust(*pProject, time);
276 mDragged = true;
277 }
278 else
279 DoAdjust(*pProject);
280
282 DragSelection(*pProject);
283
284 return RefreshAll;
285 }
286
288 const TrackPanelMouseEvent &event,
289 AudacityProject *pProject, wxWindow *pParent)
290 override
291 {
292 using namespace RefreshCode;
293 auto result = CommonRulerHandle::Release(event, pProject, pParent);
294
295 if (mClicked == Button::Left && !mDragged && !mParent->mIsRecording)
296 StartPlay(*pProject, event.event);
297
298 if (!mDragged || 0 != (result & Cancelled))
299 return result;
300
301 // Set the play region endpoints correctly even if not strictly needed
302 auto &viewInfo = ViewInfo::Get( *pProject );
303 auto &playRegion = viewInfo.playRegion;
304 playRegion.Order();
305
306 return result;
307 }
308
309 Result Cancel(AudacityProject *pProject) override
310 {
311 using namespace RefreshCode;
312 auto result = CommonRulerHandle::Cancel(pProject);
313 if (!mSaved)
314 return result;
315
317 auto &viewInfo = ViewInfo::Get(*pProject);
318 viewInfo.selectedRegion = mOldSelectedRegion;
319 auto &playRegion = viewInfo.playRegion;
320 playRegion.SetTimes(mOldStart, mOldEnd);
321 if (!mWasActive)
322 playRegion.SetActive(false);
323
324 return RefreshAll;
325 }
326
327 // Compare with class SelectHandle. Perhaps a common base class for that
328 // class too should be defined.
329 bool HasEscape(AudacityProject *pProject) const override
330 {
331 auto &ruler = AdornedRulerPanel::Get(*pProject);
332 auto values = ruler.mIsSnapped;
333 auto identity = [](auto x){ return x; }; // in the C++20 standard...
334 return std::any_of( values, values + ruler.mNumGuides, identity );
335 }
336
337 bool Escape(AudacityProject *pProject) override
338 {
339 if (HasEscape(pProject)) {
340 Unsnap(false, pProject);
341 return true;
342 }
343 return false;
344 }
345
346protected:
347
348 double SnappedTime( AudacityProject &project, size_t ii )
349 {
350 const auto &ruler = AdornedRulerPanel::Get(project);
351 bool isSnapped = ruler.mIsSnapped[ii];
352 return isSnapped
353 ? ruler.mQuickPlayPos[ii]
354 : ruler.mQuickPlayPosUnsnapped[ii];
355 }
356
357 std::pair<double, double> SnappedTimes( AudacityProject &project )
358 {
359 const auto &ruler = AdornedRulerPanel::Get(project);
360 for (size_t ii = 0; ii < ruler.mNumGuides; ++ii)
361 if (ruler.mIsSnapped[ii]) {
362 double time0 = ruler.mQuickPlayPos[ii];
363 double delta = time0 - ruler.mQuickPlayPosUnsnapped[ii];
364 double time1 = ruler.mQuickPlayPosUnsnapped[1 - ii] + delta;
365 if (ii == 1)
366 return { time1, time0 };
367 else
368 return { time0, time1 };
369 }
370 // No snap
371 return { ruler.mQuickPlayPos[0], ruler.mQuickPlayPos[1] };
372 }
373
374 void Unsnap(bool use, AudacityProject *pProject)
375 {
376 auto &ruler = AdornedRulerPanel::Get(*pProject);
377 std::fill( ruler.mIsSnapped, ruler.mIsSnapped + ruler.mNumGuides, false );
378 // Repaint to turn the snap lines on or off
380 if (Clicked())
381 DoAdjust(*pProject);
382 }
383
384 virtual void DoStartAdjust(AudacityProject &, double) = 0;
385 virtual void DoAdjust(AudacityProject &) = 0;
386
388 {
389 auto &viewInfo = ViewInfo::Get(project);
390 mOldSelectedRegion = viewInfo.selectedRegion;
391 auto &playRegion = viewInfo.playRegion;
392 mWasActive = playRegion.Active();
393 mOldStart = playRegion.GetLastActiveStart();
394 mOldEnd = playRegion.GetLastActiveEnd();
395 if (!mWasActive)
396 playRegion.SetActive(true);
397 mSaved = true;
398 }
399
400private:
401 // wxCursor is a cheaply copied reference-counting handle to a resource
402 wxCursor mCursor;
404
406 double mOldStart = 0.0, mOldEnd = 0.0;
407 bool mWasActive = false;
408 bool mSaved = false;
409 bool mDragged = false;
410};
411
412/**********************************************************************
413
414ScrubbingRulerOverlay.
415Graphical helper for AdornedRulerPanel.
416
417**********************************************************************/
418
420
421// This is an overlay drawn on the ruler.
423{
424public:
426
429
430 bool mNewScrub {};
431 bool mNewSeek {};
432
433 void Update();
434
435private:
437
438 unsigned SequenceNumber() const override;
439
440 std::pair<wxRect, bool> DoGetRectangle(wxSize size) override;
441 void Draw(OverlayPanel &panel, wxDC &dc) override;
442
444
445 // Used by this only
447 bool mOldScrub {};
448 bool mOldSeek {};
449};
450
451/**********************************************************************
452
453 TrackPanelGuidelineOverlay.
454 Updated for mouse events in AdornedRulerPanel, but draws on the TrackPanel.
455
456 **********************************************************************/
457
458// This is an overlay drawn on a different window, the track panel.
459// It draws the pale guide line that follows mouse movement.
461{
464
465public:
467
468private:
469 void Update();
470
471 unsigned SequenceNumber() const override;
472 std::pair<wxRect, bool> DoGetRectangle(wxSize size) override;
473 void Draw(OverlayPanel &panel, wxDC &dc) override;
474
476
477 std::shared_ptr<ScrubbingRulerOverlay> mPartner
478 { std::make_shared<ScrubbingRulerOverlay>(*this) };
479
481
485};
486
487/**********************************************************************
488
489 Implementation of ScrubbingRulerOverlay.
490
491 **********************************************************************/
492
495: mPartner(partner)
496{
497}
498
500{
501 return &Get( *mPartner.mProject );
502}
503
505{
506 const auto project = mPartner.mProject;
507 auto &scrubber = Scrubber::Get( *project );
508 auto ruler = GetRuler();
509
510 bool scrubbing = (scrubber.IsScrubbing()
511 && !scrubber.IsSpeedPlaying()
512 && !scrubber.IsKeyboardScrubbing());
513
514 // Hide during transport, or if mouse is not in the ruler, unless scrubbing
515 if ((!ruler->LastCell() || ProjectAudioIO::Get( *project ).IsAudioActive())
516 && !scrubbing)
517 mNewQPIndicatorPos = -1;
518 else {
519 const auto &selectedRegion = ViewInfo::Get( *project ).selectedRegion;
520 double latestEnd =
521 std::max(ruler->mTracks->GetEndTime(), selectedRegion.t1());
522 // This will determine the x coordinate of the line and of the
523 // ruler indicator
524
525 // Test all snap points
526 mNewIndicatorSnapped = -1;
527 for (size_t ii = 0;
528 mNewIndicatorSnapped == -1 && ii < ruler->mNumGuides; ++ii) {
529 if (ruler->mIsSnapped[ii]) {
530 mNewIndicatorSnapped = ii;
531 }
532 }
533 mNewQPIndicatorPos = ruler->Time2Pos(
534 ruler->mQuickPlayPos[std::max(0, mNewIndicatorSnapped)]);
535
536 // These determine which shape is drawn on the ruler, and whether
537 // in the scrub or the qp zone
538 mNewScrub =
539 !ruler->IsMouseCaptured() && // not doing some other drag in the ruler
540 (ruler->LastCell() == ruler->mScrubbingCell ||
541 (scrubber.HasMark()));
542 mNewSeek = mNewScrub &&
543 (scrubber.Seeks() || scrubber.TemporarilySeeks());
544 }
545}
546
547unsigned
549{
550 return 30;
551}
552
553std::pair<wxRect, bool>
555{
556 Update();
557
558 const auto x = mOldQPIndicatorPos;
559 if (x >= 0) {
560 // These dimensions are always sufficient, even if a little
561 // excessive for the small triangle:
562 const int width = IndicatorBigWidth() * 3 / 2;
563 //const auto height = IndicatorHeightForWidth(width);
564
565 const int indsize = width / 2;
566
567 auto xx = x - indsize;
568 auto yy = 0;
569 return {
570 { xx, yy,
571 indsize * 2 + 1,
572 GetRuler()->GetSize().GetHeight() },
573 (x != mNewQPIndicatorPos
574 || (mOldScrub != mNewScrub)
575 || (mOldSeek != mNewSeek) )
576 };
577 }
578 else
579 return { {}, mNewQPIndicatorPos >= 0 };
580}
581
583 OverlayPanel & /*panel*/, wxDC &dc)
584{
585 mOldQPIndicatorPos = mNewQPIndicatorPos;
586 mOldScrub = mNewScrub;
587 mOldSeek = mNewSeek;
588 if (mOldQPIndicatorPos >= 0) {
589 auto ruler = GetRuler();
590 auto width = mOldScrub ? IndicatorBigWidth() : IndicatorSmallWidth;
591 ruler->DoDrawScrubIndicator(
592 &dc, mOldQPIndicatorPos, width, mOldScrub, mOldSeek);
593 }
594}
595
596/**********************************************************************
597
598 Implementation of TrackPanelGuidelineOverlay.
599
600 **********************************************************************/
601
605{
606}
607
608unsigned
610{
611 return 30;
612}
613
615{
616 const auto project = mProject;
617 auto &scrubber = Scrubber::Get( *project );
618 const auto ruler = &Get( *project );
619
620 // Determine the color of the line stroked over
621 // the track panel, green for scrub or yellow for snapped or white
622 mNewPreviewingScrub =
623 ruler->LastCell() == ruler->mScrubbingCell &&
624 !scrubber.IsScrubbing();
625}
626
627std::pair<wxRect, bool>
629{
630 Update();
631
632 wxRect rect(mOldQPIndicatorPos, 0, 1, size.GetHeight());
633 return std::make_pair(
634 rect,
635 (mOldQPIndicatorPos != mPartner->mNewQPIndicatorPos ||
636 mOldIndicatorSnapped != mPartner->mNewIndicatorSnapped ||
637 mOldPreviewingScrub != mNewPreviewingScrub)
638 );
639}
640
642 OverlayPanel &panel, wxDC &dc)
643{
644 mOldQPIndicatorPos = mPartner->mNewQPIndicatorPos;
645 mOldIndicatorSnapped = mPartner->mNewIndicatorSnapped;
646 mOldPreviewingScrub = mNewPreviewingScrub;
647
648 if (mOldQPIndicatorPos >= 0) {
649 if (!mOldPreviewingScrub && mOldIndicatorSnapped < 0) {
651 if (auto pHandle =
652 dynamic_cast<PlayRegionAdjustingHandle*>(ruler.Target().get());
653 pHandle != nullptr && pHandle->Clicked())
654 // Do not draw the quick-play guideline
655 return;
656 }
657
658 mOldPreviewingScrub
659 ? AColor::IndicatorColor(&dc, true) // Draw green line for preview.
660 : (mOldIndicatorSnapped >= 0)
661 ? AColor::SnapGuidePen(&dc) // Yellow snap guideline
662 : AColor::Light(&dc, false);
663
664 // Draw indicator in all visible tracks
665 auto pCellularPanel = dynamic_cast<CellularPanel*>( &panel );
666 if ( !pCellularPanel ) {
667 wxASSERT( false );
668 return;
669 }
670 pCellularPanel
671 ->VisitCells( [&]( const wxRect &rect, TrackPanelCell &cell ) {
672 const auto pChannelView = dynamic_cast<ChannelView*>(&cell);
673 if (!pChannelView)
674 return;
675
676 // Draw the NEW indicator in its NEW location
677 AColor::Line(dc,
678 mOldQPIndicatorPos,
679 rect.GetTop(),
680 mOldQPIndicatorPos,
681 rect.GetBottom());
682 } );
683 }
684}
685
686/**********************************************************************
687
688 Implementation of AdornedRulerPanel.
689 Either we find a way to make this more generic, Or it will move
690 out of the widgets subdirectory into its own source file.
691
692**********************************************************************/
693
694enum {
703};
704
705BEGIN_EVENT_TABLE(AdornedRulerPanel, CellularPanel)
706 EVT_IDLE( AdornedRulerPanel::OnIdle )
709 EVT_LEAVE_WINDOW(AdornedRulerPanel::OnLeave)
710
711 // Context menu commands
721
725
727
729{
730public:
731 explicit
733 : mParent{ parent }
734 , mMenuChoice{ menuChoice }
735 {}
736
738 const TrackPanelMouseState &, const AudacityProject *)
739 override
740 {
741 // May come here when recording is in progress, so hit tests are turned
742 // off.
743 TranslatableString tooltip = XO("Timeline actions disabled during recording");
744
745 static wxCursor cursor{ wxCURSOR_DEFAULT };
746 return {
747 {},
748 &cursor,
749 tooltip,
750 };
751 }
752
754 const wxRect &,
755 wxWindow *, const wxPoint *pPosition, AudacityProject*) final
756 {
757 mParent->ShowContextMenu(mMenuChoice, pPosition);
758 return 0;
759 }
760
761protected:
764};
765
766#undef QUICK_PLAY_HANDLE
767#ifdef QUICK_PLAY_HANDLE
768class AdornedRulerPanel::QPHandle final : public CommonRulerHandle
769{
770public:
771 explicit
772 QPHandle( AdornedRulerPanel *pParent, wxCoord xx )
774 {
775 }
776
777private:
779 const TrackPanelMouseEvent &event, AudacityProject *pProject) override;
780
781 Result Drag(
782 const TrackPanelMouseEvent &event, AudacityProject *pProject) override;
783
785 const TrackPanelMouseState &state, AudacityProject *pProject)
786 override;
787
789 const TrackPanelMouseEvent &event, AudacityProject *pProject,
790 wxWindow *pParent) override;
791
792 Result Cancel(AudacityProject *pProject) override;
793
794 SelectedRegion mOldSelection;
795};
796#endif
797
798static std::unique_ptr<wxCursor> handOpenCursor = nullptr;
799
801public:
803 : PlayRegionAdjustingHandle( pParent, xx, MenuChoice::QuickPlay, handOpenCursor.get(), 2 )
804 {
805 if (!handOpenCursor) {
806 handOpenCursor = MakeCursor(wxCURSOR_HAND, RearrangeCursorXpm, 16, 16);
808 }
809 }
810
812 {
813 mParent->mQuickPlayOffset[0] = 0;
814 mParent->mQuickPlayOffset[1] = 0;
815 }
816
817private:
818 void DoStartAdjust(AudacityProject &project, double) override
819 {
820 // Ignore the snapping of the time at the mouse
821 const auto time = Time(project);
822 auto &playRegion = ViewInfo::Get(project).playRegion;
823 mParent->mQuickPlayOffset[0] = playRegion.GetStart() - time;
824 mParent->mQuickPlayOffset[1] = playRegion.GetEnd() - time;
825 }
826
828 {
829 // Move the whole play region rigidly (usually)
830 // though the length might change slightly if only one edge snaps
831 const auto times = SnappedTimes(project);
832
833 auto &playRegion = ViewInfo::Get(project).playRegion;
834 playRegion.SetTimes(times.first, times.second);
835 }
836};
837
839public:
841 AdornedRulerPanel *pParent, wxCoord xx, bool hitLeft )
842 : PlayRegionAdjustingHandle( pParent, xx, MenuChoice::QuickPlay, {wxCURSOR_SIZEWE} )
843 , mHitLeft{ hitLeft }
844 {
845 }
846
847private:
848 void DoStartAdjust(AudacityProject &project, double time) override
849 {
850 }
851
853 {
854 const auto time = SnappedTime(project, 0);
855
856 // Change the play region
857 // The check whether this new time should be start or end isn't
858 // important. The accessors for PlayRegion use min and max of stored
859 // values.
860 auto &playRegion = ViewInfo::Get(project).playRegion;
861 if (mHitLeft)
862 playRegion.SetStart(time);
863 else
864 playRegion.SetEnd(time);
865 }
866
867 bool mHitLeft = false;
868};
869
871public:
873 : PlayRegionAdjustingHandle( pParent, xx, MenuChoice::QuickPlay, {wxCURSOR_DEFAULT} )
874 {
875 }
876
877private:
878 void DoStartAdjust(AudacityProject &project, double time) override
879 {
880 auto &playRegion = ViewInfo::Get(project).playRegion;
881 playRegion.SetTimes(time, time);
882 }
883
885 {
886 const auto time = SnappedTime(project, 0);
887
888 // Change the play region
889 // The check whether this new time should be start or end isn't
890 // important. The accessors for PlayRegion use min and max of stored
891 // values.
892 auto &playRegion = ViewInfo::Get(project).playRegion;
893 playRegion.SetEnd(time);
894 }
895};
896
897namespace
898{
899
900wxCoord GetPlayHeadX( const AudacityProject *pProject )
901{
902 const auto &viewInfo = ViewInfo::Get( *pProject );
903 auto width = viewInfo.GetTracksUsableWidth();
904 return viewInfo.GetLeftOffset()
906}
907
908double GetPlayHeadFraction( const AudacityProject *pProject, wxCoord xx )
909{
910 const auto &viewInfo = ViewInfo::Get( *pProject );
911 auto width = viewInfo.GetTracksUsableWidth();
912 auto fraction = (xx - viewInfo.GetLeftOffset()) / double(width);
913 return std::max(0.0, std::min(1.0, fraction));
914}
915
916// Handle for dragging the pinned play head, which so far does not need
917// to be a friend of the AdornedRulerPanel class, so we don't make it nested.
919{
920public:
921 PlayheadHandle( AdornedRulerPanel &parent, wxCoord xx )
922 : mpParent{ &parent }
923 , mX( xx )
924 {}
925
926 std::shared_ptr<const Track> FindTrack() const override
927 { return nullptr; }
928
930 const PlayheadHandle &oldState, const PlayheadHandle &newState)
931 {
932 if (oldState.mX != newState.mX)
934 return 0;
935 }
936
937 static std::shared_ptr<PlayheadHandle>
939 const AudacityProject *pProject, AdornedRulerPanel &parent, wxCoord xx )
940 {
941 if( Scrubber::Get( *pProject )
942 .IsTransportingPinned() &&
943 ProjectAudioIO::Get( *pProject ).IsAudioActive() )
944 {
945 const auto targetX = GetPlayHeadX( pProject );
946 if ( abs( xx - targetX ) <= SELECT_TOLERANCE_PIXEL )
947 return std::make_shared<PlayheadHandle>( parent, xx );
948 }
949 return {};
950 }
951
952protected:
954 const TrackPanelMouseEvent &event, AudacityProject *) override
955 {
956 if (event.event.LeftDClick()) {
957 // Restore default position on double click
959
961 // Do not start a drag
963 }
964 // Fix for Bug 2357
965 if (!event.event.LeftIsDown())
967
969 return 0;
970 }
971
973 const TrackPanelMouseEvent &event, AudacityProject *pProject) override
974 {
975
976 auto value = GetPlayHeadFraction(pProject, event.event.m_x );
979 }
980
983 override
984 {
985 mpParent->SetNumGuides(1);
986 static wxCursor cursor{ wxCURSOR_SIZEWE };
987 return {
988 XO( "Click and drag to adjust, double-click to reset" ),
989 &cursor,
990 /* i18n-hint: This text is a tooltip on the icon (of a pin) representing
991 the temporal position in the audio. */
992 XO( "Record/Playhead" )
993 };
994 }
995
997 const TrackPanelMouseEvent &event, AudacityProject *pProject,
998 wxWindow *) override
999 {
1000 auto value = GetPlayHeadFraction(pProject, event.event.m_x );
1003 }
1004
1006 {
1009 }
1010
1011 void Enter(bool, AudacityProject *) override
1012 {
1013 mChangeHighlight = RefreshCode::DrawOverlays;
1014 }
1015
1017 wxCoord mX;
1018 double mOrigPreference {};
1019};
1020
1021}
1022
1024{
1025public:
1026 explicit
1028 : AdornedRulerPanel::CommonCell{ parent, MenuChoice::QuickPlay }
1029 {}
1030
1031 std::vector<UIHandlePtr> HitTest(
1032 const TrackPanelMouseState &state,
1033 const AudacityProject *pProject) override;
1034
1035 // Return shared_ptr to self, stored in parent
1036 std::shared_ptr<TrackPanelCell> ContextMenuDelegate() override
1037 { return mParent->mQPCell; }
1038
1039 bool Clicked() const {
1040#ifdef QUICK_PLAY_HANDLE
1041 if (auto ptr = mHolder.lock())
1042 return ptr->Clicked();
1043#endif
1044 return false;
1045 }
1046
1047#ifdef QUICK_PLAY_HANDLE
1048 std::weak_ptr<QPHandle> mHolder;
1049#endif
1050
1051 std::weak_ptr<ResizePlayRegionHandle> mResizePlayRegionHolder;
1052 std::weak_ptr<MovePlayRegionHandle> mMovePlayRegionHolder;
1053 std::weak_ptr<NewPlayRegionHandle> mNewPlayRegionHolder;
1054 std::weak_ptr<PlayheadHandle> mPlayheadHolder;
1055};
1056
1057std::vector<UIHandlePtr> AdornedRulerPanel::QPCell::HitTest(
1058 const TrackPanelMouseState &state,
1059 const AudacityProject *pProject)
1060{
1061 // Creation of overlays on demand here -- constructor of AdornedRulerPanel
1062 // is too early to do it
1063 mParent->CreateOverlays();
1064
1065 std::vector<UIHandlePtr> results;
1066 auto xx = state.state.m_x;
1067
1068 {
1069 // Allow click and drag on the play head even while recording
1070 // Make this handle more prominent then the quick play handle
1071 auto result = PlayheadHandle::HitTest( pProject, *mParent, xx );
1072 if (result) {
1073 result = AssignUIHandlePtr( mPlayheadHolder, result );
1074 results.push_back( result );
1075 }
1076 }
1077
1078 // Disable mouse actions on Timeline while recording.
1079 if (!mParent->mIsRecording) {
1080 mParent->UpdateQuickPlayPos( xx );
1081
1082 #if 0
1083 auto result = std::make_shared<QPHandle>( mParent, xx );
1084 result = AssignUIHandlePtr( mHolder, result );
1085 results.push_back( result );
1086 #endif
1087 }
1088
1089 // High priority hit is a handle to change the existing play region
1090 bool hitLeft = false;
1091 const auto &playRegion = ViewInfo::Get(*pProject).playRegion;
1092 if ((hitLeft =
1093 mParent->IsWithinMarker(xx, playRegion.GetLastActiveStart())) ||
1094 mParent->IsWithinMarker(xx, playRegion.GetLastActiveEnd()))
1095 {
1096 auto result =
1097 std::make_shared<ResizePlayRegionHandle>( mParent, xx, hitLeft );
1098 result = AssignUIHandlePtr( mResizePlayRegionHolder, result );
1099 results.push_back(result);
1100 }
1101
1102 // Middle priority hit is a handle to change the existing play region at
1103 // both ends, but only when the play region is active
1104 if (auto time = mParent->Pos2Time(xx);
1105 playRegion.Active() &&
1106 time >= playRegion.GetStart() &&
1107 time <= playRegion.GetEnd())
1108 {
1109 auto result =
1110 std::make_shared<MovePlayRegionHandle>( mParent, xx );
1111 result = AssignUIHandlePtr( mMovePlayRegionHolder, result );
1112 results.push_back(result);
1113 }
1114
1115 // Lowest priority hit is a handle to drag a completely new play region
1116 {
1117 auto result = std::make_shared<NewPlayRegionHandle>( mParent, xx );
1118 result = AssignUIHandlePtr( mNewPlayRegionHolder, result );
1119 results.push_back(result);
1120 }
1121
1122 return results;
1123}
1124
1126{
1127public:
1128 explicit
1129 ScrubbingHandle( AdornedRulerPanel *pParent, wxCoord xx )
1130 : CommonRulerHandle( pParent, xx, MenuChoice::Scrub )
1131 {
1132 }
1133
1134private:
1136 const TrackPanelMouseEvent &event, AudacityProject *pProject) override
1137 {
1138 auto result = CommonRulerHandle::Click(event, pProject);
1139 if (!( result & RefreshCode::Cancelled )) {
1140 if (mClicked == Button::Left) {
1141 auto &scrubber = Scrubber::Get( *pProject );
1142 // only if scrubbing is allowed now
1143 bool canScrub =
1144 scrubber.CanScrub() &&
1145 mParent &&
1146 mParent->ShowingScrubRuler();
1147
1148 if (!canScrub)
1150 if (!scrubber.HasMark()) {
1151 // Asynchronous scrub poller gets activated here
1152 scrubber.MarkScrubStart(
1153 event.event.m_x, Scrubber::ShouldScrubPinned(), false);
1154 }
1155 }
1156 }
1157 return result;
1158 }
1159
1161 const TrackPanelMouseEvent &event, AudacityProject *pProject) override
1162 {
1163 auto result = CommonRulerHandle::Drag(event, pProject);
1164 if (!( result & RefreshCode::Cancelled )) {
1165 // Nothing needed here. The scrubber works by polling mouse state
1166 // after the start has been marked.
1167 }
1168 return result;
1169 }
1170
1172 const TrackPanelMouseState &state, AudacityProject *pProject)
1173 override;
1174
1176 const TrackPanelMouseEvent &event, AudacityProject *pProject,
1177 wxWindow *pParent) override {
1178 auto result = CommonRulerHandle::Release(event, pProject, pParent);
1179 if (!( result & RefreshCode::Cancelled )) {
1180 // Nothing needed here either. The scrub poller may have decided to
1181 // seek because a drag happened before button up, or it may decide
1182 // to start a scrub, as it watches mouse movement after the button up.
1183 }
1184 return result;
1185 }
1186
1187 Result Cancel(AudacityProject *pProject) override
1188 {
1189 auto result = CommonRulerHandle::Cancel(pProject);
1190
1191 if (mClicked == Button::Left) {
1192 auto &scrubber = Scrubber::Get( *pProject );
1193 scrubber.Cancel();
1194
1195 ProjectAudioManager::Get( *pProject ).Stop();
1196 }
1197
1198 return result;
1199 }
1200};
1201
1203{
1204public:
1205 explicit
1207 : AdornedRulerPanel::CommonCell{ parent, MenuChoice::Scrub }
1208 {}
1209
1210 std::vector<UIHandlePtr> HitTest(
1211 const TrackPanelMouseState &state,
1212 const AudacityProject *pProject) override;
1213
1214 // Return shared_ptr to self, stored in parent
1215 std::shared_ptr<TrackPanelCell> ContextMenuDelegate() override
1216 { return mParent->mScrubbingCell; }
1217
1218 bool Hit() const { return !mHolder.expired(); }
1219 bool Clicked() const {
1220 if (auto ptr = mHolder.lock())
1221 return ptr->Clicked();
1222 return false;
1223 }
1224
1225private:
1226 std::weak_ptr<ScrubbingHandle> mHolder;
1227};
1228
1230 const TrackPanelMouseState &state, const AudacityProject *)
1231{
1232 // Creation of overlays on demand here -- constructor of AdornedRulerPanel
1233 // is too early to do it
1234 mParent->CreateOverlays();
1235
1236 std::vector<UIHandlePtr> results;
1237
1238 // Disable mouse actions on Timeline while recording.
1239 if (!mParent->mIsRecording) {
1240 auto xx = state.state.m_x;
1241 mParent->UpdateQuickPlayPos( xx );
1242 auto result = std::make_shared<ScrubbingHandle>( mParent, xx );
1243 result = AssignUIHandlePtr( mHolder, result );
1244 results.push_back( result );
1245 }
1246
1247 return results;
1248}
1249
1250namespace{
1251AttachedWindows::RegisteredFactory sKey{
1252[]( AudacityProject &project ) -> wxWeakRef< wxWindow > {
1253 auto &viewInfo = ViewInfo::Get( project );
1254 auto &window = ProjectWindow::Get( project );
1255
1256 return safenew AdornedRulerPanel( &project, window.GetTrackListWindow(),
1257 wxID_ANY,
1258 wxDefaultPosition,
1259 wxSize( -1, AdornedRulerPanel::GetRulerHeight(false) ),
1260 &viewInfo );
1261}
1262};
1263}
1264
1266{
1268}
1269
1271 const AudacityProject &project )
1272{
1273 return Get( const_cast< AudacityProject & >( project ) );
1274}
1275
1277{
1278 auto *pPanel = GetAttachedWindows(project).Find( sKey );
1279 if (pPanel) {
1280 pPanel->wxWindow::Destroy();
1282 }
1283}
1284
1286 wxWindow *parent,
1287 wxWindowID id,
1288 const wxPoint& pos,
1289 const wxSize& size,
1290 ViewInfo *viewinfo
1291) : CellularPanel(parent, id, pos, size, viewinfo)
1292 , mProject { project }
1293 , mUpdater { ProjectTimeRuler::Get(*project).GetUpdater() }
1294 , mRuler { ProjectTimeRuler::Get(*project).GetRuler() }
1295{
1296 SetLayoutDirection(wxLayout_LeftToRight);
1297
1298 mQPCell = std::make_shared<QPCell>( this );
1299 mScrubbingCell = std::make_shared<ScrubbingCell>( this );
1300
1301 for (auto &button : mButtons)
1302 button = nullptr;
1303
1304 SetLabel( XO("Timeline") );
1305 SetName();
1306 SetBackgroundStyle(wxBG_STYLE_PAINT);
1307
1308 mLeftOffset = 0;
1309 mIndTime = -1;
1310
1311 mLeftDownClick = -1;
1313 mIsDragging = false;
1314
1315 mOuter = GetClientRect();
1316
1318
1320
1321 mRuler.SetLabelEdges( false );
1322
1324
1325 mIsRecording = false;
1326
1327 mPlayRegionDragsSelection = (gPrefs->Read(wxT("/QuickPlay/DragSelection"), 0L) == 1)? true : false;
1328
1329#if wxUSE_TOOLTIPS
1330 wxToolTip::Enable(true);
1331#endif
1332
1335
1336 // Delay until after CommandManager has been populated:
1338
1341
1342 // Bind event that updates the play region
1345
1347 mRuler.Subscribe([this](auto) { Refresh(); });
1348
1349 // And call it once to initialize it
1351}
1352
1354{
1355}
1356
1357void AdornedRulerPanel::Refresh( bool eraseBackground, const wxRect *rect )
1358{
1359 CellularPanel::Refresh( eraseBackground, rect );
1361}
1362
1364{
1365 if (mNeedButtonUpdate) {
1366 // Visit this block once only in the lifetime of this panel
1367 mNeedButtonUpdate = false;
1368 // Do this first time setting of button status texts
1369 // when we are sure the CommandManager is initialized.
1371 }
1372
1373 // Update button texts for language change
1375
1377 Refresh();
1378 // Update();
1379}
1380
1382{
1383 // TODO: Should we do this to destroy the grabber??
1384 // Get rid of any children we may have
1385 // DestroyChildren();
1386
1388 SetBackgroundColour(GetBackgroundColour());
1389
1390 for (auto & button : mButtons) {
1391 if (button)
1392 button->Destroy();
1393 button = nullptr;
1394 }
1395
1396 size_t iButton = 0;
1397 // Make the short row of time ruler push buttons.
1398 // Don't bother with sizers. Their sizes and positions are fixed.
1399 // Add a grabber converted to a spacer.
1400 // This makes it visually clearer that the button is a button.
1401
1402 wxPoint position( 12, 0 );
1403
1404 position.x = 12;
1405
1406 auto size = theTheme.ImageSize( bmpRecoloredUpSmall );
1407 size.y = std::min(size.y, GetRulerHeight(false));
1408
1409 const auto button = ToolBar::MakeButton(
1410 this,
1411 bmpRecoloredUpSmall, bmpRecoloredDownSmall,
1412 bmpRecoloredUpHiliteSmall, bmpRecoloredHiliteSmall,
1413 bmpCogwheel, bmpCogwheel, bmpCogwheel,
1414 OnTogglePinnedStateID, position, true, size
1415 );
1416
1417 position.x += size.GetWidth();
1418 mButtons[iButton++] = button;
1419
1421}
1422
1424{
1426}
1427
1428namespace {
1430 {
1431#if 0
1432 if(scrubber.Seeks())
1433 /* i18n-hint: These commands assist the user in finding a sound by ear. ...
1434 "Scrubbing" is variable-speed playback, ...
1435 "Seeking" is normal speed playback but with skips
1436 */
1437 return XO("Click or drag to begin Seek");
1438 else
1439 /* i18n-hint: These commands assist the user in finding a sound by ear. ...
1440 "Scrubbing" is variable-speed playback, ...
1441 "Seeking" is normal speed playback but with skips
1442 */
1443 return XO("Click or drag to begin Scrub");
1444#else
1445 /* i18n-hint: These commands assist the user in finding a sound by ear. ...
1446 "Scrubbing" is variable-speed playback, ...
1447 "Seeking" is normal speed playback but with skips
1448 */
1449 return XO("Click & move to Scrub. Click & drag to Seek.");
1450#endif
1451 }
1452
1454 const Scrubber &scrubber, bool clicked)
1455 {
1456#if 0
1457 if(scrubber.Seeks())
1458 /* i18n-hint: These commands assist the user in finding a sound by ear. ...
1459 "Scrubbing" is variable-speed playback, ...
1460 "Seeking" is normal speed playback but with skips
1461 */
1462 return XO("Move to Seek");
1463 else
1464 /* i18n-hint: These commands assist the user in finding a sound by ear. ...
1465 "Scrubbing" is variable-speed playback, ...
1466 "Seeking" is normal speed playback but with skips
1467 */
1468 return XO("Move to Scrub");
1469#else
1470 if( clicked ) {
1471 // Since mouse is down, mention dragging first.
1472 // IsScrubbing is true if Scrubbing OR seeking.
1473 if( scrubber.IsScrubbing() )
1474 // User is dragging already, explain.
1475 return XO("Drag to Seek. Release to stop seeking.");
1476 else
1477 // User has clicked but not yet moved or released.
1478 return XO("Drag to Seek. Release and move to Scrub.");
1479 }
1480 // Since mouse is up, mention moving first.
1481 return XO("Move to Scrub. Drag to Seek.");
1482#endif
1483 }
1484
1485 const TranslatableString ScrubbingMessage(const Scrubber &scrubber, bool clicked)
1486 {
1487 if (scrubber.HasMark())
1488 return ContinueScrubbingMessage(scrubber, clicked);
1489 else
1490 return StartScrubbingMessage(scrubber);
1491 }
1492}
1493
1494void AdornedRulerPanel::OnIdle( wxIdleEvent &evt )
1495{
1496 evt.Skip();
1497 DoIdle();
1498}
1499
1501{
1502 bool changed = UpdateRects();
1503 changed = SetPanelSize() || changed;
1504
1505 auto &project = *mProject;
1506 auto &viewInfo = ViewInfo::Get( project );
1507 const bool isIconized = ProjectWindow::Get(project).IsIconized();
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.hpos
1516 || mLastDrawnZoom != viewInfo.GetZoom()
1517 || mLastPlayRegionActive != viewInfo.playRegion.Active()
1518 ;
1519 if (changed && !isIconized)
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{
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.hpos;
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 = mInner;
1576 wxRect rectR = {};
1577 if (!rectO.IsEmpty()) {
1578 rectR = { wxPoint{ rectO.GetRight() + 1, top }, rectL.GetBottomRight() };
1579 rectL = { rectL.GetTopLeft(), wxPoint{ rectO.GetLeft() - 1, bottom } };
1580 }
1581
1582 DoDrawPlayRegion(&backDC, rectP, rectL, rectR);
1583 DoDrawOverlap(&backDC, rectO);
1584 DoDrawSelection(&backDC, rectS, rectL, rectR);
1585
1586 DoDrawPlayRegionLimits(&backDC, rectP);
1587
1588 DoDrawMarks(&backDC, true);
1589
1590 DoDrawEdge(&backDC);
1591
1592 DisplayBitmap(dc);
1593
1594 // Stroke extras direct to the client area,
1595 // maybe outside of the damaged area
1596 // As with TrackPanel, do not make a NEW wxClientDC or else Mac flashes badly!
1597 dc.DestroyClippingRegion();
1598 DrawOverlays(true, &dc);
1599}
1600
1601void AdornedRulerPanel::OnSize(wxSizeEvent &evt)
1602{
1603 mOuter = GetClientRect();
1604 if (mOuter.GetWidth() == 0 || mOuter.GetHeight() == 0)
1605 {
1606 return;
1607 }
1608
1609 UpdateRects();
1610
1612}
1613
1614void AdornedRulerPanel::OnLeave(wxMouseEvent& evt)
1615{
1616 evt.Skip();
1617 CallAfter([this]{
1619 });
1620}
1621
1623{
1624 if (message.appearance)
1625 return;
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 + mLeftOffset;
1651 inner.width -= (LeftMargin + RightMargin + mLeftOffset);
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 ? 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) {
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 = ProjectAudioIO::GetDefaultOptions(*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 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 auto timelineOptionsButton = static_cast<AButton*>(FindWindow(OnTogglePinnedStateID));
2175 timelineOptionsButton->PopUp();
2176 // Bug 1584: Tooltip now shows what clicking will do.
2177 // Bug 2357: Action of button (and hence tooltip wording) updated.
2178 const auto label = XO("Timeline Options");
2179 common(*timelineOptionsButton, wxT("PinnedHead"), label);
2180 }
2181}
2182
2183void AdornedRulerPanel::OnPinnedButton(wxCommandEvent & /*event*/)
2184{
2186}
2187
2188void AdornedRulerPanel::OnTogglePinnedState(wxCommandEvent & /*event*/)
2189{
2192}
2193
2195{
2196 // Invoked for mouse-over preview events, or dragging, or scrub position
2197 // polling updates. Remember x coordinates, converted to times, for
2198 // drawing of guides.
2199
2200 // Keep Quick-Play within usable track area. (Dependent on zoom)
2201 const auto &viewInfo = ViewInfo::Get( *mProject );
2202 auto width = viewInfo.GetTracksUsableWidth();
2203 mousePosX = std::max(mousePosX, viewInfo.GetLeftOffset());
2204 mousePosX = std::min(mousePosX, viewInfo.GetLeftOffset() + width - 1);
2205 const auto time = Pos2Time(mousePosX);
2206
2207 for (size_t ii = 0; ii < mNumGuides; ++ii) {
2209 time + mQuickPlayOffset[ii];
2210 HandleSnapping(ii);
2211 }
2212}
2213
2214// Pop-up menus
2215
2216void AdornedRulerPanel::ShowMenu(const wxPoint & pos)
2217{
2218 const auto &viewInfo = ViewInfo::Get( *GetProject() );
2219 const auto &playRegion = viewInfo.playRegion;
2220 wxMenu rulerMenu;
2221
2222 {
2223 auto item = rulerMenu.AppendRadioItem(OnMinutesAndSecondsID,
2224 _("Minutes and Seconds"));
2226 }
2227
2228 {
2229 auto item = rulerMenu.AppendRadioItem(OnBeatsAndMeasuresID,
2230 _("Beats and Measures"));
2232 }
2233
2234 rulerMenu.AppendSeparator();
2235
2236 auto pDrag = rulerMenu.AppendCheckItem(OnSyncQuickPlaySelID, _("Setting a loop region also makes an audio selection"));
2237 pDrag->Check(mPlayRegionDragsSelection && playRegion.Active());
2238 pDrag->Enable(playRegion.Active());
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
2259 rulerMenu.AppendCheckItem(OnAutoScrollID, _("Scroll view to playhead"))->
2261
2262 rulerMenu.AppendCheckItem(OnTogglePinnedStateID, _("Continuous scrolling"))->
2264
2265 BasicMenu::Handle{ &rulerMenu }.Popup(
2267 { pos.x, pos.y }
2268 );
2269}
2270
2271void AdornedRulerPanel::ShowScrubMenu(const wxPoint & pos)
2272{
2273 auto &scrubber = Scrubber::Get( *mProject );
2274 PushEventHandler(&scrubber);
2275 auto cleanup = finally([this]{ PopEventHandler(); });
2276
2277 wxMenu rulerMenu;
2278 scrubber.PopulatePopupMenu(rulerMenu);
2279 BasicMenu::Handle{ &rulerMenu }.Popup(
2281 { pos.x, pos.y }
2282 );
2283}
2284
2286{
2287 auto &viewInfo = ViewInfo::Get( project );
2288 const auto &playRegion = viewInfo.playRegion;
2289 auto &selectedRegion = viewInfo.selectedRegion;
2290 selectedRegion.setT0(playRegion.GetStart(), false);
2291 selectedRegion.setT1(playRegion.GetEnd(), true);
2292}
2293
2295{
2296 // Play region dragging can snap to selection boundaries
2297 const auto &selectedRegion = ViewInfo::Get(*GetProject()).selectedRegion;
2298 SnapPointArray candidates;
2300 candidates = {
2301 SnapPoint{ selectedRegion.t0() },
2302 SnapPoint{ selectedRegion.t1() },
2303 };
2304 SnapManager snapManager{ *mProject, *mTracks, *mViewInfo, move(candidates) };
2305 auto results = snapManager.Snap(nullptr, mQuickPlayPos[index], false);
2306 mQuickPlayPos[index] = results.outTime;
2307 mIsSnapped[index] = results.Snapped();
2308}
2309
2311{
2312 int id = event.GetId();
2313 TimeDisplayMode changeFlag = mTimeDisplayMode;
2314 wxASSERT(id == OnMinutesAndSecondsID || id == OnBeatsAndMeasuresID);
2317
2319
2320 if (changeFlag != mTimeDisplayMode)
2321 Refresh();
2322}
2323
2325{
2327 gPrefs->Write(wxT("/QuickPlay/DragSelection"), mPlayRegionDragsSelection);
2328 gPrefs->Flush();
2329}
2330
2332{
2334 gPrefs->Write(wxT("/GUI/AutoScroll"), false);
2335 else
2336 gPrefs->Write(wxT("/GUI/AutoScroll"), true);
2337
2338 gPrefs->Flush();
2339
2341}
2342
2343
2345{
2347}
2348
2350{
2352}
2353
2355{
2357}
2358
2359
2361 MenuChoice choice, const wxPoint *pPosition)
2362{
2363 wxPoint position;
2364 if(pPosition)
2365 position = *pPosition;
2366 else
2367 {
2368 auto rect = GetRect();
2369 //Old code put menu too low down. y position applied twice.
2370 //position = { rect.GetLeft() + 1, rect.GetBottom() + 1 };
2371
2372 // The cell does not pass in the mouse or button position.
2373 // We happen to know this is the pin/unpin button
2374 // so these magic values 'fix a bug' - but really the cell should
2375 // pass more information to work with in.
2376 position = { rect.GetLeft() + 38, rect.GetHeight()/2 + 1 };
2377 }
2378
2379 switch (choice) {
2381 ShowMenu(position);
2383 break;
2384 case MenuChoice::Scrub:
2385 ShowScrubMenu(position); break;
2386 default:
2387 return;
2388 }
2389}
2390
2391using ColorId = decltype(clrTrackInfo);
2392
2394{
2395 return clrTimelineRulerBackground;
2396}
2397
2399{
2400 return clrTrackPanelText;
2401}
2402
2404{
2405 return TimelineTextColor();
2406}
2407
2409{
2410 return clrRulerSelected;
2411}
2412
2414{
2415 return isActive ? clrLoopEnabled : clrLoopDisabled;
2416}
2417
2418static inline wxColour AlphaBlend(ColorId fg, ColorId bg, double alpha)
2419{
2420 const auto &fgc = theTheme.Colour(fg);
2421 const auto &bgc = theTheme.Colour(bg);
2422 return wxColour{
2423 wxColour::AlphaBlend(fgc.Red(), bgc.Red(), alpha),
2424 wxColour::AlphaBlend(fgc.Green(), bgc.Green(), alpha),
2425 wxColour::AlphaBlend(fgc.Blue(), bgc.Blue(), alpha)
2426 };
2427}
2428
2430{
2431 // Draw AdornedRulerPanel border
2433 dc->DrawRectangle( mOuter );
2434
2435 if (ShowingScrubRuler()) {
2436 // Let's distinguish the scrubbing area by using a themable
2437 // colour and a line to set it off.
2438 AColor::UseThemeColour(dc, clrScrubRuler, TimelineTextColor() );
2439 wxRect ScrubRect = mScrubZone;
2440 ScrubRect.Inflate( 1,0 );
2441 dc->DrawRectangle(ScrubRect);
2442 }
2443}
2444
2446{
2447 // Black stroke at bottom
2448 dc->SetPen( *wxBLACK_PEN );
2449 AColor::Line( *dc, mOuter.x,
2450 mOuter.y + mOuter.height - 1,
2451 mOuter.x + mOuter.width - 1 ,
2452 mOuter.y + mOuter.height - 1 );
2453}
2454
2455void AdornedRulerPanel::DoDrawMarks(wxDC * dc, bool /*text */ )
2456{
2457 const double min = Pos2Time(mInner.x);
2458 const double hiddenMin = Pos2Time(mInner.x, true);
2459 const double max = Pos2Time(mInner.x + mInner.width);
2460 const double hiddenMax = Pos2Time(mInner.x + mInner.width, true);
2461
2463 mRuler.SetRange( min, max, hiddenMin, hiddenMax );
2465 {
2466 mRuler.SetTickLengths({ 5, 3, 1 });
2467 }
2469 {
2470 mRuler.SetTickLengths({ 4, 2, 2 });
2471 }
2472 mRuler.Draw( *dc );
2473}
2474
2476{
2477 Refresh();
2478}
2479
2481{
2482 const auto &viewInfo = ViewInfo::Get(*mProject);
2483 const auto &playRegion = viewInfo.playRegion;
2484 const auto t0 = playRegion.GetLastActiveStart(),
2485 t1 = playRegion.GetLastActiveEnd();
2486 return RegionRectangle(t0, t1);
2487}
2488
2490{
2491 const auto &viewInfo = ViewInfo::Get(*mProject);
2492 const auto &selectedRegion = viewInfo.selectedRegion;
2493 const auto t0 = selectedRegion.t0(), t1 = selectedRegion.t1();
2494 return RegionRectangle(t0, t1);
2495}
2496
2497wxRect AdornedRulerPanel::RegionRectangle(double t0, double t1) const
2498{
2499 int p0 = -PLAY_REGION_TRIANGLE_SIZE;
2500 int p1 = -PLAY_REGION_TRIANGLE_SIZE;
2501 if (t0 == t1)
2502 // Make the rectangle off-screen horizontally, but set the height
2503 ;
2504 else {
2505 p0 = max(mInner.x, Time2Pos(t0));
2506 p1 = min(mInner.x + mInner.width, Time2Pos(t1));
2507 }
2508
2509 const int left = p0, top = mInner.y, right = p1, bottom = mInner.GetBottom();
2510 return { wxPoint{left, top}, wxPoint{right, bottom} };
2511}
2512
2514 wxDC * dc, const wxRect &rectP, const wxRect &rectL, const wxRect &rectR)
2515{
2516 const auto &viewInfo = ViewInfo::Get(*mProject);
2517 const auto& playRegion = viewInfo.playRegion;
2518
2519 const bool isActive = (mLastPlayRegionActive = playRegion.Active());
2520
2521 if (playRegion.IsLastActiveRegionClear())
2522 return;
2523
2524 // Paint the selected region bolder if independently varying, else dim
2525 const auto color = TimelineLoopRegionColor(isActive);
2526 dc->SetBrush( wxBrush( theTheme.Colour( color )) );
2527 dc->SetPen( wxPen( theTheme.Colour( color )) );
2528
2529 dc->DrawRectangle( rectP.Intersect(rectL) );
2530 dc->DrawRectangle( rectP.Intersect(rectR) );
2531}
2532
2533void AdornedRulerPanel::DoDrawPlayRegionLimits(wxDC * dc, const wxRect &rect)
2534{
2535 // Color the edges of the play region like the ticks and numbers
2536 ADCChanger cleanup( dc );
2537 const auto edgeColour = theTheme.Colour(TimelineLimitsColor());
2538 dc->SetPen( { edgeColour } );
2539 dc->SetBrush( { edgeColour } );
2540
2541 constexpr int side = 7;
2542 constexpr int sideLessOne = side - 1;
2543
2544 // Paint two shapes, each a line plus triangle at bottom
2545 const auto left = rect.GetLeft(),
2546 right = rect.GetRight(),
2547 bottom = rect.GetBottom(),
2548 top = rect.GetTop();
2549 {
2550 wxPoint points[]{
2551 {left, bottom - sideLessOne},
2552 {left - sideLessOne, bottom},
2553 {left, bottom},
2554 {left, top},
2555 };
2556 dc->DrawPolygon( 4, points );
2557 }
2558
2559 {
2560 wxPoint points[]{
2561 {right, top},
2562 {right, bottom},
2563 {right + sideLessOne, bottom},
2564 {right, bottom - sideLessOne},
2565 };
2566 dc->DrawPolygon( 4, points );
2567 }
2568}
2569
2570constexpr double SelectionOpacity = 0.4;
2571
2572void AdornedRulerPanel::DoDrawOverlap(wxDC * dc, const wxRect &rect)
2573{
2574 dc->SetBrush( wxBrush{ AlphaBlend(
2576 SelectionOpacity) } );
2577 dc->SetPen( *wxTRANSPARENT_PEN );
2578 dc->DrawRectangle( rect );
2579}
2580
2582 wxDC * dc, const wxRect &rectS, const wxRect &rectL, const wxRect &rectR)
2583{
2584 dc->SetBrush( wxBrush{ AlphaBlend(
2586 dc->SetPen( *wxTRANSPARENT_PEN );
2587 dc->DrawRectangle( rectS.Intersect(rectL) );
2588 dc->DrawRectangle( rectS.Intersect(rectR) );
2589}
2590
2592{
2593 return ProperRulerHeight + (showScrubBar ? ScrubHeight : 0);
2594}
2595
2597{
2598 if (mLeftOffset != offset) {
2599 mLeftOffset = offset;
2600 mUpdater.SetData(mViewInfo, offset);
2602 }
2603}
2604
2605// Draws the scrubbing/seeking indicator.
2607 wxDC * dc, wxCoord xx, int width, bool scrub, bool seek)
2608{
2609 ADCChanger changer(dc); // Undo pen and brush changes at function exit
2610
2611 wxPoint tri[ 3 ];
2612 if (seek) {
2613 auto height = IndicatorHeightForWidth(width);
2614 // Make four triangles
2615 const int TriangleWidth = width * 3 / 8;
2616
2617 // Double-double headed, left-right
2618 auto yy = ShowingScrubRuler()
2619 ? mScrubZone.y
2620 : (mInner.GetBottom() + 1) - 1 /* bevel */ - height;
2621 tri[ 0 ].x = xx - IndicatorOffset;
2622 tri[ 0 ].y = yy;
2623 tri[ 1 ].x = xx - IndicatorOffset;
2624 tri[ 1 ].y = yy + height;
2625 tri[ 2 ].x = xx - TriangleWidth;
2626 tri[ 2 ].y = yy + height / 2;
2627 dc->DrawPolygon( 3, tri );
2628
2629 tri[ 0 ].x -= TriangleWidth;
2630 tri[ 1 ].x -= TriangleWidth;
2631 tri[ 2 ].x -= TriangleWidth;
2632 dc->DrawPolygon( 3, tri );
2633
2634 tri[ 0 ].x = tri[ 1 ].x = xx + IndicatorOffset;
2635 tri[ 2 ].x = xx + TriangleWidth;
2636 dc->DrawPolygon( 3, tri );
2637
2638
2639 tri[ 0 ].x += TriangleWidth;
2640 tri[ 1 ].x += TriangleWidth;
2641 tri[ 2 ].x += TriangleWidth;
2642 dc->DrawPolygon( 3, tri );
2643 }
2644 else if (scrub) {
2645 auto height = IndicatorHeightForWidth(width);
2646 const int IndicatorHalfWidth = width / 2;
2647
2648 // Double headed, left-right
2649 auto yy = ShowingScrubRuler()
2650 ? mScrubZone.y
2651 : (mInner.GetBottom() + 1) - 1 /* bevel */ - height;
2652 tri[ 0 ].x = xx - IndicatorOffset;
2653 tri[ 0 ].y = yy;
2654 tri[ 1 ].x = xx - IndicatorOffset;
2655 tri[ 1 ].y = yy + height;
2656 tri[ 2 ].x = xx - IndicatorHalfWidth;
2657 tri[ 2 ].y = yy + height / 2;
2658 dc->DrawPolygon( 3, tri );
2659 tri[ 0 ].x = tri[ 1 ].x = xx + IndicatorOffset;
2660 tri[ 2 ].x = xx + IndicatorHalfWidth;
2661 dc->DrawPolygon( 3, tri );
2662 }
2663}
2664
2666 double playRegionStart, double playRegionEnd)
2667{
2668 // This is called by AudacityProject to make the play region follow
2669 // the current selection. But while the user is selecting a play region
2670 // with the mouse directly in the ruler, changes from outside are blocked.
2672 return;
2673
2674 auto &viewInfo = ViewInfo::Get( *GetProject() );
2675 auto &playRegion = viewInfo.playRegion;
2676 playRegion.SetTimes( playRegionStart, playRegionEnd );
2677
2678 Refresh();
2679}
2680
2682{
2683 ProjectAudioManager::Get( *mProject ).Stop();
2684
2685 auto &viewInfo = ViewInfo::Get( *GetProject() );
2686 auto &playRegion = viewInfo.playRegion;
2687 playRegion.Clear();
2688
2689 Refresh();
2690}
2691
2692void AdornedRulerPanel::GetMaxSize(wxCoord *width, wxCoord *height)
2693{
2694 mRuler.GetMaxSize(width, height);
2695}
2696
2698
2700 s_AcceptsFocus = true;
2701 return TempAllowFocus{ &s_AcceptsFocus };
2702}
2703
2705{
2706 nn = std::min(nn, MAX_GUIDES);
2707 // If increasing the number of guides, reinitialize newer ones
2708 for (size_t ii = mNumGuides; ii < nn; ++ii) {
2709 mQuickPlayOffset[ii] = 0;
2710 mQuickPlayPosUnsnapped[ii] = 0;
2711 mQuickPlayPos[ii] = 0;
2712 mIsSnapped[ii] = false;
2713 }
2714 mNumGuides = nn;
2715}
2716
2718{
2719 auto temp = TemporarilyAllowFocus();
2720 SetFocus();
2721}
2722
2723// Second-level subdivision includes quick-play region and maybe the scrub bar
2724// and also shaves little margins above and below
2726 explicit Subgroup( const AdornedRulerPanel &ruler ) : mRuler{ ruler } {}
2727 Subdivision Children( const wxRect & ) override
2728 {
2729 return { Axis::Y, ( mRuler.ShowingScrubRuler() )
2730 ? Refinement{
2731 { mRuler.mInner.GetTop(), mRuler.mQPCell },
2732 { mRuler.mScrubZone.GetTop(), mRuler.mScrubbingCell },
2733 { mRuler.mScrubZone.GetBottom() + 1, nullptr }
2734 }
2735 : Refinement{
2736 { mRuler.mInner.GetTop(), mRuler.mQPCell },
2737 { mRuler.mInner.GetBottom() + 1, nullptr }
2738 }
2739 };
2740 }
2742};
2743
2744// Top-level subdivision shaves little margins off left and right
2746 explicit MainGroup( const AdornedRulerPanel &ruler ) : mRuler{ ruler } {}
2747 Subdivision Children( const wxRect & ) override
2748 { return { Axis::X, Refinement{
2749 // Subgroup is a throwaway object
2750 { mRuler.mInner.GetLeft(), std::make_shared< Subgroup >( mRuler ) },
2751 { mRuler.mInner.GetRight() + 1, nullptr }
2752 } }; }
2754};
2755
2757{
2758 auto &scrubber = Scrubber::Get( *GetProject() );
2759 return scrubber.ShowsBar();
2760}
2761
2762// CellularPanel implementation
2763std::shared_ptr<TrackPanelNode> AdornedRulerPanel::Root()
2764{
2765 // Root is a throwaway object
2766 return std::make_shared< MainGroup >( *this );
2767}
2768
2770{
2771 return mProject;
2772}
2773
2774std::shared_ptr<TrackPanelCell> AdornedRulerPanel::GetFocusedCell()
2775{
2776 // No switching of focus yet to the other, scrub zone
2777 return mQPCell;
2778}
2779
2781{
2782}
2783
2785 TrackPanelCell *, TrackPanelCell *, unsigned refreshResult)
2786{
2787 if (refreshResult & RefreshCode::RefreshAll)
2788 Refresh(); // Overlays will be repainted too
2789 else if (refreshResult & RefreshCode::DrawOverlays)
2790 DrawBothOverlays(); // cheaper redrawing of guidelines only
2791}
2792
2794{
2795 ProjectStatus::Get( *GetProject() ).Set(message);
2796}
2797
2799{
2800 if (!mOverlay) {
2801 mOverlay =
2802 std::make_shared<TrackPanelGuidelineOverlay>( mProject );
2803 auto pCellularPanel =
2804 dynamic_cast<CellularPanel*>( &GetProjectPanel( *GetProject() ) );
2805 if ( !pCellularPanel ) {
2806 wxASSERT( false );
2807 }
2808 else
2809 pCellularPanel->AddOverlay( mOverlay );
2810 this->AddOverlay( mOverlay->mPartner );
2811 }
2812}
2813
2815{
2819
2820 auto &project = *mProject;
2821 // Update button image
2823
2824 auto &scrubber = Scrubber::Get( project );
2825 if (scrubber.HasMark())
2826 scrubber.SetScrollScrubbing(value);
2827}
2828
2830{
2831 return mTimeDisplayMode;
2832}
2833
2835{
2836 if (mTimeDisplayMode == type)
2837 return;
2838
2839 mTimeDisplayMode = type;
2841 Refresh();
2842}
2843
2844// Attach menu item
2845
2846#include "CommandContext.h"
2847#include "CommonCommandFlags.h"
2848
2849namespace {
2851{
2853}
2854
2855using namespace MenuRegistry;
2857 Command( wxT("PinnedHead"), XXO("Continuous scrolling"),
2859 // Switching of scrolling on and off is permitted
2860 // even during transport
2862 Options{}.CheckTest([](const AudacityProject&){
2864 { wxT("Transport/Other/Options/Part2"), { OrderingHint::Begin, {} } }
2865};
2866}
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)
#define PLAY_REGION_TRIANGLE_SIZE
int IndicatorBigHeight()
ColorId TimelineSelectionColor()
static std::unique_ptr< wxCursor > handOpenCursor
ColorId TimelineBackgroundColor()
int IndicatorBigWidth()
@ OnTogglePinnedStateID
@ OnBeatsAndMeasuresID
@ OnSetPlayRegionToSelectionID
@ OnClearPlayRegionID
@ OnAutoScrollID
@ OnTogglePlayRegionID
@ OnMinutesAndSecondsID
@ OnSyncQuickPlaySelID
int IndicatorWidthForHeight(int height)
ColorId TimelineTextColor()
wxEVT_COMMAND_BUTTON_CLICKED
int IndicatorHeightForWidth(int width)
@ ProperRulerHeight
@ ScrubHeight
#define SELECT_TOLERANCE_PIXEL
constexpr double SelectionOpacity
wxT("CloseDown"))
Abstractions of menus and their items.
END_EVENT_TABLE()
constexpr CommandFlag AlwaysEnabledFlag
Definition: CommandFlag.h:34
int min(int a, int b)
const wxChar * values
XO("Cut/Copy/Paste")
XXO("&Cut/Copy/Paste Toolbar")
#define _(s)
Definition: Internat.h:73
EVT_COMMAND(wxID_ANY, EVT_FREQUENCYTEXTCTRL_UPDATED, LabelDialog::OnFreqUpdate) LabelDialog
Definition: LabelDialog.cpp:89
#define safenew
Definition: MemoryX.h:10
audacity::BasicSettings * gPrefs
Definition: Prefs.cpp:68
@ 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:165
const auto project
THEME_API Theme theTheme
Definition: Theme.cpp:82
TimeDisplayModeSetting TimeDisplayModePreference
TimeDisplayMode
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:189
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:164
const TranslatableString LoopToggleText
Definition: ViewInfo.cpp:227
int id
A wxButton with mouse-over behaviour.
Definition: AButton.h:104
void UpdateStatus()
Definition: AButton.cpp:542
void PopUp()
Definition: AButton.cpp:672
void SetLabel(const TranslatableString &label)
Definition: AButton.cpp:205
static void IndicatorColor(wxDC *dc, bool bIsNotRecording)
Definition: AColor.cpp:446
static void Line(wxDC &dc, wxCoord x1, wxCoord y1, wxCoord x2, wxCoord y2)
Definition: AColor.cpp:194
static void SnapGuidePen(wxDC *dc)
Definition: AColor.cpp:462
static void Light(wxDC *dc, bool selected, bool highlight=false)
Definition: AColor.cpp:395
static void UseThemeColour(wxDC *dc, int iBrush, int iPen=-1, int alpha=255)
Definition: AColor.cpp:354
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
std::shared_ptr< const Track > FindTrack() const 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
PlayRegionAdjustingHandle(AdornedRulerPanel *pParent, wxCoord xx, MenuChoice menuChoice, wxCursor *cursor, size_t numGuides=1)
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
wxRect PlayRegionRectangle() const
void OnPaint(wxPaintEvent &evt)
double mQuickPlayOffset[MAX_GUIDES]
void SetTimeDisplayMode(TimeDisplayMode rulerType)
void OnAudioStartStop(AudioIOEvent)
std::shared_ptr< ScrubbingCell > mScrubbingCell
Observer::Subscription mAudioIOSubscription
LinearUpdater & mUpdater
void OnTogglePinnedState(wxCommandEvent &event)
void DoSelectionChange(const SelectedRegion &selectedRegion)
void SetPlayRegion(double playRegionStart, double playRegionEnd)
void ShowContextMenu(MenuChoice choice, const wxPoint *pPosition)
wxRect RegionRectangle(double t0, double t1) const
void SetFocusFromKbd() override
void OnSetPlayRegionToSelection(wxCommandEvent &evt)
void DoDrawOverlap(wxDC *dc, const wxRect &rect)
void OnSize(wxSizeEvent &evt)
void OnTimelineFormatChange(wxCommandEvent &evt)
wxWindow * mButtons[3]
AudacityProject *const mProject
void DoDrawScrubIndicator(wxDC *dc, wxCoord xx, int width, bool scrub, bool seek)
void GetMaxSize(wxCoord *width, wxCoord *height)
void OnPinnedButton(wxCommandEvent &event)
void UpdateStatusMessage(const TranslatableString &) override
void OnClearPlayRegion(wxCommandEvent &evt)
wxRect SelectedRegionRectangle() const
void OnIdle(wxIdleEvent &evt)
void ShowScrubMenu(const wxPoint &pos)
bool IsWithinMarker(int mousePosX, double markerTime)
void DoDrawMarks(wxDC *dc, bool)
std::shared_ptr< TrackPanelGuidelineOverlay > mOverlay
void OnTogglePlayRegion(wxCommandEvent &evt)
static TempAllowFocus TemporarilyAllowFocus()
std::unique_ptr< bool, Resetter > TempAllowFocus
std::shared_ptr< TrackPanelCell > GetFocusedCell() override
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
Observer::Subscription mRulerInvalidatedSubscription
TimeDisplayMode mTimeDisplayMode
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]
TimeDisplayMode GetTimeDisplayMode() const
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:90
static AudioIOBase * Get()
Definition: AudioIOBase.cpp:94
static AudioIO * Get()
Definition: AudioIO.cpp:126
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:342
Subclass & Get(const RegisteredFactory &key)
Get reference to an attachment, creating on demand if not present, down-cast it to Subclass.
Definition: ClientData.h:318
void Assign(const RegisteredFactory &key, ReplacementPointer &&replacement)
Reassign Site's pointer to ClientData.
Definition: ClientData.h:364
CommandContext provides additional information to an 'Apply()' command. It provides the project,...
AudacityProject & project
ComponentInterfaceSymbol pairs a persistent string identifier used internally with an optional,...
Enum ReadEnum() const
Definition: Prefs.h:534
void SetData(const ZoomInfo *pZoomInfo=nullptr, int leftOffset=0)
Definition: LinearUpdater.h:33
Subscription Subscribe(Callback callback)
Connect a callback to the Publisher; later-connected are called earlier.
Definition: Observer.h:199
void DrawOverlays(bool repaint_all, wxDC *pDC=nullptr)
void AddOverlay(const std::weak_ptr< Overlay > &pOverlay)
double GetStart() const
Definition: ViewInfo.h:129
void SetStart(double start)
Definition: ViewInfo.cpp:161
double GetEnd() const
Definition: ViewInfo.h:136
bool Empty() const
Definition: ViewInfo.h:128
void SetEnd(double end)
Definition: ViewInfo.cpp:171
bool Active() const
Definition: ViewInfo.h:125
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:128
bool IsAudioActive() const
static AudioIOStartStreamOptions GetDefaultOptions(AudacityProject &project, bool newDefaults=false)
Invoke the global hook, supplying a default argument.
static ProjectAudioIO & Get(AudacityProject &project)
void Stop(bool stopStream=true)
static ProjectAudioManager & Get(AudacityProject &project)
static ProjectStatus & Get(AudacityProject &project)
void Set(const TranslatableString &msg, StatusBarField field=MainStatusBarField())
static ProjectWindow & Get(AudacityProject &project)
bool IsIconized() const override
Generates classes whose instances register items at construction.
Definition: Registry.h:388
void SetTickColour(const wxColour &colour)
Definition: Ruler.h:135
void Draw(wxDC &dc) const
Definition: Ruler.cpp:441
void SetTickLengths(const TickLengths &tLengths)
Definition: Ruler.cpp:255
void SetLabelEdges(bool labelEdges)
Definition: Ruler.cpp:179
void GetMaxSize(wxCoord *width, wxCoord *height)
Definition: Ruler.cpp:612
void SetBounds(int left, int top, int right, int bottom)
Definition: Ruler.cpp:304
void SetRange(double min, double max)
Definition: Ruler.cpp:152
void Invalidate()
Definition: Ruler.cpp:317
bool Seeks() const
Definition: Scrubbing.cpp:800
static bool ShouldScrubPinned()
Definition: Scrubbing.cpp:149
bool IsScrubbing() const
Definition: Scrubbing.cpp:768
static Scrubber & Get(AudacityProject &project)
Definition: Scrubbing.cpp:186
bool HasMark() const
Definition: Scrubbing.h:89
Defines a selected portion of a project.
double t1() const
double t0() const
Definition: Snap.h:31
wxColour & Colour(int iIndex)
wxSize ImageSize(int iIndex)
bool WriteEnum(TimeDisplayMode value)
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:839
static void MakeButtonBackgroundsSmall()
Definition: ToolBar.cpp:819
static void SetButtonToolTip(AudacityProject &project, AButton &button, const ComponentInterfaceSymbol commands[], size_t nCommands)
Definition: ToolBar.cpp:934
static void ModifyAllProjectToolbarMenus()
double GetEndTime() const
Return the greatest end time of the tracks, or 0 when no tracks.
Definition: Track.cpp:784
double GetStartTime() const
Return the least start time of the tracks, or 0 when no tracks.
Definition: Track.cpp:778
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:314
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:37
Result mChangeHighlight
Definition: UIHandle.h:152
unsigned Result
Definition: UIHandle.h:40
virtual HitTestPreview Preview(const TrackPanelMouseState &state, AudacityProject *pProject)=0
bool bUpdateTrackIndicator
Definition: ViewInfo.h:227
PlayRegion playRegion
Definition: ViewInfo.h:217
NotifyingSelectedRegion selectedRegion
Definition: ViewInfo.h:216
static int UpdateScrollPrefsID()
Definition: ViewInfo.cpp:304
static ViewInfo & Get(AudacityProject &project)
Definition: ViewInfo.cpp:235
double PositionToTime(int64 position, int64 origin=0, bool ignoreFisheye=false) const
Definition: ZoomInfo.cpp:34
int64 TimeToPosition(double time, int64 origin=0, bool ignoreFisheye=false) const
STM: Converts a project time to screen x position.
Definition: ZoomInfo.cpp:44
std::shared_ptr< const Track > FindTrack() const override
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
virtual bool Flush() noexcept=0
virtual bool Write(const wxString &key, bool value)=0
virtual bool Read(const wxString &key, bool *value) const =0
void SetLabel(const TranslatableString &label)
void SetFocus(const WindowPlacement &focus)
Set the window that accepts keyboard input.
Definition: BasicUI.h:392
void CallAfter(Action action)
Schedule an action to be done later, and in the main thread.
Definition: BasicUI.cpp:214
bool Begin(const FilePath &dataDir)
Definition: Journal.cpp:226
void Release(wxWindow *handler)
bool HitTest(const RectangleArgs &args, const wxPoint &mousePos)
constexpr auto Command
Definition: MenuRegistry.h:456
Namespace containing an enum 'what to do on a refresh?'.
Definition: RefreshCode.h:16
void SetPlayRegionToSelection(AudacityProject &project)
void ActivatePlayRegion(AudacityProject &project)
void InactivatePlayRegion(AudacityProject &project)
void ClearPlayRegion(AudacityProject &project)
void TogglePlayRegion(AudacityProject &project)
double GetPlayHeadFraction(const AudacityProject *pProject, wxCoord xx)
void OnTogglePinnedHead(const CommandContext &context)
const TranslatableString ScrubbingMessage(const Scrubber &scrubber, bool clicked)
const TranslatableString StartScrubbingMessage(const Scrubber &)
wxCoord GetPlayHeadX(const AudacityProject *pProject)
const TranslatableString ContinueScrubbingMessage(const Scrubber &scrubber, bool clicked)
AttachedWindows::RegisteredFactory sKey
const char * end(const char *str) noexcept
Definition: StringUtils.h:106
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:68
enum AudioIOEvent::Type type
Options && CheckTest(const CheckFn &fn) &&
Definition: MenuRegistry.h:74
Default message type for Publisher.
Definition: Observer.h:26
std::optional< PreferredSystemAppearance > appearance
Definition: Theme.h:111
Window placement information for wxWidgetsBasicUI can be constructed from a wxWindow pointer.