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 6
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 = 2, // 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(theTheme.Colour( clrMedium ));
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( 1, 0 );
1403
1404 Grabber * pGrabber = safenew Grabber(this, {});
1405 pGrabber->SetAsSpacer( true );
1406 //pGrabber->SetSize( 10, 27 ); // default is 10,27
1407 pGrabber->SetPosition( position );
1408
1409 position.x = 12;
1410
1411 auto size = theTheme.ImageSize( bmpRecoloredUpSmall );
1412 size.y = std::min(size.y, GetRulerHeight(false));
1413
1414 const auto button = ToolBar::MakeButton(
1415 this,
1416 bmpRecoloredUpSmall, bmpRecoloredDownSmall,
1417 bmpRecoloredUpHiliteSmall, bmpRecoloredHiliteSmall,
1418 bmpCogwheel, bmpCogwheel, bmpCogwheel,
1419 OnTogglePinnedStateID, position, true, size
1420 );
1421
1422 position.x += size.GetWidth();
1423 mButtons[iButton++] = button;
1424
1426}
1427
1429{
1431}
1432
1433namespace {
1435 {
1436#if 0
1437 if(scrubber.Seeks())
1438 /* i18n-hint: These commands assist the user in finding a sound by ear. ...
1439 "Scrubbing" is variable-speed playback, ...
1440 "Seeking" is normal speed playback but with skips
1441 */
1442 return XO("Click or drag to begin Seek");
1443 else
1444 /* i18n-hint: These commands assist the user in finding a sound by ear. ...
1445 "Scrubbing" is variable-speed playback, ...
1446 "Seeking" is normal speed playback but with skips
1447 */
1448 return XO("Click or drag to begin Scrub");
1449#else
1450 /* i18n-hint: These commands assist the user in finding a sound by ear. ...
1451 "Scrubbing" is variable-speed playback, ...
1452 "Seeking" is normal speed playback but with skips
1453 */
1454 return XO("Click & move to Scrub. Click & drag to Seek.");
1455#endif
1456 }
1457
1459 const Scrubber &scrubber, bool clicked)
1460 {
1461#if 0
1462 if(scrubber.Seeks())
1463 /* i18n-hint: These commands assist the user in finding a sound by ear. ...
1464 "Scrubbing" is variable-speed playback, ...
1465 "Seeking" is normal speed playback but with skips
1466 */
1467 return XO("Move to Seek");
1468 else
1469 /* i18n-hint: These commands assist the user in finding a sound by ear. ...
1470 "Scrubbing" is variable-speed playback, ...
1471 "Seeking" is normal speed playback but with skips
1472 */
1473 return XO("Move to Scrub");
1474#else
1475 if( clicked ) {
1476 // Since mouse is down, mention dragging first.
1477 // IsScrubbing is true if Scrubbing OR seeking.
1478 if( scrubber.IsScrubbing() )
1479 // User is dragging already, explain.
1480 return XO("Drag to Seek. Release to stop seeking.");
1481 else
1482 // User has clicked but not yet moved or released.
1483 return XO("Drag to Seek. Release and move to Scrub.");
1484 }
1485 // Since mouse is up, mention moving first.
1486 return XO("Move to Scrub. Drag to Seek.");
1487#endif
1488 }
1489
1490 const TranslatableString ScrubbingMessage(const Scrubber &scrubber, bool clicked)
1491 {
1492 if (scrubber.HasMark())
1493 return ContinueScrubbingMessage(scrubber, clicked);
1494 else
1495 return StartScrubbingMessage(scrubber);
1496 }
1497}
1498
1499void AdornedRulerPanel::OnIdle( wxIdleEvent &evt )
1500{
1501 evt.Skip();
1502 DoIdle();
1503}
1504
1506{
1507 bool changed = UpdateRects();
1508 changed = SetPanelSize() || changed;
1509
1510 auto &project = *mProject;
1511 auto &viewInfo = ViewInfo::Get( project );
1512 const auto &selectedRegion = viewInfo.selectedRegion;
1513 const auto &playRegion = viewInfo.playRegion;
1514
1515 changed = changed
1516 || mLastDrawnSelectedRegion != selectedRegion
1517 || mLastDrawnPlayRegion != std::pair{
1518 playRegion.GetLastActiveStart(), playRegion.GetLastActiveEnd() }
1519 || mLastDrawnH != viewInfo.hpos
1520 || mLastDrawnZoom != viewInfo.GetZoom()
1521 || mLastPlayRegionActive != viewInfo.playRegion.Active()
1522 ;
1523 if (changed)
1524 // Cause ruler redraw anyway, because we may be zooming or scrolling,
1525 // showing or hiding the scrub bar, etc.
1526 Refresh();
1527}
1528
1530{
1531 if (evt.type == AudioIOEvent::MONITOR)
1532 return;
1533 if ( evt.type == AudioIOEvent::CAPTURE ) {
1534 if (evt.on)
1535 {
1536 mIsRecording = true;
1537 this->CellularPanel::CancelDragging( false );
1539
1541 }
1542 else {
1543 mIsRecording = false;
1545 }
1546 }
1547
1548 if ( !evt.on )
1549 // So that the play region is updated
1551}
1552
1553void AdornedRulerPanel::OnPaint(wxPaintEvent & WXUNUSED(evt))
1554{
1555 const auto &viewInfo = ViewInfo::Get( *GetProject() );
1556 const auto &playRegion = viewInfo.playRegion;
1557 const auto playRegionBounds = std::pair{
1558 playRegion.GetLastActiveStart(), playRegion.GetLastActiveEnd() };
1559 mLastDrawnH = viewInfo.hpos;
1560 mLastDrawnZoom = viewInfo.GetZoom();
1561 mLastDrawnPlayRegion = playRegionBounds;
1562 mLastDrawnSelectedRegion = viewInfo.selectedRegion;
1563 // To do, note other fisheye state when we have that
1564
1565 wxPaintDC dc(this);
1566
1567 auto &backDC = GetBackingDCForRepaint();
1568
1569 DoDrawBackground(&backDC);
1570
1571 // Find play region rectangle, selected rectangle, and their overlap
1572 const auto rectP = PlayRegionRectangle(),
1573 rectS = SelectedRegionRectangle(),
1574 rectO = rectP.Intersect(rectS);
1575
1576 // What's left and right of the overlap? Assume same tops and bottoms
1577 const auto top = rectP.GetTop(),
1578 bottom = rectP.GetBottom();
1579 wxRect rectL{
1580 wxPoint{ 0, top }, wxPoint{ this->GetSize().GetWidth() - 1, bottom } };
1581 wxRect rectR = {};
1582 if (!rectO.IsEmpty()) {
1583 rectR = { wxPoint{ rectO.GetRight() + 1, top }, rectL.GetBottomRight() };
1584 rectL = { rectL.GetTopLeft(), wxPoint{ rectO.GetLeft() - 1, bottom } };
1585 }
1586
1587 DoDrawPlayRegion(&backDC, rectP, rectL, rectR);
1588 DoDrawOverlap(&backDC, rectO);
1589 DoDrawSelection(&backDC, rectS, rectL, rectR);
1590
1591 DoDrawPlayRegionLimits(&backDC, rectP);
1592
1593 DoDrawMarks(&backDC, true);
1594
1595 DoDrawEdge(&backDC);
1596
1597 DisplayBitmap(dc);
1598
1599 // Stroke extras direct to the client area,
1600 // maybe outside of the damaged area
1601 // As with TrackPanel, do not make a NEW wxClientDC or else Mac flashes badly!
1602 dc.DestroyClippingRegion();
1603 DrawOverlays(true, &dc);
1604}
1605
1606void AdornedRulerPanel::OnSize(wxSizeEvent &evt)
1607{
1608 mOuter = GetClientRect();
1609 if (mOuter.GetWidth() == 0 || mOuter.GetHeight() == 0)
1610 {
1611 return;
1612 }
1613
1614 UpdateRects();
1615
1617}
1618
1619void AdornedRulerPanel::OnLeave(wxMouseEvent& evt)
1620{
1621 evt.Skip();
1622 CallAfter([this]{
1624 });
1625}
1626
1628{
1629 if (message.appearance)
1630 return;
1632}
1633
1635{
1636 auto &selectedRegion = mViewInfo->selectedRegion;
1637 DoSelectionChange( selectedRegion );
1638}
1639
1641 const SelectedRegion &selectedRegion )
1642{
1643
1644 auto gAudioIO = AudioIOBase::Get();
1645 if ( !ViewInfo::Get( *mProject ).playRegion.Active() ) {
1646 // "Inactivated" play region follows the selection.
1647 SetPlayRegion( selectedRegion.t0(), selectedRegion.t1() );
1648 }
1649}
1650
1652{
1653 auto inner = mOuter;
1654 wxRect scrubZone;
1655 inner.x += LeftMargin;
1656 inner.width -= (LeftMargin + RightMargin);
1657
1658 auto top = &inner;
1659 auto bottom = &inner;
1660
1661 if (ShowingScrubRuler()) {
1662 scrubZone = inner;
1663 auto scrubHeight = std::min(scrubZone.height, (int)(ScrubHeight));
1664
1665 int topHeight;
1666#ifdef SCRUB_ABOVE
1667 top = &scrubZone, topHeight = scrubHeight;
1668#else
1669 auto qpHeight = scrubZone.height - scrubHeight;
1670 bottom = &scrubZone, topHeight = qpHeight;
1671 // Increase scrub zone height so that hit testing finds it and
1672 // not QP region, when on bottom 'edge'.
1673 scrubZone.height+=BottomMargin;
1674#endif
1675
1676 top->height = topHeight;
1677 bottom->height -= topHeight;
1678 bottom->y += topHeight;
1679 }
1680
1681 top->y += TopMargin;
1682 top->height -= TopMargin;
1683
1684 bottom->height -= BottomMargin;
1685
1686 if (!ShowingScrubRuler())
1687 scrubZone = inner;
1688
1689 if ( inner == mInner && scrubZone == mScrubZone )
1690 // no changes
1691 return false;
1692
1693 mInner = inner;
1694 mScrubZone = scrubZone;
1695
1696 mRuler.SetBounds(mInner.GetLeft(),
1697 mInner.GetTop(),
1698 mInner.GetRight(),
1699 mInner.GetBottom());
1700
1701 return true;
1702}
1703
1704double AdornedRulerPanel::Pos2Time(int p, bool ignoreFisheye) const
1705{
1707 , ignoreFisheye
1708 );
1709}
1710
1711int AdornedRulerPanel::Time2Pos(double t, bool ignoreFisheye) const
1712{
1714 , ignoreFisheye
1715 );
1716}
1717
1718bool AdornedRulerPanel::IsWithinMarker(int mousePosX, double markerTime)
1719{
1720 if (markerTime < 0)
1721 return false;
1722
1723 int pixelPos = Time2Pos(markerTime);
1724 int boundLeft = pixelPos - SELECT_TOLERANCE_PIXEL;
1725 int boundRight = pixelPos + SELECT_TOLERANCE_PIXEL;
1726
1727 return mousePosX >= boundLeft && mousePosX < boundRight;
1728}
1729
1730#ifdef QUICK_PLAY_HANDLE
1731auto AdornedRulerPanel::QPHandle::Click(
1732 const TrackPanelMouseEvent &event, AudacityProject *pProject) -> Result
1733{
1734 auto result = CommonRulerHandle::Click(event, pProject);
1735 if (!( result & RefreshCode::Cancelled )) {
1736 if (mClicked == Button::Left) {
1737 if (!mParent)
1739
1740 auto &scrubber = Scrubber::Get( *pProject );
1741 if(scrubber.HasMark()) {
1742 // We can't stop scrubbing yet (see comments in Bug 1391),
1743 // but we can pause it.
1744 ProjectAudioManager::Get( *pProject ).OnPause();
1745 }
1746
1747 // Store the initial play region state
1748 const auto &viewInfo = ViewInfo::Get( *pProject );
1749 const auto &playRegion = viewInfo.playRegion;
1750 mParent->mOldPlayRegion = playRegion;
1751
1752 // Save old selection, in case drag of selection is cancelled
1753 mOldSelection = ViewInfo::Get( *pProject ).selectedRegion;
1754
1755 mParent->HandleQPClick( event.event, mX );
1756 mParent->HandleQPDrag( event.event, mX );
1757 }
1758 }
1759
1760 return result;
1761}
1762
1763void AdornedRulerPanel::HandleQPClick(wxMouseEvent &evt, wxCoord mousePosX)
1764{
1765 // Temporarily inactivate play region
1766 if (mOldPlayRegion.Active() && evt.LeftDown()) {
1767 //mPlayRegionLock = true;
1769 }
1770
1773 bool isWithinStart = IsWithinMarker(mousePosX, mOldPlayRegion.GetStart());
1774 bool isWithinEnd = IsWithinMarker(mousePosX, mOldPlayRegion.GetEnd());
1775
1776 if (isWithinStart || isWithinEnd) {
1777 // If Quick-Play is playing from a point, we need to treat it as a click
1778 // not as dragging.
1779 if (mOldPlayRegion.Empty())
1781 // otherwise check which marker is nearer
1782 else {
1783 // Don't compare times, compare positions.
1784 //if (fabs(mQuickPlayPos[0] - mPlayRegionStart) < fabs(mQuickPlayPos[0] - mPlayRegionEnd))
1785 auto start = mOldPlayRegion.GetStart();
1786 auto end = mOldPlayRegion.GetEnd();
1787 if (abs(Time2Pos(mQuickPlayPos[0]) - Time2Pos(start)) <
1788 abs(Time2Pos(mQuickPlayPos[0]) - Time2Pos(end)))
1790 else
1792 }
1793 }
1794 else {
1795 // Clicked but not yet dragging
1797 }
1798}
1799
1800auto AdornedRulerPanel::QPHandle::Drag(
1801 const TrackPanelMouseEvent &event, AudacityProject *pProject) -> Result
1802{
1803 auto result = CommonRulerHandle::Drag(event, pProject);
1804 if (!( result & RefreshCode::Cancelled )) {
1805 if (mClicked == Button::Left) {
1806 if ( mParent ) {
1807 mX = event.event.m_x;
1808 mParent->UpdateQuickPlayPos( mX );
1809 mParent->HandleQPDrag( event.event, mX );
1810 }
1811 }
1812 }
1813 return result;
1814}
1815
1816void AdornedRulerPanel::HandleQPDrag(wxMouseEvent &/*event*/, wxCoord mousePosX)
1817{
1818 bool isWithinClick =
1819 (mLeftDownClickUnsnapped >= 0) &&
1821 bool isWithinStart = IsWithinMarker(mousePosX, mOldPlayRegion.GetStart());
1822 bool isWithinEnd = IsWithinMarker(mousePosX, mOldPlayRegion.GetEnd());
1823 bool canDragSel = !mOldPlayRegion.Active() && mPlayRegionDragsSelection;
1824 auto &viewInfo = ViewInfo::Get( *GetProject() );
1825 auto &playRegion = viewInfo.playRegion;
1826
1827 switch (mMouseEventState)
1828 {
1829 case mesNone:
1830 // If close to either end of play region, snap to closest
1831 if (isWithinStart || isWithinEnd) {
1834 else
1836 }
1837 break;
1839 // Don't start dragging until beyond tolerance initial playback start
1840 if (!mIsDragging && isWithinStart)
1842 else
1843 mIsDragging = true;
1844 // avoid accidental tiny selection
1845 if (isWithinEnd)
1847 playRegion.SetStart( mQuickPlayPos[0] );
1848 if (canDragSel) {
1850 }
1851 break;
1853 if (!mIsDragging && isWithinEnd) {
1855 }
1856 else
1857 mIsDragging = true;
1858 if (isWithinStart) {
1860 }
1861 playRegion.SetEnd( mQuickPlayPos[0] );
1862 if (canDragSel) {
1864 }
1865 break;
1867
1868 // Don't start dragging until mouse is beyond tolerance of initial click.
1869 if (isWithinClick || mLeftDownClick == -1) {
1871 playRegion.SetTimes(mLeftDownClick, mLeftDownClick);
1872 }
1873 else {
1875 }
1876 break;
1878 if (isWithinClick) {
1880 }
1881
1883 playRegion.SetTimes( mQuickPlayPos[0], mLeftDownClick );
1884 else
1885 playRegion.SetTimes( mLeftDownClick, mQuickPlayPos[0] );
1886 if (canDragSel) {
1888 }
1889 break;
1890 }
1891 Refresh();
1892 Update();
1893}
1894#endif
1895
1897 const TrackPanelMouseState &, AudacityProject *pProject)
1899{
1900 auto &scrubber = Scrubber::Get( *pProject );
1901 auto message = ScrubbingMessage(scrubber, mClicked == Button::Left);
1902
1903 mParent->SetNumGuides(1);
1904 return {
1905 message,
1906 {},
1907 // Tooltip is same as status message, or blank
1908 mParent ? message : TranslatableString{},
1909 };
1910}
1911
1912#ifdef QUICK_PLAY_HANDLE
1913auto AdornedRulerPanel::QPHandle::Preview(
1914 const TrackPanelMouseState &state, AudacityProject *pProject)
1916{
1917 mParent->SetNumGuides(1);
1918 TranslatableString tooltip;
1919 #if 0
1920 if (mParent) {
1921 if (!mParent->mQuickPlayEnabled)
1922 tooltip = XO("Quick-Play disabled");
1923 else
1924 tooltip = XO("Quick-Play enabled");
1925 }
1926 #endif
1927
1928 TranslatableString message;
1929 auto &scrubber = Scrubber::Get( *pProject );
1930 const bool scrubbing = scrubber.HasMark();
1931 if (scrubbing)
1932 // Don't distinguish zones
1933 message = ScrubbingMessage(scrubber, false);
1934 else
1935 // message = Insert timeline status bar message here
1936 ;
1937
1938 static wxCursor cursorHand{ wxCURSOR_HAND };
1939 static wxCursor cursorSizeWE{ wxCURSOR_SIZEWE };
1940
1941 bool showArrows = false;
1942 if (mParent)
1943 showArrows =
1944 (mClicked == Button::Left)
1945 || mParent->IsWithinMarker(
1946 state.state.m_x, mParent->mOldPlayRegion.GetStart())
1947 || mParent->IsWithinMarker(
1948 state.state.m_x, mParent->mOldPlayRegion.GetEnd());
1949
1950 return {
1951 message,
1952 showArrows ? &cursorSizeWE : &cursorHand,
1953 tooltip,
1954 };
1955}
1956
1958 const TrackPanelMouseEvent &event, AudacityProject *pProject,
1959 wxWindow *pParent)
1960 -> Result
1961{
1962 // Keep a shared pointer to self. Otherwise *this might get deleted
1963 // in HandleQPRelease on Windows! Because there is an event-loop yield
1964 // stopping playback, which caused OnCaptureLost to be called, which caused
1965 // clearing of CellularPanel targets!
1966 auto saveMe = mParent->mQPCell->mHolder.lock();
1967
1968 auto result = CommonRulerHandle::Release(event, pProject, pParent);
1969 if (!( result & RefreshCode::Cancelled )) {
1970 if (mClicked == Button::Left) {
1971 if ( mParent ) {
1972 mParent->HandleQPRelease( event.event );
1973 // Update the hot zones for cursor changes
1974 const auto &viewInfo = ViewInfo::Get( *pProject );
1975 const auto &playRegion = viewInfo.playRegion;
1976 mParent->mOldPlayRegion = playRegion;
1977 }
1978 }
1979 }
1980 return result;
1981}
1982
1983void AdornedRulerPanel::HandleQPRelease(wxMouseEvent &evt)
1984{
1985 auto &viewInfo = ViewInfo::Get(*GetProject());
1986 auto &playRegion = viewInfo.playRegion;
1987 playRegion.Order();
1988
1989 const double t0 = mTracks->GetStartTime();
1990 const double t1 = mTracks->GetEndTime();
1991 const auto &selectedRegion = viewInfo.selectedRegion;
1992 const double sel0 = selectedRegion.t0();
1993 const double sel1 = selectedRegion.t1();
1994
1995 // We want some audio in the selection, but we allow a dragged
1996 // region to include selected white-space and space before audio start.
1997 if (evt.ShiftDown() && playRegion.Empty()) {
1998 // Looping the selection or project.
1999 // Disable if track selection is in white-space beyond end of tracks and
2000 // play position is outside of track contents.
2001 if (((sel1 < t0) || (sel0 > t1)) &&
2002 ((playRegion.GetStart() < t0) || (playRegion.GetStart() > t1))) {
2004 }
2005 }
2006 // Disable if beyond end.
2007 else if (playRegion.GetStart() >= t1) {
2009 }
2010 // Disable if empty selection before start.
2011 // (allow Quick-Play region to include 'pre-roll' white space)
2012 else if (
2013 playRegion.GetEnd() - playRegion.GetStart() > 0.0 &&
2014 playRegion.GetEnd() < t0
2015 ) {
2017 }
2018
2020 mIsDragging = false;
2021 mLeftDownClick = -1;
2022
2023 auto cleanup = finally( [&] {
2024 if (mOldPlayRegion.Active()) {
2025 // Restore Locked Play region
2026 SetPlayRegion(mOldPlayRegion.GetStart(), mOldPlayRegion.GetEnd());
2027 SelectUtilities::ActivatePlayRegion(*mProject);
2028 // and release local lock
2029 mOldPlayRegion.SetActive( false );
2030 }
2031 } );
2032
2033 StartQPPlay(!evt.ShiftDown(), evt.ControlDown());
2034}
2035
2036auto AdornedRulerPanel::QPHandle::Cancel(AudacityProject *pProject) -> Result
2037{
2038 auto result = CommonRulerHandle::Cancel(pProject);
2039
2040 if (mClicked == Button::Left) {
2041 if( mParent ) {
2042 ViewInfo::Get( *pProject ).selectedRegion = mOldSelection;
2043 mParent->mMouseEventState = mesNone;
2044 mParent->SetPlayRegion(
2045 mParent->mOldPlayRegion.GetStart(), mParent->mOldPlayRegion.GetEnd());
2046 if (mParent->mOldPlayRegion.Active()) {
2047 // Restore Locked Play region
2049 // and release local lock
2050 mParent->mOldPlayRegion.SetActive( false );
2051 }
2052 }
2053 }
2054
2055 return result;
2056}
2057#endif
2058
2060 bool newDefault, bool cutPreview, const double *pStartTime)
2061{
2062 const double t0 = mTracks->GetStartTime();
2063 const double t1 = mTracks->GetEndTime();
2064 auto &viewInfo = ViewInfo::Get( *mProject );
2065 const auto &playRegion = viewInfo.playRegion;
2066 const auto &selectedRegion = viewInfo.selectedRegion;
2067 const double sel0 = selectedRegion.t0();
2068 const double sel1 = selectedRegion.t1();
2069
2070 // Start / Restart playback on left click.
2071 bool startPlaying = true; // = (playRegion.GetStart() >= 0);
2072
2073 if (startPlaying) {
2074 bool loopEnabled = true;
2075 auto oldStart = std::max(0.0, playRegion.GetStart());
2076 double start = oldStart, end = 0;
2077
2078 if (playRegion.Empty()) {
2079 // Play either a selection or the project.
2080 if (oldStart > sel0 && oldStart < sel1) {
2081 // we are in a selection, so use the selection
2082 start = sel0;
2083 end = sel1;
2084 } // not in a selection, so use the project
2085 else {
2086 start = t0;
2087 end = t1;
2088 }
2089 }
2090 else
2091 end = std::max(start, playRegion.GetEnd());
2092
2093 // Looping a tiny selection may freeze, so just play it once.
2094 loopEnabled = ((end - start) > 0.001)? true : false;
2095
2096 newDefault = (loopEnabled && newDefault);
2097 if (newDefault)
2098 cutPreview = false;
2099 auto options = ProjectAudioIO::GetDefaultOptions(*mProject, newDefault);
2100
2101 if (!cutPreview) {
2102 if (pStartTime)
2103 options.pStartTime.emplace(*pStartTime);
2104 }
2105 else
2106 options.envelope = nullptr;
2107
2108 auto mode =
2109 cutPreview ? PlayMode::cutPreviewPlay
2110 : newDefault ? PlayMode::loopedPlay
2112
2113 // Stop only after deciding where to start again, because an event
2114 // callback may change the play region back to the selection
2115 auto &projectAudioManager = ProjectAudioManager::Get( *mProject );
2116 projectAudioManager.Stop();
2117
2118 // Don't change play region, assume caller set it as needed
2119 // playRegion.SetTimes( start, end );
2120 // Refresh();
2121
2122 projectAudioManager.PlayPlayRegion((SelectedRegion(start, end)),
2123 options, mode,
2124 false);
2125
2126 }
2127}
2128
2129#if 0
2130// This version toggles ruler state indirectly via the scrubber
2131// to ensure that all the places where the state is shown update.
2132// For example buttons and menus must update.
2133void AdornedRulerPanel::OnToggleScrubRulerFromMenu(wxCommandEvent&)
2134{
2135 auto &scrubber = Scrubber::Get( *mProject );
2136 scrubber.OnToggleScrubRuler(*mProject);
2137}
2138#endif
2139
2140
2142{
2143 const auto oldSize = GetSize();
2144 wxSize size { oldSize.GetWidth(), GetRulerHeight(ShowingScrubRuler()) };
2145 if ( size != oldSize ) {
2146 SetSize(size);
2147 SetMinSize(size);
2148 PostSizeEventToParent();
2149 return true;
2150 }
2151 else
2152 return false;
2153}
2154
2156{
2157 auto pCellularPanel =
2158 dynamic_cast<CellularPanel*>( &GetProjectPanel( *GetProject() ) );
2159 if ( !pCellularPanel ) {
2160 wxASSERT( false );
2161 }
2162 else
2163 pCellularPanel->DrawOverlays( false );
2164 DrawOverlays( false );
2165}
2166
2168{
2169 auto common = [this](
2170 AButton &button, const CommandID &commandName, const TranslatableString &label) {
2171 ComponentInterfaceSymbol command{ commandName, label };
2172 ToolBar::SetButtonToolTip( *mProject, button, &command, 1u );
2173 button.SetLabel( Verbatim( button.GetToolTipText() ) );
2174
2175 button.UpdateStatus();
2176 };
2177
2178 {
2179 auto timelineOptionsButton = static_cast<AButton*>(FindWindow(OnTogglePinnedStateID));
2180 timelineOptionsButton->PopUp();
2181 // Bug 1584: Tooltip now shows what clicking will do.
2182 // Bug 2357: Action of button (and hence tooltip wording) updated.
2183 const auto label = XO("Timeline Options");
2184 common(*timelineOptionsButton, wxT("PinnedHead"), label);
2185 }
2186}
2187
2188void AdornedRulerPanel::OnPinnedButton(wxCommandEvent & /*event*/)
2189{
2191}
2192
2193void AdornedRulerPanel::OnTogglePinnedState(wxCommandEvent & /*event*/)
2194{
2197}
2198
2200{
2201 // Invoked for mouse-over preview events, or dragging, or scrub position
2202 // polling updates. Remember x coordinates, converted to times, for
2203 // drawing of guides.
2204
2205 // Keep Quick-Play within usable track area. (Dependent on zoom)
2206 const auto &viewInfo = ViewInfo::Get( *mProject );
2207 auto width = viewInfo.GetTracksUsableWidth();
2208 mousePosX = std::max(mousePosX, viewInfo.GetLeftOffset());
2209 mousePosX = std::min(mousePosX, viewInfo.GetLeftOffset() + width - 1);
2210 const auto time = Pos2Time(mousePosX);
2211
2212 for (size_t ii = 0; ii < mNumGuides; ++ii) {
2214 time + mQuickPlayOffset[ii];
2215 HandleSnapping(ii);
2216 }
2217}
2218
2219// Pop-up menus
2220
2221void AdornedRulerPanel::ShowMenu(const wxPoint & pos)
2222{
2223 const auto &viewInfo = ViewInfo::Get( *GetProject() );
2224 const auto &playRegion = viewInfo.playRegion;
2225 wxMenu rulerMenu;
2226
2227 {
2228 auto item = rulerMenu.AppendRadioItem(OnMinutesAndSecondsID,
2229 _("Minutes and Seconds"));
2231 }
2232
2233 {
2234 auto item = rulerMenu.AppendRadioItem(OnBeatsAndMeasuresID,
2235 _("Beats and Measures"));
2237 }
2238
2239 rulerMenu.AppendSeparator();
2240
2241 auto pDrag = rulerMenu.AppendCheckItem(OnSyncQuickPlaySelID, _("Setting a loop region also makes an audio selection"));
2242 pDrag->Check(mPlayRegionDragsSelection && playRegion.Active());
2243 pDrag->Enable(playRegion.Active());
2244
2245 {
2246 auto item = rulerMenu.AppendCheckItem(OnTogglePlayRegionID,
2248 item->Check(playRegion.Active());
2249 }
2250
2251 {
2252 auto item = rulerMenu.Append(OnClearPlayRegionID,
2253 /* i18n-hint Clear is a verb */
2254 _("Clear Loop"));
2255 }
2256
2257 {
2258 auto item = rulerMenu.Append(OnSetPlayRegionToSelectionID,
2259 _("Set Loop To Selection"));
2260 }
2261
2262 rulerMenu.AppendSeparator();
2263
2264 rulerMenu.AppendCheckItem(OnAutoScrollID, _("Scroll view to playhead"))->
2266
2267 rulerMenu.AppendCheckItem(OnTogglePinnedStateID, _("Continuous scrolling"))->
2269
2270 BasicMenu::Handle{ &rulerMenu }.Popup(
2272 { pos.x, pos.y }
2273 );
2274}
2275
2276void AdornedRulerPanel::ShowScrubMenu(const wxPoint & pos)
2277{
2278 auto &scrubber = Scrubber::Get( *mProject );
2279 PushEventHandler(&scrubber);
2280 auto cleanup = finally([this]{ PopEventHandler(); });
2281
2282 wxMenu rulerMenu;
2283 scrubber.PopulatePopupMenu(rulerMenu);
2284 BasicMenu::Handle{ &rulerMenu }.Popup(
2286 { pos.x, pos.y }
2287 );
2288}
2289
2291{
2292 auto &viewInfo = ViewInfo::Get( project );
2293 const auto &playRegion = viewInfo.playRegion;
2294 auto &selectedRegion = viewInfo.selectedRegion;
2295 selectedRegion.setT0(playRegion.GetStart(), false);
2296 selectedRegion.setT1(playRegion.GetEnd(), true);
2297}
2298
2300{
2301 // Play region dragging can snap to selection boundaries
2302 const auto &selectedRegion = ViewInfo::Get(*GetProject()).selectedRegion;
2303 SnapPointArray candidates;
2305 candidates = {
2306 SnapPoint{ selectedRegion.t0() },
2307 SnapPoint{ selectedRegion.t1() },
2308 };
2309 SnapManager snapManager{ *mProject, *mTracks, *mViewInfo, move(candidates) };
2310 auto results = snapManager.Snap(nullptr, mQuickPlayPos[index], false);
2311 mQuickPlayPos[index] = results.outTime;
2312 mIsSnapped[index] = results.Snapped();
2313}
2314
2316{
2317 int id = event.GetId();
2318 TimeDisplayMode changeFlag = mTimeDisplayMode;
2319 wxASSERT(id == OnMinutesAndSecondsID || id == OnBeatsAndMeasuresID);
2322
2324
2325 if (changeFlag != mTimeDisplayMode)
2326 Refresh();
2327}
2328
2330{
2332 gPrefs->Write(wxT("/QuickPlay/DragSelection"), mPlayRegionDragsSelection);
2333 gPrefs->Flush();
2334}
2335
2337{
2339 gPrefs->Write(wxT("/GUI/AutoScroll"), false);
2340 else
2341 gPrefs->Write(wxT("/GUI/AutoScroll"), true);
2342
2343 gPrefs->Flush();
2344
2346}
2347
2348
2350{
2352}
2353
2355{
2357}
2358
2360{
2362}
2363
2364
2366 MenuChoice choice, const wxPoint *pPosition)
2367{
2368 wxPoint position;
2369 if(pPosition)
2370 position = *pPosition;
2371 else
2372 {
2373 auto rect = GetRect();
2374 //Old code put menu too low down. y position applied twice.
2375 //position = { rect.GetLeft() + 1, rect.GetBottom() + 1 };
2376
2377 // The cell does not pass in the mouse or button position.
2378 // We happen to know this is the pin/unpin button
2379 // so these magic values 'fix a bug' - but really the cell should
2380 // pass more information to work with in.
2381 position = { rect.GetLeft() + 38, rect.GetHeight()/2 + 1 };
2382 }
2383
2384 switch (choice) {
2386 ShowMenu(position);
2388 break;
2389 case MenuChoice::Scrub:
2390 ShowScrubMenu(position); break;
2391 default:
2392 return;
2393 }
2394}
2395
2396using ColorId = decltype(clrTrackInfo);
2397
2399{
2400 return clrTrackInfo;
2401}
2402
2404{
2405 return clrTrackPanelText;
2406}
2407
2409{
2410 return TimelineTextColor();
2411}
2412
2414{
2415 return isActive ? clrRulerBackground : clrClipAffordanceInactiveBrush;
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( mInner );
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 wxRect r = mOuter;
2448 r.width -= RightMargin;
2449 r.height -= BottomMargin;
2450 AColor::BevelTrackInfo( *dc, true, r );
2451
2452 // Black stroke at bottom
2453 dc->SetPen( *wxBLACK_PEN );
2454 AColor::Line( *dc, mOuter.x,
2455 mOuter.y + mOuter.height - 1,
2456 mOuter.x + mOuter.width - 1 ,
2457 mOuter.y + mOuter.height - 1 );
2458}
2459
2460void AdornedRulerPanel::DoDrawMarks(wxDC * dc, bool /*text */ )
2461{
2462 const double min = Pos2Time(0);
2463 const double hiddenMin = Pos2Time(0, true);
2464 const double max = Pos2Time(mInner.width);
2465 const double hiddenMax = Pos2Time(mInner.width, true);
2466
2468 mRuler.SetRange( min, max, hiddenMin, hiddenMax );
2470 {
2471 mRuler.SetTickLengths({ 5, 3, 1 });
2472 }
2474 {
2475 mRuler.SetTickLengths({ 4, 2, 2 });
2476 }
2477 mRuler.Draw( *dc );
2478}
2479
2481{
2482 Refresh();
2483}
2484
2486{
2487 const auto &viewInfo = ViewInfo::Get(*mProject);
2488 const auto &playRegion = viewInfo.playRegion;
2489 const auto t0 = playRegion.GetLastActiveStart(),
2490 t1 = playRegion.GetLastActiveEnd();
2491 return RegionRectangle(t0, t1);
2492}
2493
2495{
2496 const auto &viewInfo = ViewInfo::Get(*mProject);
2497 const auto &selectedRegion = viewInfo.selectedRegion;
2498 const auto t0 = selectedRegion.t0(), t1 = selectedRegion.t1();
2499 return RegionRectangle(t0, t1);
2500}
2501
2502wxRect AdornedRulerPanel::RegionRectangle(double t0, double t1) const
2503{
2504 int p0 = -1, p1 = -1;
2505 if (t0 == t1)
2506 // Make the rectangle off-screen horizontally, but set the height
2507 ;
2508 else {
2509 p0 = max(1, Time2Pos(t0));
2510 p1 = min(mInner.width, Time2Pos(t1));
2511 }
2512
2513 const int left = p0, top = mInner.y, right = p1, bottom = mInner.GetBottom();
2514 return { wxPoint{left, top}, wxPoint{right, bottom} };
2515}
2516
2518 wxDC * dc, const wxRect &rectP, const wxRect &rectL, const wxRect &rectR)
2519{
2520 const auto &viewInfo = ViewInfo::Get(*mProject);
2521 const auto& playRegion = viewInfo.playRegion;
2522
2523 const bool isActive = (mLastPlayRegionActive = playRegion.Active());
2524
2525 if (playRegion.IsLastActiveRegionClear())
2526 return;
2527
2528 // Paint the selected region bolder if independently varying, else dim
2529 const auto color = TimelineLoopRegionColor(isActive);
2530 dc->SetBrush( wxBrush( theTheme.Colour( color )) );
2531 dc->SetPen( wxPen( theTheme.Colour( color )) );
2532
2533 dc->DrawRectangle( rectP.Intersect(rectL) );
2534 dc->DrawRectangle( rectP.Intersect(rectR) );
2535}
2536
2537void AdornedRulerPanel::DoDrawPlayRegionLimits(wxDC * dc, const wxRect &rect)
2538{
2539 // Color the edges of the play region like the ticks and numbers
2540 ADCChanger cleanup( dc );
2541 const auto edgeColour = theTheme.Colour(TimelineLimitsColor());
2542 dc->SetPen( { edgeColour } );
2543 dc->SetBrush( { edgeColour } );
2544
2545 constexpr int side = 7;
2546 constexpr int sideLessOne = side - 1;
2547
2548 // Paint two shapes, each a line plus triangle at bottom
2549 const auto left = rect.GetLeft(),
2550 right = rect.GetRight(),
2551 bottom = rect.GetBottom(),
2552 top = rect.GetTop();
2553 {
2554 wxPoint points[]{
2555 {left, bottom - sideLessOne},
2556 {left - sideLessOne, bottom},
2557 {left, bottom},
2558 {left, top},
2559 };
2560 dc->DrawPolygon( 4, points );
2561 }
2562
2563 {
2564 wxPoint points[]{
2565 {right, top},
2566 {right, bottom},
2567 {right + sideLessOne, bottom},
2568 {right, bottom - sideLessOne},
2569 };
2570 dc->DrawPolygon( 4, points );
2571 }
2572}
2573
2574constexpr double SelectionOpacity = 0.2;
2575
2576void AdornedRulerPanel::DoDrawOverlap(wxDC * dc, const wxRect &rect)
2577{
2578 dc->SetBrush( wxBrush{ AlphaBlend(
2580 SelectionOpacity) } );
2581 dc->SetPen( *wxTRANSPARENT_PEN );
2582 dc->DrawRectangle( rect );
2583}
2584
2586 wxDC * dc, const wxRect &rectS, const wxRect &rectL, const wxRect &rectR)
2587{
2588 dc->SetBrush( wxBrush{ AlphaBlend(
2590 dc->SetPen( *wxTRANSPARENT_PEN );
2591 dc->DrawRectangle( rectS.Intersect(rectL) );
2592 dc->DrawRectangle( rectS.Intersect(rectR) );
2593}
2594
2596{
2597 return ProperRulerHeight + (showScrubBar ? ScrubHeight : 0);
2598}
2599
2601{
2602 if (mLeftOffset != offset) {
2603 mLeftOffset = offset;
2604 mUpdater.SetData(mViewInfo, offset);
2606 }
2607}
2608
2609// Draws the scrubbing/seeking indicator.
2611 wxDC * dc, wxCoord xx, int width, bool scrub, bool seek)
2612{
2613 ADCChanger changer(dc); // Undo pen and brush changes at function exit
2614
2615 wxPoint tri[ 3 ];
2616 if (seek) {
2617 auto height = IndicatorHeightForWidth(width);
2618 // Make four triangles
2619 const int TriangleWidth = width * 3 / 8;
2620
2621 // Double-double headed, left-right
2622 auto yy = ShowingScrubRuler()
2623 ? mScrubZone.y
2624 : (mInner.GetBottom() + 1) - 1 /* bevel */ - height;
2625 tri[ 0 ].x = xx - IndicatorOffset;
2626 tri[ 0 ].y = yy;
2627 tri[ 1 ].x = xx - IndicatorOffset;
2628 tri[ 1 ].y = yy + height;
2629 tri[ 2 ].x = xx - TriangleWidth;
2630 tri[ 2 ].y = yy + height / 2;
2631 dc->DrawPolygon( 3, tri );
2632
2633 tri[ 0 ].x -= TriangleWidth;
2634 tri[ 1 ].x -= TriangleWidth;
2635 tri[ 2 ].x -= TriangleWidth;
2636 dc->DrawPolygon( 3, tri );
2637
2638 tri[ 0 ].x = tri[ 1 ].x = xx + IndicatorOffset;
2639 tri[ 2 ].x = xx + TriangleWidth;
2640 dc->DrawPolygon( 3, tri );
2641
2642
2643 tri[ 0 ].x += TriangleWidth;
2644 tri[ 1 ].x += TriangleWidth;
2645 tri[ 2 ].x += TriangleWidth;
2646 dc->DrawPolygon( 3, tri );
2647 }
2648 else if (scrub) {
2649 auto height = IndicatorHeightForWidth(width);
2650 const int IndicatorHalfWidth = width / 2;
2651
2652 // Double headed, left-right
2653 auto yy = ShowingScrubRuler()
2654 ? mScrubZone.y
2655 : (mInner.GetBottom() + 1) - 1 /* bevel */ - height;
2656 tri[ 0 ].x = xx - IndicatorOffset;
2657 tri[ 0 ].y = yy;
2658 tri[ 1 ].x = xx - IndicatorOffset;
2659 tri[ 1 ].y = yy + height;
2660 tri[ 2 ].x = xx - IndicatorHalfWidth;
2661 tri[ 2 ].y = yy + height / 2;
2662 dc->DrawPolygon( 3, tri );
2663 tri[ 0 ].x = tri[ 1 ].x = xx + IndicatorOffset;
2664 tri[ 2 ].x = xx + IndicatorHalfWidth;
2665 dc->DrawPolygon( 3, tri );
2666 }
2667}
2668
2670 double playRegionStart, double playRegionEnd)
2671{
2672 // This is called by AudacityProject to make the play region follow
2673 // the current selection. But while the user is selecting a play region
2674 // with the mouse directly in the ruler, changes from outside are blocked.
2676 return;
2677
2678 auto &viewInfo = ViewInfo::Get( *GetProject() );
2679 auto &playRegion = viewInfo.playRegion;
2680 playRegion.SetTimes( playRegionStart, playRegionEnd );
2681
2682 Refresh();
2683}
2684
2686{
2687 ProjectAudioManager::Get( *mProject ).Stop();
2688
2689 auto &viewInfo = ViewInfo::Get( *GetProject() );
2690 auto &playRegion = viewInfo.playRegion;
2691 playRegion.Clear();
2692
2693 Refresh();
2694}
2695
2696void AdornedRulerPanel::GetMaxSize(wxCoord *width, wxCoord *height)
2697{
2698 mRuler.GetMaxSize(width, height);
2699}
2700
2702
2704 s_AcceptsFocus = true;
2705 return TempAllowFocus{ &s_AcceptsFocus };
2706}
2707
2709{
2710 nn = std::min(nn, MAX_GUIDES);
2711 // If increasing the number of guides, reinitialize newer ones
2712 for (size_t ii = mNumGuides; ii < nn; ++ii) {
2713 mQuickPlayOffset[ii] = 0;
2714 mQuickPlayPosUnsnapped[ii] = 0;
2715 mQuickPlayPos[ii] = 0;
2716 mIsSnapped[ii] = false;
2717 }
2718 mNumGuides = nn;
2719}
2720
2722{
2723 auto temp = TemporarilyAllowFocus();
2724 SetFocus();
2725}
2726
2727// Second-level subdivision includes quick-play region and maybe the scrub bar
2728// and also shaves little margins above and below
2730 explicit Subgroup( const AdornedRulerPanel &ruler ) : mRuler{ ruler } {}
2731 Subdivision Children( const wxRect & ) override
2732 {
2733 return { Axis::Y, ( mRuler.ShowingScrubRuler() )
2734 ? Refinement{
2735 { mRuler.mInner.GetTop(), mRuler.mQPCell },
2736 { mRuler.mScrubZone.GetTop(), mRuler.mScrubbingCell },
2737 { mRuler.mScrubZone.GetBottom() + 1, nullptr }
2738 }
2739 : Refinement{
2740 { mRuler.mInner.GetTop(), mRuler.mQPCell },
2741 { mRuler.mInner.GetBottom() + 1, nullptr }
2742 }
2743 };
2744 }
2746};
2747
2748// Top-level subdivision shaves little margins off left and right
2750 explicit MainGroup( const AdornedRulerPanel &ruler ) : mRuler{ ruler } {}
2751 Subdivision Children( const wxRect & ) override
2752 { return { Axis::X, Refinement{
2753 // Subgroup is a throwaway object
2754 { mRuler.mInner.GetLeft(), std::make_shared< Subgroup >( mRuler ) },
2755 { mRuler.mInner.GetRight() + 1, nullptr }
2756 } }; }
2758};
2759
2761{
2762 auto &scrubber = Scrubber::Get( *GetProject() );
2763 return scrubber.ShowsBar();
2764}
2765
2766// CellularPanel implementation
2767std::shared_ptr<TrackPanelNode> AdornedRulerPanel::Root()
2768{
2769 // Root is a throwaway object
2770 return std::make_shared< MainGroup >( *this );
2771}
2772
2774{
2775 return mProject;
2776}
2777
2778std::shared_ptr<TrackPanelCell> AdornedRulerPanel::GetFocusedCell()
2779{
2780 // No switching of focus yet to the other, scrub zone
2781 return mQPCell;
2782}
2783
2785{
2786}
2787
2789 TrackPanelCell *, TrackPanelCell *, unsigned refreshResult)
2790{
2791 if (refreshResult & RefreshCode::RefreshAll)
2792 Refresh(); // Overlays will be repainted too
2793 else if (refreshResult & RefreshCode::DrawOverlays)
2794 DrawBothOverlays(); // cheaper redrawing of guidelines only
2795}
2796
2798{
2799 ProjectStatus::Get( *GetProject() ).Set(message);
2800}
2801
2803{
2804 if (!mOverlay) {
2805 mOverlay =
2806 std::make_shared<TrackPanelGuidelineOverlay>( mProject );
2807 auto pCellularPanel =
2808 dynamic_cast<CellularPanel*>( &GetProjectPanel( *GetProject() ) );
2809 if ( !pCellularPanel ) {
2810 wxASSERT( false );
2811 }
2812 else
2813 pCellularPanel->AddOverlay( mOverlay );
2814 this->AddOverlay( mOverlay->mPartner );
2815 }
2816}
2817
2819{
2823
2824 auto &project = *mProject;
2825 // Update button image
2827
2828 auto &scrubber = Scrubber::Get( project );
2829 if (scrubber.HasMark())
2830 scrubber.SetScrollScrubbing(value);
2831}
2832
2834{
2835 return mTimeDisplayMode;
2836}
2837
2839{
2840 if (mTimeDisplayMode == type)
2841 return;
2842
2843 mTimeDisplayMode = type;
2845 Refresh();
2846}
2847
2848// Attach menu item
2849
2850#include "CommandContext.h"
2851#include "CommonCommandFlags.h"
2852
2853namespace {
2855{
2857}
2858
2859using namespace MenuRegistry;
2861 Command( wxT("PinnedHead"), XXO("Continuous scrolling"),
2863 // Switching of scrolling on and off is permitted
2864 // even during transport
2866 Options{}.CheckTest([](const AudacityProject&){
2868 { wxT("Transport/Other/Options/Part2"), { OrderingHint::Begin, {} } }
2869};
2870}
EVT_MENU(OnSetPlayRegionToSelectionID, AdornedRulerPanel::OnSetPlayRegionToSelection) EVT_COMMAND(OnTogglePinnedStateID
ColorId TimelineLimitsColor()
decltype(clrTrackInfo) ColorId
ColorId TimelineLoopRegionColor(bool isActive)
@ IndicatorOffset
@ IndicatorSmallWidth
@ LeftMargin
@ RightMargin
@ TopMargin
@ IndicatorMediumWidth
@ BottomMargin
static wxColour AlphaBlend(ColorId fg, ColorId bg, double alpha)
int IndicatorBigHeight()
static 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:455
void PopUp()
Definition: AButton.cpp:585
void SetLabel(const TranslatableString &label)
Definition: AButton.cpp:189
static void IndicatorColor(wxDC *dc, bool bIsNotRecording)
Definition: AColor.cpp:458
static void Line(wxDC &dc, wxCoord x1, wxCoord y1, wxCoord x2, wxCoord y2)
Definition: AColor.cpp:185
static void SnapGuidePen(wxDC *dc)
Definition: AColor.cpp:474
static void Light(wxDC *dc, bool selected, bool highlight=false)
Definition: AColor.cpp:407
static void BevelTrackInfo(wxDC &dc, bool up, const wxRect &r, bool highlight=false)
Definition: AColor.cpp:338
static void UseThemeColour(wxDC *dc, int iBrush, int iPen=-1, int alpha=255)
Definition: AColor.cpp:366
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
The widget to the left of a ToolBar that allows it to be dragged around to NEW positions.
Definition: Grabber.h:107
void SetAsSpacer(bool bIsSpacer)
Definition: Grabber.cpp:99
void SetData(const ZoomInfo *pZoomInfo=nullptr, int leftOffset=0)
Definition: LinearUpdater.h:33
Subscription Subscribe(Callback callback)
Connect a callback to the Publisher; later-connected are called earlier.
Definition: Observer.h:199
void DrawOverlays(bool repaint_all, wxDC *pDC=nullptr)
void AddOverlay(const std::weak_ptr< Overlay > &pOverlay)
double GetStart() const
Definition: ViewInfo.h:128
void SetStart(double start)
Definition: ViewInfo.cpp:161
double GetEnd() const
Definition: ViewInfo.h:135
bool Empty() const
Definition: ViewInfo.h:127
void SetEnd(double end)
Definition: ViewInfo.cpp:171
bool Active() const
Definition: ViewInfo.h:124
void SetTimes(double start, double end)
Definition: ViewInfo.cpp:181
static void Broadcast(int id=0)
Call this static function to notify all PrefsListener objects.
Definition: Prefs.cpp: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:875
static void MakeButtonBackgroundsSmall()
Definition: ToolBar.cpp:837
static void SetButtonToolTip(AudacityProject &project, AButton &button, const ComponentInterfaceSymbol commands[], size_t nCommands)
Definition: ToolBar.cpp:970
static void ModifyAllProjectToolbarMenus()
double GetEndTime() const
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:226
PlayRegion playRegion
Definition: ViewInfo.h:216
NotifyingSelectedRegion selectedRegion
Definition: ViewInfo.h:215
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:384
void CallAfter(Action action)
Schedule an action to be done later, and in the main thread.
Definition: BasicUI.cpp:213
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:66
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.