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)
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 auto &selectedRegion = viewInfo.selectedRegion;
1508 const auto &playRegion = viewInfo.playRegion;
1509
1510 changed = changed
1511 || mLastDrawnSelectedRegion != selectedRegion
1512 || mLastDrawnPlayRegion != std::pair{
1513 playRegion.GetLastActiveStart(), playRegion.GetLastActiveEnd() }
1514 || mLastDrawnH != viewInfo.hpos
1515 || mLastDrawnZoom != viewInfo.GetZoom()
1516 || mLastPlayRegionActive != viewInfo.playRegion.Active()
1517 ;
1518 if (changed)
1519 // Cause ruler redraw anyway, because we may be zooming or scrolling,
1520 // showing or hiding the scrub bar, etc.
1521 Refresh();
1522}
1523
1525{
1527 return;
1528 if ( evt.type == AudioIOEvent::CAPTURE ) {
1529 if (evt.on)
1530 {
1531 mIsRecording = true;
1532 this->CellularPanel::CancelDragging( false );
1534
1536 }
1537 else {
1538 mIsRecording = false;
1540 }
1541 }
1542
1543 if ( !evt.on )
1544 // So that the play region is updated
1546}
1547
1548void AdornedRulerPanel::OnPaint(wxPaintEvent & WXUNUSED(evt))
1549{
1550 const auto &viewInfo = ViewInfo::Get( *GetProject() );
1551 const auto &playRegion = viewInfo.playRegion;
1552 const auto playRegionBounds = std::pair{
1553 playRegion.GetLastActiveStart(), playRegion.GetLastActiveEnd() };
1554 mLastDrawnH = viewInfo.hpos;
1555 mLastDrawnZoom = viewInfo.GetZoom();
1556 mLastDrawnPlayRegion = playRegionBounds;
1557 mLastDrawnSelectedRegion = viewInfo.selectedRegion;
1558 // To do, note other fisheye state when we have that
1559
1560 wxPaintDC dc(this);
1561
1562 auto &backDC = GetBackingDCForRepaint();
1563
1564 DoDrawBackground(&backDC);
1565
1566 // Find play region rectangle, selected rectangle, and their overlap
1567 const auto rectP = PlayRegionRectangle(),
1568 rectS = SelectedRegionRectangle(),
1569 rectO = rectP.Intersect(rectS);
1570
1571 // What's left and right of the overlap? Assume same tops and bottoms
1572 const auto top = rectP.GetTop(),
1573 bottom = rectP.GetBottom();
1574 wxRect rectL = mInner;
1575 wxRect rectR = {};
1576 if (!rectO.IsEmpty()) {
1577 rectR = { wxPoint{ rectO.GetRight() + 1, top }, rectL.GetBottomRight() };
1578 rectL = { rectL.GetTopLeft(), wxPoint{ rectO.GetLeft() - 1, bottom } };
1579 }
1580
1581 DoDrawPlayRegion(&backDC, rectP, rectL, rectR);
1582 DoDrawOverlap(&backDC, rectO);
1583 DoDrawSelection(&backDC, rectS, rectL, rectR);
1584
1585 DoDrawPlayRegionLimits(&backDC, rectP);
1586
1587 DoDrawMarks(&backDC, true);
1588
1589 DoDrawEdge(&backDC);
1590
1591 DisplayBitmap(dc);
1592
1593 // Stroke extras direct to the client area,
1594 // maybe outside of the damaged area
1595 // As with TrackPanel, do not make a NEW wxClientDC or else Mac flashes badly!
1596 dc.DestroyClippingRegion();
1597 DrawOverlays(true, &dc);
1598}
1599
1600void AdornedRulerPanel::OnSize(wxSizeEvent &evt)
1601{
1602 mOuter = GetClientRect();
1603 if (mOuter.GetWidth() == 0 || mOuter.GetHeight() == 0)
1604 {
1605 return;
1606 }
1607
1608 UpdateRects();
1609
1611}
1612
1613void AdornedRulerPanel::OnLeave(wxMouseEvent& evt)
1614{
1615 evt.Skip();
1616 CallAfter([this]{
1618 });
1619}
1620
1622{
1623 if (message.appearance)
1624 return;
1626}
1627
1629{
1630 auto &selectedRegion = mViewInfo->selectedRegion;
1631 DoSelectionChange( selectedRegion );
1632}
1633
1635 const SelectedRegion &selectedRegion )
1636{
1637
1638 auto gAudioIO = AudioIOBase::Get();
1639 if ( !ViewInfo::Get( *mProject ).playRegion.Active() ) {
1640 // "Inactivated" play region follows the selection.
1641 SetPlayRegion( selectedRegion.t0(), selectedRegion.t1() );
1642 }
1643}
1644
1646{
1647 auto inner = mOuter;
1648 wxRect scrubZone;
1649 inner.x += LeftMargin + mLeftOffset;
1650 inner.width -= (LeftMargin + RightMargin + mLeftOffset);
1651
1652 auto top = &inner;
1653 auto bottom = &inner;
1654
1655 if (ShowingScrubRuler()) {
1656 scrubZone = inner;
1657 auto scrubHeight = std::min(scrubZone.height, (int)(ScrubHeight));
1658
1659 int topHeight;
1660#ifdef SCRUB_ABOVE
1661 top = &scrubZone, topHeight = scrubHeight;
1662#else
1663 auto qpHeight = scrubZone.height - scrubHeight;
1664 bottom = &scrubZone, topHeight = qpHeight;
1665 // Increase scrub zone height so that hit testing finds it and
1666 // not QP region, when on bottom 'edge'.
1667 scrubZone.height+=BottomMargin;
1668#endif
1669
1670 top->height = topHeight;
1671 bottom->height -= topHeight;
1672 bottom->y += topHeight;
1673 }
1674
1675 top->y += TopMargin;
1676 top->height -= TopMargin;
1677
1678 bottom->height -= BottomMargin;
1679
1680 if (!ShowingScrubRuler())
1681 scrubZone = inner;
1682
1683 if ( inner == mInner && scrubZone == mScrubZone )
1684 // no changes
1685 return false;
1686
1687 mInner = inner;
1688 mScrubZone = scrubZone;
1689
1690 mRuler.SetBounds(mInner.GetLeft(),
1691 mInner.GetTop(),
1692 mInner.GetRight(),
1693 mInner.GetBottom());
1694
1695 return true;
1696}
1697
1698double AdornedRulerPanel::Pos2Time(int p, bool ignoreFisheye) const
1699{
1701 , ignoreFisheye
1702 );
1703}
1704
1705int AdornedRulerPanel::Time2Pos(double t, bool ignoreFisheye) const
1706{
1708 , ignoreFisheye
1709 );
1710}
1711
1712bool AdornedRulerPanel::IsWithinMarker(int mousePosX, double markerTime)
1713{
1714 if (markerTime < 0)
1715 return false;
1716
1717 int pixelPos = Time2Pos(markerTime);
1718 int boundLeft = pixelPos - SELECT_TOLERANCE_PIXEL;
1719 int boundRight = pixelPos + SELECT_TOLERANCE_PIXEL;
1720
1721 return mousePosX >= boundLeft && mousePosX < boundRight;
1722}
1723
1724#ifdef QUICK_PLAY_HANDLE
1725auto AdornedRulerPanel::QPHandle::Click(
1726 const TrackPanelMouseEvent &event, AudacityProject *pProject) -> Result
1727{
1728 auto result = CommonRulerHandle::Click(event, pProject);
1729 if (!( result & RefreshCode::Cancelled )) {
1730 if (mClicked == Button::Left) {
1731 if (!mParent)
1733
1734 auto &scrubber = Scrubber::Get( *pProject );
1735 if(scrubber.HasMark()) {
1736 // We can't stop scrubbing yet (see comments in Bug 1391),
1737 // but we can pause it.
1738 ProjectAudioManager::Get( *pProject ).OnPause();
1739 }
1740
1741 // Store the initial play region state
1742 const auto &viewInfo = ViewInfo::Get( *pProject );
1743 const auto &playRegion = viewInfo.playRegion;
1744 mParent->mOldPlayRegion = playRegion;
1745
1746 // Save old selection, in case drag of selection is cancelled
1747 mOldSelection = ViewInfo::Get( *pProject ).selectedRegion;
1748
1749 mParent->HandleQPClick( event.event, mX );
1750 mParent->HandleQPDrag( event.event, mX );
1751 }
1752 }
1753
1754 return result;
1755}
1756
1757void AdornedRulerPanel::HandleQPClick(wxMouseEvent &evt, wxCoord mousePosX)
1758{
1759 // Temporarily inactivate play region
1760 if (mOldPlayRegion.Active() && evt.LeftDown()) {
1761 //mPlayRegionLock = true;
1763 }
1764
1767 bool isWithinStart = IsWithinMarker(mousePosX, mOldPlayRegion.GetStart());
1768 bool isWithinEnd = IsWithinMarker(mousePosX, mOldPlayRegion.GetEnd());
1769
1770 if (isWithinStart || isWithinEnd) {
1771 // If Quick-Play is playing from a point, we need to treat it as a click
1772 // not as dragging.
1773 if (mOldPlayRegion.Empty())
1775 // otherwise check which marker is nearer
1776 else {
1777 // Don't compare times, compare positions.
1778 //if (fabs(mQuickPlayPos[0] - mPlayRegionStart) < fabs(mQuickPlayPos[0] - mPlayRegionEnd))
1779 auto start = mOldPlayRegion.GetStart();
1780 auto end = mOldPlayRegion.GetEnd();
1781 if (abs(Time2Pos(mQuickPlayPos[0]) - Time2Pos(start)) <
1782 abs(Time2Pos(mQuickPlayPos[0]) - Time2Pos(end)))
1784 else
1786 }
1787 }
1788 else {
1789 // Clicked but not yet dragging
1791 }
1792}
1793
1794auto AdornedRulerPanel::QPHandle::Drag(
1795 const TrackPanelMouseEvent &event, AudacityProject *pProject) -> Result
1796{
1797 auto result = CommonRulerHandle::Drag(event, pProject);
1798 if (!( result & RefreshCode::Cancelled )) {
1799 if (mClicked == Button::Left) {
1800 if ( mParent ) {
1801 mX = event.event.m_x;
1802 mParent->UpdateQuickPlayPos( mX );
1803 mParent->HandleQPDrag( event.event, mX );
1804 }
1805 }
1806 }
1807 return result;
1808}
1809
1810void AdornedRulerPanel::HandleQPDrag(wxMouseEvent &/*event*/, wxCoord mousePosX)
1811{
1812 bool isWithinClick =
1813 (mLeftDownClickUnsnapped >= 0) &&
1815 bool isWithinStart = IsWithinMarker(mousePosX, mOldPlayRegion.GetStart());
1816 bool isWithinEnd = IsWithinMarker(mousePosX, mOldPlayRegion.GetEnd());
1817 bool canDragSel = !mOldPlayRegion.Active() && mPlayRegionDragsSelection;
1818 auto &viewInfo = ViewInfo::Get( *GetProject() );
1819 auto &playRegion = viewInfo.playRegion;
1820
1821 switch (mMouseEventState)
1822 {
1823 case mesNone:
1824 // If close to either end of play region, snap to closest
1825 if (isWithinStart || isWithinEnd) {
1828 else
1830 }
1831 break;
1833 // Don't start dragging until beyond tolerance initial playback start
1834 if (!mIsDragging && isWithinStart)
1836 else
1837 mIsDragging = true;
1838 // avoid accidental tiny selection
1839 if (isWithinEnd)
1841 playRegion.SetStart( mQuickPlayPos[0] );
1842 if (canDragSel) {
1844 }
1845 break;
1847 if (!mIsDragging && isWithinEnd) {
1849 }
1850 else
1851 mIsDragging = true;
1852 if (isWithinStart) {
1854 }
1855 playRegion.SetEnd( mQuickPlayPos[0] );
1856 if (canDragSel) {
1858 }
1859 break;
1861
1862 // Don't start dragging until mouse is beyond tolerance of initial click.
1863 if (isWithinClick || mLeftDownClick == -1) {
1865 playRegion.SetTimes(mLeftDownClick, mLeftDownClick);
1866 }
1867 else {
1869 }
1870 break;
1872 if (isWithinClick) {
1874 }
1875
1877 playRegion.SetTimes( mQuickPlayPos[0], mLeftDownClick );
1878 else
1879 playRegion.SetTimes( mLeftDownClick, mQuickPlayPos[0] );
1880 if (canDragSel) {
1882 }
1883 break;
1884 }
1885 Refresh();
1886 Update();
1887}
1888#endif
1889
1891 const TrackPanelMouseState &, AudacityProject *pProject)
1893{
1894 auto &scrubber = Scrubber::Get( *pProject );
1895 auto message = ScrubbingMessage(scrubber, mClicked == Button::Left);
1896
1897 mParent->SetNumGuides(1);
1898 return {
1899 message,
1900 {},
1901 // Tooltip is same as status message, or blank
1902 mParent ? message : TranslatableString{},
1903 };
1904}
1905
1906#ifdef QUICK_PLAY_HANDLE
1907auto AdornedRulerPanel::QPHandle::Preview(
1908 const TrackPanelMouseState &state, AudacityProject *pProject)
1910{
1911 mParent->SetNumGuides(1);
1912 TranslatableString tooltip;
1913 #if 0
1914 if (mParent) {
1915 if (!mParent->mQuickPlayEnabled)
1916 tooltip = XO("Quick-Play disabled");
1917 else
1918 tooltip = XO("Quick-Play enabled");
1919 }
1920 #endif
1921
1922 TranslatableString message;
1923 auto &scrubber = Scrubber::Get( *pProject );
1924 const bool scrubbing = scrubber.HasMark();
1925 if (scrubbing)
1926 // Don't distinguish zones
1927 message = ScrubbingMessage(scrubber, false);
1928 else
1929 // message = Insert timeline status bar message here
1930 ;
1931
1932 static wxCursor cursorHand{ wxCURSOR_HAND };
1933 static wxCursor cursorSizeWE{ wxCURSOR_SIZEWE };
1934
1935 bool showArrows = false;
1936 if (mParent)
1937 showArrows =
1938 (mClicked == Button::Left)
1939 || mParent->IsWithinMarker(
1940 state.state.m_x, mParent->mOldPlayRegion.GetStart())
1941 || mParent->IsWithinMarker(
1942 state.state.m_x, mParent->mOldPlayRegion.GetEnd());
1943
1944 return {
1945 message,
1946 showArrows ? &cursorSizeWE : &cursorHand,
1947 tooltip,
1948 };
1949}
1950
1952 const TrackPanelMouseEvent &event, AudacityProject *pProject,
1953 wxWindow *pParent)
1954 -> Result
1955{
1956 // Keep a shared pointer to self. Otherwise *this might get deleted
1957 // in HandleQPRelease on Windows! Because there is an event-loop yield
1958 // stopping playback, which caused OnCaptureLost to be called, which caused
1959 // clearing of CellularPanel targets!
1960 auto saveMe = mParent->mQPCell->mHolder.lock();
1961
1962 auto result = CommonRulerHandle::Release(event, pProject, pParent);
1963 if (!( result & RefreshCode::Cancelled )) {
1964 if (mClicked == Button::Left) {
1965 if ( mParent ) {
1966 mParent->HandleQPRelease( event.event );
1967 // Update the hot zones for cursor changes
1968 const auto &viewInfo = ViewInfo::Get( *pProject );
1969 const auto &playRegion = viewInfo.playRegion;
1970 mParent->mOldPlayRegion = playRegion;
1971 }
1972 }
1973 }
1974 return result;
1975}
1976
1977void AdornedRulerPanel::HandleQPRelease(wxMouseEvent &evt)
1978{
1979 auto &viewInfo = ViewInfo::Get(*GetProject());
1980 auto &playRegion = viewInfo.playRegion;
1981 playRegion.Order();
1982
1983 const double t0 = mTracks->GetStartTime();
1984 const double t1 = mTracks->GetEndTime();
1985 const auto &selectedRegion = viewInfo.selectedRegion;
1986 const double sel0 = selectedRegion.t0();
1987 const double sel1 = selectedRegion.t1();
1988
1989 // We want some audio in the selection, but we allow a dragged
1990 // region to include selected white-space and space before audio start.
1991 if (evt.ShiftDown() && playRegion.Empty()) {
1992 // Looping the selection or project.
1993 // Disable if track selection is in white-space beyond end of tracks and
1994 // play position is outside of track contents.
1995 if (((sel1 < t0) || (sel0 > t1)) &&
1996 ((playRegion.GetStart() < t0) || (playRegion.GetStart() > t1))) {
1998 }
1999 }
2000 // Disable if beyond end.
2001 else if (playRegion.GetStart() >= t1) {
2003 }
2004 // Disable if empty selection before start.
2005 // (allow Quick-Play region to include 'pre-roll' white space)
2006 else if (
2007 playRegion.GetEnd() - playRegion.GetStart() > 0.0 &&
2008 playRegion.GetEnd() < t0
2009 ) {
2011 }
2012
2014 mIsDragging = false;
2015 mLeftDownClick = -1;
2016
2017 auto cleanup = finally( [&] {
2018 if (mOldPlayRegion.Active()) {
2019 // Restore Locked Play region
2020 SetPlayRegion(mOldPlayRegion.GetStart(), mOldPlayRegion.GetEnd());
2021 SelectUtilities::ActivatePlayRegion(*mProject);
2022 // and release local lock
2023 mOldPlayRegion.SetActive( false );
2024 }
2025 } );
2026
2027 StartQPPlay(!evt.ShiftDown(), evt.ControlDown());
2028}
2029
2030auto AdornedRulerPanel::QPHandle::Cancel(AudacityProject *pProject) -> Result
2031{
2032 auto result = CommonRulerHandle::Cancel(pProject);
2033
2034 if (mClicked == Button::Left) {
2035 if( mParent ) {
2036 ViewInfo::Get( *pProject ).selectedRegion = mOldSelection;
2037 mParent->mMouseEventState = mesNone;
2038 mParent->SetPlayRegion(
2039 mParent->mOldPlayRegion.GetStart(), mParent->mOldPlayRegion.GetEnd());
2040 if (mParent->mOldPlayRegion.Active()) {
2041 // Restore Locked Play region
2043 // and release local lock
2044 mParent->mOldPlayRegion.SetActive( false );
2045 }
2046 }
2047 }
2048
2049 return result;
2050}
2051#endif
2052
2054 bool newDefault, bool cutPreview, const double *pStartTime)
2055{
2056 const double t0 = mTracks->GetStartTime();
2057 const double t1 = mTracks->GetEndTime();
2058 auto &viewInfo = ViewInfo::Get( *mProject );
2059 const auto &playRegion = viewInfo.playRegion;
2060 const auto &selectedRegion = viewInfo.selectedRegion;
2061 const double sel0 = selectedRegion.t0();
2062 const double sel1 = selectedRegion.t1();
2063
2064 // Start / Restart playback on left click.
2065 bool startPlaying = true; // = (playRegion.GetStart() >= 0);
2066
2067 if (startPlaying) {
2068 bool loopEnabled = true;
2069 auto oldStart = std::max(0.0, playRegion.GetStart());
2070 double start = oldStart, end = 0;
2071
2072 if (playRegion.Empty()) {
2073 // Play either a selection or the project.
2074 if (oldStart > sel0 && oldStart < sel1) {
2075 // we are in a selection, so use the selection
2076 start = sel0;
2077 end = sel1;
2078 } // not in a selection, so use the project
2079 else {
2080 start = t0;
2081 end = t1;
2082 }
2083 }
2084 else
2085 end = std::max(start, playRegion.GetEnd());
2086
2087 // Looping a tiny selection may freeze, so just play it once.
2088 loopEnabled = ((end - start) > 0.001)? true : false;
2089
2090 newDefault = (loopEnabled && newDefault);
2091 if (newDefault)
2092 cutPreview = false;
2093 auto options = ProjectAudioIO::GetDefaultOptions(*mProject, newDefault);
2094
2095 if (!cutPreview) {
2096 if (pStartTime)
2097 options.pStartTime.emplace(*pStartTime);
2098 }
2099 else
2100 options.envelope = nullptr;
2101
2102 auto mode =
2103 cutPreview ? PlayMode::cutPreviewPlay
2104 : newDefault ? PlayMode::loopedPlay
2106
2107 // Stop only after deciding where to start again, because an event
2108 // callback may change the play region back to the selection
2109 auto &projectAudioManager = ProjectAudioManager::Get( *mProject );
2110 projectAudioManager.Stop();
2111
2112 // Don't change play region, assume caller set it as needed
2113 // playRegion.SetTimes( start, end );
2114 // Refresh();
2115
2116 projectAudioManager.PlayPlayRegion((SelectedRegion(start, end)),
2117 options, mode,
2118 false);
2119
2120 }
2121}
2122
2123#if 0
2124// This version toggles ruler state indirectly via the scrubber
2125// to ensure that all the places where the state is shown update.
2126// For example buttons and menus must update.
2127void AdornedRulerPanel::OnToggleScrubRulerFromMenu(wxCommandEvent&)
2128{
2129 auto &scrubber = Scrubber::Get( *mProject );
2130 scrubber.OnToggleScrubRuler(*mProject);
2131}
2132#endif
2133
2134
2136{
2137 const auto oldSize = GetSize();
2138 wxSize size { oldSize.GetWidth(), GetRulerHeight(ShowingScrubRuler()) };
2139 if ( size != oldSize ) {
2140 SetSize(size);
2141 SetMinSize(size);
2142 PostSizeEventToParent();
2143 return true;
2144 }
2145 else
2146 return false;
2147}
2148
2150{
2151 auto pCellularPanel =
2152 dynamic_cast<CellularPanel*>( &GetProjectPanel( *GetProject() ) );
2153 if ( !pCellularPanel ) {
2154 wxASSERT( false );
2155 }
2156 else
2157 pCellularPanel->DrawOverlays( false );
2158 DrawOverlays( false );
2159}
2160
2162{
2163 auto common = [this](
2164 AButton &button, const CommandID &commandName, const TranslatableString &label) {
2165 ComponentInterfaceSymbol command{ commandName, label };
2166 ToolBar::SetButtonToolTip( *mProject, button, &command, 1u );
2167 button.SetLabel( Verbatim( button.GetToolTipText() ) );
2168
2169 button.UpdateStatus();
2170 };
2171
2172 {
2173 auto timelineOptionsButton = static_cast<AButton*>(FindWindow(OnTogglePinnedStateID));
2174 timelineOptionsButton->PopUp();
2175 // Bug 1584: Tooltip now shows what clicking will do.
2176 // Bug 2357: Action of button (and hence tooltip wording) updated.
2177 const auto label = XO("Timeline Options");
2178 common(*timelineOptionsButton, wxT("PinnedHead"), label);
2179 }
2180}
2181
2182void AdornedRulerPanel::OnPinnedButton(wxCommandEvent & /*event*/)
2183{
2185}
2186
2187void AdornedRulerPanel::OnTogglePinnedState(wxCommandEvent & /*event*/)
2188{
2191}
2192
2194{
2195 // Invoked for mouse-over preview events, or dragging, or scrub position
2196 // polling updates. Remember x coordinates, converted to times, for
2197 // drawing of guides.
2198
2199 // Keep Quick-Play within usable track area. (Dependent on zoom)
2200 const auto &viewInfo = ViewInfo::Get( *mProject );
2201 auto width = viewInfo.GetTracksUsableWidth();
2202 mousePosX = std::max(mousePosX, viewInfo.GetLeftOffset());
2203 mousePosX = std::min(mousePosX, viewInfo.GetLeftOffset() + width - 1);
2204 const auto time = Pos2Time(mousePosX);
2205
2206 for (size_t ii = 0; ii < mNumGuides; ++ii) {
2208 time + mQuickPlayOffset[ii];
2209 HandleSnapping(ii);
2210 }
2211}
2212
2213// Pop-up menus
2214
2215void AdornedRulerPanel::ShowMenu(const wxPoint & pos)
2216{
2217 const auto &viewInfo = ViewInfo::Get( *GetProject() );
2218 const auto &playRegion = viewInfo.playRegion;
2219 wxMenu rulerMenu;
2220
2221 {
2222 auto item = rulerMenu.AppendRadioItem(OnMinutesAndSecondsID,
2223 _("Minutes and Seconds"));
2225 }
2226
2227 {
2228 auto item = rulerMenu.AppendRadioItem(OnBeatsAndMeasuresID,
2229 _("Beats and Measures"));
2231 }
2232
2233 rulerMenu.AppendSeparator();
2234
2235 auto pDrag = rulerMenu.AppendCheckItem(OnSyncQuickPlaySelID, _("Setting a loop region also makes an audio selection"));
2236 pDrag->Check(mPlayRegionDragsSelection && playRegion.Active());
2237 pDrag->Enable(playRegion.Active());
2238
2239 {
2240 auto item = rulerMenu.AppendCheckItem(OnTogglePlayRegionID,
2242 item->Check(playRegion.Active());
2243 }
2244
2245 {
2246 auto item = rulerMenu.Append(OnClearPlayRegionID,
2247 /* i18n-hint Clear is a verb */
2248 _("Clear Loop"));
2249 }
2250
2251 {
2252 auto item = rulerMenu.Append(OnSetPlayRegionToSelectionID,
2253 _("Set Loop To Selection"));
2254 }
2255
2256 rulerMenu.AppendSeparator();
2257
2258 rulerMenu.AppendCheckItem(OnAutoScrollID, _("Scroll view to playhead"))->
2260
2261 rulerMenu.AppendCheckItem(OnTogglePinnedStateID, _("Continuous scrolling"))->
2263
2264 BasicMenu::Handle{ &rulerMenu }.Popup(
2266 { pos.x, pos.y }
2267 );
2268}
2269
2270void AdornedRulerPanel::ShowScrubMenu(const wxPoint & pos)
2271{
2272 auto &scrubber = Scrubber::Get( *mProject );
2273 PushEventHandler(&scrubber);
2274 auto cleanup = finally([this]{ PopEventHandler(); });
2275
2276 wxMenu rulerMenu;
2277 scrubber.PopulatePopupMenu(rulerMenu);
2278 BasicMenu::Handle{ &rulerMenu }.Popup(
2280 { pos.x, pos.y }
2281 );
2282}
2283
2285{
2286 auto &viewInfo = ViewInfo::Get( project );
2287 const auto &playRegion = viewInfo.playRegion;
2288 auto &selectedRegion = viewInfo.selectedRegion;
2289 selectedRegion.setT0(playRegion.GetStart(), false);
2290 selectedRegion.setT1(playRegion.GetEnd(), true);
2291}
2292
2294{
2295 // Play region dragging can snap to selection boundaries
2296 const auto &selectedRegion = ViewInfo::Get(*GetProject()).selectedRegion;
2297 SnapPointArray candidates;
2299 candidates = {
2300 SnapPoint{ selectedRegion.t0() },
2301 SnapPoint{ selectedRegion.t1() },
2302 };
2303 SnapManager snapManager{ *mProject, *mTracks, *mViewInfo, move(candidates) };
2304 auto results = snapManager.Snap(nullptr, mQuickPlayPos[index], false);
2305 mQuickPlayPos[index] = results.outTime;
2306 mIsSnapped[index] = results.Snapped();
2307}
2308
2310{
2311 int id = event.GetId();
2312 TimeDisplayMode changeFlag = mTimeDisplayMode;
2313 wxASSERT(id == OnMinutesAndSecondsID || id == OnBeatsAndMeasuresID);
2316
2318
2319 if (changeFlag != mTimeDisplayMode)
2320 Refresh();
2321}
2322
2324{
2326 gPrefs->Write(wxT("/QuickPlay/DragSelection"), mPlayRegionDragsSelection);
2327 gPrefs->Flush();
2328}
2329
2331{
2333 gPrefs->Write(wxT("/GUI/AutoScroll"), false);
2334 else
2335 gPrefs->Write(wxT("/GUI/AutoScroll"), true);
2336
2337 gPrefs->Flush();
2338
2340}
2341
2342
2344{
2346}
2347
2349{
2351}
2352
2354{
2356}
2357
2358
2360 MenuChoice choice, const wxPoint *pPosition)
2361{
2362 wxPoint position;
2363 if(pPosition)
2364 position = *pPosition;
2365 else
2366 {
2367 auto rect = GetRect();
2368 //Old code put menu too low down. y position applied twice.
2369 //position = { rect.GetLeft() + 1, rect.GetBottom() + 1 };
2370
2371 // The cell does not pass in the mouse or button position.
2372 // We happen to know this is the pin/unpin button
2373 // so these magic values 'fix a bug' - but really the cell should
2374 // pass more information to work with in.
2375 position = { rect.GetLeft() + 38, rect.GetHeight()/2 + 1 };
2376 }
2377
2378 switch (choice) {
2380 ShowMenu(position);
2382 break;
2383 case MenuChoice::Scrub:
2384 ShowScrubMenu(position); break;
2385 default:
2386 return;
2387 }
2388}
2389
2390using ColorId = decltype(clrTrackInfo);
2391
2393{
2394 return clrTimelineRulerBackground;
2395}
2396
2398{
2399 return clrTrackPanelText;
2400}
2401
2403{
2404 return TimelineTextColor();
2405}
2406
2408{
2409 return clrRulerSelected;
2410}
2411
2413{
2414 return isActive ? clrLoopEnabled : clrLoopDisabled;
2415}
2416
2417static inline wxColour AlphaBlend(ColorId fg, ColorId bg, double alpha)
2418{
2419 const auto &fgc = theTheme.Colour(fg);
2420 const auto &bgc = theTheme.Colour(bg);
2421 return wxColour{
2422 wxColour::AlphaBlend(fgc.Red(), bgc.Red(), alpha),
2423 wxColour::AlphaBlend(fgc.Green(), bgc.Green(), alpha),
2424 wxColour::AlphaBlend(fgc.Blue(), bgc.Blue(), alpha)
2425 };
2426}
2427
2429{
2430 // Draw AdornedRulerPanel border
2432 dc->DrawRectangle( mOuter );
2433
2434 if (ShowingScrubRuler()) {
2435 // Let's distinguish the scrubbing area by using a themable
2436 // colour and a line to set it off.
2437 AColor::UseThemeColour(dc, clrScrubRuler, TimelineTextColor() );
2438 wxRect ScrubRect = mScrubZone;
2439 ScrubRect.Inflate( 1,0 );
2440 dc->DrawRectangle(ScrubRect);
2441 }
2442}
2443
2445{
2446 // Black stroke at bottom
2447 dc->SetPen( *wxBLACK_PEN );
2448 AColor::Line( *dc, mOuter.x,
2449 mOuter.y + mOuter.height - 1,
2450 mOuter.x + mOuter.width - 1 ,
2451 mOuter.y + mOuter.height - 1 );
2452}
2453
2454void AdornedRulerPanel::DoDrawMarks(wxDC * dc, bool /*text */ )
2455{
2456 const double min = Pos2Time(mInner.x);
2457 const double hiddenMin = Pos2Time(mInner.x, true);
2458 const double max = Pos2Time(mInner.x + mInner.width);
2459 const double hiddenMax = Pos2Time(mInner.x + mInner.width, true);
2460
2462 mRuler.SetRange( min, max, hiddenMin, hiddenMax );
2464 {
2465 mRuler.SetTickLengths({ 5, 3, 1 });
2466 }
2468 {
2469 mRuler.SetTickLengths({ 4, 2, 2 });
2470 }
2471 mRuler.Draw( *dc );
2472}
2473
2475{
2476 Refresh();
2477}
2478
2480{
2481 const auto &viewInfo = ViewInfo::Get(*mProject);
2482 const auto &playRegion = viewInfo.playRegion;
2483 const auto t0 = playRegion.GetLastActiveStart(),
2484 t1 = playRegion.GetLastActiveEnd();
2485 return RegionRectangle(t0, t1);
2486}
2487
2489{
2490 const auto &viewInfo = ViewInfo::Get(*mProject);
2491 const auto &selectedRegion = viewInfo.selectedRegion;
2492 const auto t0 = selectedRegion.t0(), t1 = selectedRegion.t1();
2493 return RegionRectangle(t0, t1);
2494}
2495
2496wxRect AdornedRulerPanel::RegionRectangle(double t0, double t1) const
2497{
2498 int p0 = -PLAY_REGION_TRIANGLE_SIZE;
2499 int p1 = -PLAY_REGION_TRIANGLE_SIZE;
2500 if (t0 == t1)
2501 // Make the rectangle off-screen horizontally, but set the height
2502 ;
2503 else {
2504 p0 = max(mInner.x, Time2Pos(t0));
2505 p1 = min(mInner.x + mInner.width, Time2Pos(t1));
2506 }
2507
2508 const int left = p0, top = mInner.y, right = p1, bottom = mInner.GetBottom();
2509 return { wxPoint{left, top}, wxPoint{right, bottom} };
2510}
2511
2513 wxDC * dc, const wxRect &rectP, const wxRect &rectL, const wxRect &rectR)
2514{
2515 const auto &viewInfo = ViewInfo::Get(*mProject);
2516 const auto& playRegion = viewInfo.playRegion;
2517
2518 const bool isActive = (mLastPlayRegionActive = playRegion.Active());
2519
2520 if (playRegion.IsLastActiveRegionClear())
2521 return;
2522
2523 // Paint the selected region bolder if independently varying, else dim
2524 const auto color = TimelineLoopRegionColor(isActive);
2525 dc->SetBrush( wxBrush( theTheme.Colour( color )) );
2526 dc->SetPen( wxPen( theTheme.Colour( color )) );
2527
2528 dc->DrawRectangle( rectP.Intersect(rectL) );
2529 dc->DrawRectangle( rectP.Intersect(rectR) );
2530}
2531
2532void AdornedRulerPanel::DoDrawPlayRegionLimits(wxDC * dc, const wxRect &rect)
2533{
2534 // Color the edges of the play region like the ticks and numbers
2535 ADCChanger cleanup( dc );
2536 const auto edgeColour = theTheme.Colour(TimelineLimitsColor());
2537 dc->SetPen( { edgeColour } );
2538 dc->SetBrush( { edgeColour } );
2539
2540 constexpr int side = 7;
2541 constexpr int sideLessOne = side - 1;
2542
2543 // Paint two shapes, each a line plus triangle at bottom
2544 const auto left = rect.GetLeft(),
2545 right = rect.GetRight(),
2546 bottom = rect.GetBottom(),
2547 top = rect.GetTop();
2548 {
2549 wxPoint points[]{
2550 {left, bottom - sideLessOne},
2551 {left - sideLessOne, bottom},
2552 {left, bottom},
2553 {left, top},
2554 };
2555 dc->DrawPolygon( 4, points );
2556 }
2557
2558 {
2559 wxPoint points[]{
2560 {right, top},
2561 {right, bottom},
2562 {right + sideLessOne, bottom},
2563 {right, bottom - sideLessOne},
2564 };
2565 dc->DrawPolygon( 4, points );
2566 }
2567}
2568
2569constexpr double SelectionOpacity = 0.4;
2570
2571void AdornedRulerPanel::DoDrawOverlap(wxDC * dc, const wxRect &rect)
2572{
2573 dc->SetBrush( wxBrush{ AlphaBlend(
2575 SelectionOpacity) } );
2576 dc->SetPen( *wxTRANSPARENT_PEN );
2577 dc->DrawRectangle( rect );
2578}
2579
2581 wxDC * dc, const wxRect &rectS, const wxRect &rectL, const wxRect &rectR)
2582{
2583 dc->SetBrush( wxBrush{ AlphaBlend(
2585 dc->SetPen( *wxTRANSPARENT_PEN );
2586 dc->DrawRectangle( rectS.Intersect(rectL) );
2587 dc->DrawRectangle( rectS.Intersect(rectR) );
2588}
2589
2591{
2592 return ProperRulerHeight + (showScrubBar ? ScrubHeight : 0);
2593}
2594
2596{
2597 if (mLeftOffset != offset) {
2598 mLeftOffset = offset;
2599 mUpdater.SetData(mViewInfo, offset);
2601 }
2602}
2603
2604// Draws the scrubbing/seeking indicator.
2606 wxDC * dc, wxCoord xx, int width, bool scrub, bool seek)
2607{
2608 ADCChanger changer(dc); // Undo pen and brush changes at function exit
2609
2610 wxPoint tri[ 3 ];
2611 if (seek) {
2612 auto height = IndicatorHeightForWidth(width);
2613 // Make four triangles
2614 const int TriangleWidth = width * 3 / 8;
2615
2616 // Double-double headed, left-right
2617 auto yy = ShowingScrubRuler()
2618 ? mScrubZone.y
2619 : (mInner.GetBottom() + 1) - 1 /* bevel */ - height;
2620 tri[ 0 ].x = xx - IndicatorOffset;
2621 tri[ 0 ].y = yy;
2622 tri[ 1 ].x = xx - IndicatorOffset;
2623 tri[ 1 ].y = yy + height;
2624 tri[ 2 ].x = xx - TriangleWidth;
2625 tri[ 2 ].y = yy + height / 2;
2626 dc->DrawPolygon( 3, tri );
2627
2628 tri[ 0 ].x -= TriangleWidth;
2629 tri[ 1 ].x -= TriangleWidth;
2630 tri[ 2 ].x -= TriangleWidth;
2631 dc->DrawPolygon( 3, tri );
2632
2633 tri[ 0 ].x = tri[ 1 ].x = xx + IndicatorOffset;
2634 tri[ 2 ].x = xx + TriangleWidth;
2635 dc->DrawPolygon( 3, tri );
2636
2637
2638 tri[ 0 ].x += TriangleWidth;
2639 tri[ 1 ].x += TriangleWidth;
2640 tri[ 2 ].x += TriangleWidth;
2641 dc->DrawPolygon( 3, tri );
2642 }
2643 else if (scrub) {
2644 auto height = IndicatorHeightForWidth(width);
2645 const int IndicatorHalfWidth = width / 2;
2646
2647 // Double headed, left-right
2648 auto yy = ShowingScrubRuler()
2649 ? mScrubZone.y
2650 : (mInner.GetBottom() + 1) - 1 /* bevel */ - height;
2651 tri[ 0 ].x = xx - IndicatorOffset;
2652 tri[ 0 ].y = yy;
2653 tri[ 1 ].x = xx - IndicatorOffset;
2654 tri[ 1 ].y = yy + height;
2655 tri[ 2 ].x = xx - IndicatorHalfWidth;
2656 tri[ 2 ].y = yy + height / 2;
2657 dc->DrawPolygon( 3, tri );
2658 tri[ 0 ].x = tri[ 1 ].x = xx + IndicatorOffset;
2659 tri[ 2 ].x = xx + IndicatorHalfWidth;
2660 dc->DrawPolygon( 3, tri );
2661 }
2662}
2663
2665 double playRegionStart, double playRegionEnd)
2666{
2667 // This is called by AudacityProject to make the play region follow
2668 // the current selection. But while the user is selecting a play region
2669 // with the mouse directly in the ruler, changes from outside are blocked.
2671 return;
2672
2673 auto &viewInfo = ViewInfo::Get( *GetProject() );
2674 auto &playRegion = viewInfo.playRegion;
2675 playRegion.SetTimes( playRegionStart, playRegionEnd );
2676
2677 Refresh();
2678}
2679
2681{
2682 ProjectAudioManager::Get( *mProject ).Stop();
2683
2684 auto &viewInfo = ViewInfo::Get( *GetProject() );
2685 auto &playRegion = viewInfo.playRegion;
2686 playRegion.Clear();
2687
2688 Refresh();
2689}
2690
2691void AdornedRulerPanel::GetMaxSize(wxCoord *width, wxCoord *height)
2692{
2693 mRuler.GetMaxSize(width, height);
2694}
2695
2697
2699 s_AcceptsFocus = true;
2700 return TempAllowFocus{ &s_AcceptsFocus };
2701}
2702
2704{
2705 nn = std::min(nn, MAX_GUIDES);
2706 // If increasing the number of guides, reinitialize newer ones
2707 for (size_t ii = mNumGuides; ii < nn; ++ii) {
2708 mQuickPlayOffset[ii] = 0;
2709 mQuickPlayPosUnsnapped[ii] = 0;
2710 mQuickPlayPos[ii] = 0;
2711 mIsSnapped[ii] = false;
2712 }
2713 mNumGuides = nn;
2714}
2715
2717{
2718 auto temp = TemporarilyAllowFocus();
2719 SetFocus();
2720}
2721
2722// Second-level subdivision includes quick-play region and maybe the scrub bar
2723// and also shaves little margins above and below
2725 explicit Subgroup( const AdornedRulerPanel &ruler ) : mRuler{ ruler } {}
2726 Subdivision Children( const wxRect & ) override
2727 {
2728 return { Axis::Y, ( mRuler.ShowingScrubRuler() )
2729 ? Refinement{
2730 { mRuler.mInner.GetTop(), mRuler.mQPCell },
2731 { mRuler.mScrubZone.GetTop(), mRuler.mScrubbingCell },
2732 { mRuler.mScrubZone.GetBottom() + 1, nullptr }
2733 }
2734 : Refinement{
2735 { mRuler.mInner.GetTop(), mRuler.mQPCell },
2736 { mRuler.mInner.GetBottom() + 1, nullptr }
2737 }
2738 };
2739 }
2741};
2742
2743// Top-level subdivision shaves little margins off left and right
2745 explicit MainGroup( const AdornedRulerPanel &ruler ) : mRuler{ ruler } {}
2746 Subdivision Children( const wxRect & ) override
2747 { return { Axis::X, Refinement{
2748 // Subgroup is a throwaway object
2749 { mRuler.mInner.GetLeft(), std::make_shared< Subgroup >( mRuler ) },
2750 { mRuler.mInner.GetRight() + 1, nullptr }
2751 } }; }
2753};
2754
2756{
2757 auto &scrubber = Scrubber::Get( *GetProject() );
2758 return scrubber.ShowsBar();
2759}
2760
2761// CellularPanel implementation
2762std::shared_ptr<TrackPanelNode> AdornedRulerPanel::Root()
2763{
2764 // Root is a throwaway object
2765 return std::make_shared< MainGroup >( *this );
2766}
2767
2769{
2770 return mProject;
2771}
2772
2773std::shared_ptr<TrackPanelCell> AdornedRulerPanel::GetFocusedCell()
2774{
2775 // No switching of focus yet to the other, scrub zone
2776 return mQPCell;
2777}
2778
2780{
2781}
2782
2784 TrackPanelCell *, TrackPanelCell *, unsigned refreshResult)
2785{
2786 if (refreshResult & RefreshCode::RefreshAll)
2787 Refresh(); // Overlays will be repainted too
2788 else if (refreshResult & RefreshCode::DrawOverlays)
2789 DrawBothOverlays(); // cheaper redrawing of guidelines only
2790}
2791
2793{
2794 ProjectStatus::Get( *GetProject() ).Set(message);
2795}
2796
2798{
2799 if (!mOverlay) {
2800 mOverlay =
2801 std::make_shared<TrackPanelGuidelineOverlay>( mProject );
2802 auto pCellularPanel =
2803 dynamic_cast<CellularPanel*>( &GetProjectPanel( *GetProject() ) );
2804 if ( !pCellularPanel ) {
2805 wxASSERT( false );
2806 }
2807 else
2808 pCellularPanel->AddOverlay( mOverlay );
2809 this->AddOverlay( mOverlay->mPartner );
2810 }
2811}
2812
2814{
2818
2819 auto &project = *mProject;
2820 // Update button image
2822
2823 auto &scrubber = Scrubber::Get( project );
2824 if (scrubber.HasMark())
2825 scrubber.SetScrollScrubbing(value);
2826}
2827
2829{
2830 return mTimeDisplayMode;
2831}
2832
2834{
2835 if (mTimeDisplayMode == type)
2836 return;
2837
2838 mTimeDisplayMode = type;
2840 Refresh();
2841}
2842
2843// Attach menu item
2844
2845#include "CommandContext.h"
2846#include "CommonCommandFlags.h"
2847
2848namespace {
2850{
2852}
2853
2854using namespace MenuRegistry;
2856 Command( wxT("PinnedHead"), XXO("Continuous scrolling"),
2858 // Switching of scrolling on and off is permitted
2859 // even during transport
2861 Options{}.CheckTest([](const AudacityProject&){
2863 { wxT("Transport/Other/Options/Part2"), { OrderingHint::Begin, {} } }
2864};
2865}
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:522
void PopUp()
Definition: AButton.cpp:652
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)
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:88
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:67
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.