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 Channel> FindChannel() 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 Channel> FindChannel() 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#ifdef EXPERIMENTAL_DRAGGABLE_PLAY_HEAD
1069 {
1070 // Allow click and drag on the play head even while recording
1071 // Make this handle more prominent then the quick play handle
1072 auto result = PlayheadHandle::HitTest( pProject, *mParent, xx );
1073 if (result) {
1074 result = AssignUIHandlePtr( mPlayheadHolder, result );
1075 results.push_back( result );
1076 }
1077 }
1078#endif
1079
1080 // Disable mouse actions on Timeline while recording.
1081 if (!mParent->mIsRecording) {
1082 mParent->UpdateQuickPlayPos( xx );
1083
1084 #if 0
1085 auto result = std::make_shared<QPHandle>( mParent, xx );
1086 result = AssignUIHandlePtr( mHolder, result );
1087 results.push_back( result );
1088 #endif
1089 }
1090
1091 // High priority hit is a handle to change the existing play region
1092 bool hitLeft = false;
1093 const auto &playRegion = ViewInfo::Get(*pProject).playRegion;
1094 if ((hitLeft =
1095 mParent->IsWithinMarker(xx, playRegion.GetLastActiveStart())) ||
1096 mParent->IsWithinMarker(xx, playRegion.GetLastActiveEnd()))
1097 {
1098 auto result =
1099 std::make_shared<ResizePlayRegionHandle>( mParent, xx, hitLeft );
1100 result = AssignUIHandlePtr( mResizePlayRegionHolder, result );
1101 results.push_back(result);
1102 }
1103
1104 // Middle priority hit is a handle to change the existing play region at
1105 // both ends, but only when the play region is active
1106 if (auto time = mParent->Pos2Time(xx);
1107 playRegion.Active() &&
1108 time >= playRegion.GetStart() &&
1109 time <= playRegion.GetEnd())
1110 {
1111 auto result =
1112 std::make_shared<MovePlayRegionHandle>( mParent, xx );
1113 result = AssignUIHandlePtr( mMovePlayRegionHolder, result );
1114 results.push_back(result);
1115 }
1116
1117 // Lowest priority hit is a handle to drag a completely new play region
1118 {
1119 auto result = std::make_shared<NewPlayRegionHandle>( mParent, xx );
1120 result = AssignUIHandlePtr( mNewPlayRegionHolder, result );
1121 results.push_back(result);
1122 }
1123
1124 return results;
1125}
1126
1128{
1129public:
1130 explicit
1131 ScrubbingHandle( AdornedRulerPanel *pParent, wxCoord xx )
1132 : CommonRulerHandle( pParent, xx, MenuChoice::Scrub )
1133 {
1134 }
1135
1136private:
1138 const TrackPanelMouseEvent &event, AudacityProject *pProject) override
1139 {
1140 auto result = CommonRulerHandle::Click(event, pProject);
1141 if (!( result & RefreshCode::Cancelled )) {
1142 if (mClicked == Button::Left) {
1143 auto &scrubber = Scrubber::Get( *pProject );
1144 // only if scrubbing is allowed now
1145 bool canScrub =
1146 scrubber.CanScrub() &&
1147 mParent &&
1148 mParent->ShowingScrubRuler();
1149
1150 if (!canScrub)
1152 if (!scrubber.HasMark()) {
1153 // Asynchronous scrub poller gets activated here
1154 scrubber.MarkScrubStart(
1155 event.event.m_x, Scrubber::ShouldScrubPinned(), false);
1156 }
1157 }
1158 }
1159 return result;
1160 }
1161
1163 const TrackPanelMouseEvent &event, AudacityProject *pProject) override
1164 {
1165 auto result = CommonRulerHandle::Drag(event, pProject);
1166 if (!( result & RefreshCode::Cancelled )) {
1167 // Nothing needed here. The scrubber works by polling mouse state
1168 // after the start has been marked.
1169 }
1170 return result;
1171 }
1172
1174 const TrackPanelMouseState &state, AudacityProject *pProject)
1175 override;
1176
1178 const TrackPanelMouseEvent &event, AudacityProject *pProject,
1179 wxWindow *pParent) override {
1180 auto result = CommonRulerHandle::Release(event, pProject, pParent);
1181 if (!( result & RefreshCode::Cancelled )) {
1182 // Nothing needed here either. The scrub poller may have decided to
1183 // seek because a drag happened before button up, or it may decide
1184 // to start a scrub, as it watches mouse movement after the button up.
1185 }
1186 return result;
1187 }
1188
1189 Result Cancel(AudacityProject *pProject) override
1190 {
1191 auto result = CommonRulerHandle::Cancel(pProject);
1192
1193 if (mClicked == Button::Left) {
1194 auto &scrubber = Scrubber::Get( *pProject );
1195 scrubber.Cancel();
1196
1197 ProjectAudioManager::Get( *pProject ).Stop();
1198 }
1199
1200 return result;
1201 }
1202};
1203
1205{
1206public:
1207 explicit
1209 : AdornedRulerPanel::CommonCell{ parent, MenuChoice::Scrub }
1210 {}
1211
1212 std::vector<UIHandlePtr> HitTest(
1213 const TrackPanelMouseState &state,
1214 const AudacityProject *pProject) override;
1215
1216 // Return shared_ptr to self, stored in parent
1217 std::shared_ptr<TrackPanelCell> ContextMenuDelegate() override
1218 { return mParent->mScrubbingCell; }
1219
1220 bool Hit() const { return !mHolder.expired(); }
1221 bool Clicked() const {
1222 if (auto ptr = mHolder.lock())
1223 return ptr->Clicked();
1224 return false;
1225 }
1226
1227private:
1228 std::weak_ptr<ScrubbingHandle> mHolder;
1229};
1230
1232 const TrackPanelMouseState &state, const AudacityProject *)
1233{
1234 // Creation of overlays on demand here -- constructor of AdornedRulerPanel
1235 // is too early to do it
1236 mParent->CreateOverlays();
1237
1238 std::vector<UIHandlePtr> results;
1239
1240 // Disable mouse actions on Timeline while recording.
1241 if (!mParent->mIsRecording) {
1242 auto xx = state.state.m_x;
1243 mParent->UpdateQuickPlayPos( xx );
1244 auto result = std::make_shared<ScrubbingHandle>( mParent, xx );
1245 result = AssignUIHandlePtr( mHolder, result );
1246 results.push_back( result );
1247 }
1248
1249 return results;
1250}
1251
1252namespace{
1253AttachedWindows::RegisteredFactory sKey{
1254[]( AudacityProject &project ) -> wxWeakRef< wxWindow > {
1255 auto &viewInfo = ViewInfo::Get( project );
1256 auto &window = ProjectWindow::Get( project );
1257
1258 return safenew AdornedRulerPanel( &project, window.GetTrackListWindow(),
1259 wxID_ANY,
1260 wxDefaultPosition,
1261 wxSize( -1, AdornedRulerPanel::GetRulerHeight(false) ),
1262 &viewInfo );
1263}
1264};
1265}
1266
1268{
1270}
1271
1273 const AudacityProject &project )
1274{
1275 return Get( const_cast< AudacityProject & >( project ) );
1276}
1277
1279{
1280 auto *pPanel = GetAttachedWindows(project).Find( sKey );
1281 if (pPanel) {
1282 pPanel->wxWindow::Destroy();
1284 }
1285}
1286
1288 wxWindow *parent,
1289 wxWindowID id,
1290 const wxPoint& pos,
1291 const wxSize& size,
1292 ViewInfo *viewinfo
1293) : CellularPanel(parent, id, pos, size, viewinfo)
1294 , mProject { project }
1295 , mUpdater { ProjectTimeRuler::Get(*project).GetUpdater() }
1296 , mRuler { ProjectTimeRuler::Get(*project).GetRuler() }
1297{
1298 SetLayoutDirection(wxLayout_LeftToRight);
1299
1300 mQPCell = std::make_shared<QPCell>( this );
1301 mScrubbingCell = std::make_shared<ScrubbingCell>( this );
1302
1303 for (auto &button : mButtons)
1304 button = nullptr;
1305
1306 SetLabel( XO("Timeline") );
1307 SetName();
1308 SetBackgroundStyle(wxBG_STYLE_PAINT);
1309
1310 mLeftOffset = 0;
1311 mIndTime = -1;
1312
1313 mLeftDownClick = -1;
1315 mIsDragging = false;
1316
1317 mOuter = GetClientRect();
1318
1320
1322
1323 mRuler.SetLabelEdges( false );
1324
1326
1327 mIsRecording = false;
1328
1329 mPlayRegionDragsSelection = (gPrefs->Read(wxT("/QuickPlay/DragSelection"), 0L) == 1)? true : false;
1330
1331#if wxUSE_TOOLTIPS
1332 wxToolTip::Enable(true);
1333#endif
1334
1337
1338 // Delay until after CommandManager has been populated:
1340
1343
1344 // Bind event that updates the play region
1347
1349 mRuler.Subscribe([this](auto) { Refresh(); });
1350
1351 // And call it once to initialize it
1353}
1354
1356{
1357}
1358
1359void AdornedRulerPanel::Refresh( bool eraseBackground, const wxRect *rect )
1360{
1361 CellularPanel::Refresh( eraseBackground, rect );
1363}
1364
1366{
1367 if (mNeedButtonUpdate) {
1368 // Visit this block once only in the lifetime of this panel
1369 mNeedButtonUpdate = false;
1370 // Do this first time setting of button status texts
1371 // when we are sure the CommandManager is initialized.
1373 }
1374
1375 // Update button texts for language change
1377
1379 Refresh();
1380 // Update();
1381}
1382
1384{
1385 // TODO: Should we do this to destroy the grabber??
1386 // Get rid of any children we may have
1387 // DestroyChildren();
1388
1390 SetBackgroundColour(theTheme.Colour( clrMedium ));
1391
1392 for (auto & button : mButtons) {
1393 if (button)
1394 button->Destroy();
1395 button = nullptr;
1396 }
1397
1398 size_t iButton = 0;
1399 // Make the short row of time ruler push buttons.
1400 // Don't bother with sizers. Their sizes and positions are fixed.
1401 // Add a grabber converted to a spacer.
1402 // This makes it visually clearer that the button is a button.
1403
1404 wxPoint position( 1, 0 );
1405
1406 Grabber * pGrabber = safenew Grabber(this, {});
1407 pGrabber->SetAsSpacer( true );
1408 //pGrabber->SetSize( 10, 27 ); // default is 10,27
1409 pGrabber->SetPosition( position );
1410
1411 position.x = 12;
1412
1413 auto size = theTheme.ImageSize( bmpRecoloredUpSmall );
1414 size.y = std::min(size.y, GetRulerHeight(false));
1415
1416 const auto button = ToolBar::MakeButton(
1417 this,
1418 bmpRecoloredUpSmall, bmpRecoloredDownSmall,
1419 bmpRecoloredUpHiliteSmall, bmpRecoloredHiliteSmall,
1420 bmpCogwheel, bmpCogwheel, bmpCogwheel,
1421 OnTogglePinnedStateID, position, true, size
1422 );
1423
1424 position.x += size.GetWidth();
1425 mButtons[iButton++] = button;
1426
1428}
1429
1431{
1433}
1434
1435namespace {
1437 {
1438#if 0
1439 if(scrubber.Seeks())
1440 /* i18n-hint: These commands assist the user in finding a sound by ear. ...
1441 "Scrubbing" is variable-speed playback, ...
1442 "Seeking" is normal speed playback but with skips
1443 */
1444 return XO("Click or drag to begin Seek");
1445 else
1446 /* i18n-hint: These commands assist the user in finding a sound by ear. ...
1447 "Scrubbing" is variable-speed playback, ...
1448 "Seeking" is normal speed playback but with skips
1449 */
1450 return XO("Click or drag to begin Scrub");
1451#else
1452 /* i18n-hint: These commands assist the user in finding a sound by ear. ...
1453 "Scrubbing" is variable-speed playback, ...
1454 "Seeking" is normal speed playback but with skips
1455 */
1456 return XO("Click & move to Scrub. Click & drag to Seek.");
1457#endif
1458 }
1459
1461 const Scrubber &scrubber, bool clicked)
1462 {
1463#if 0
1464 if(scrubber.Seeks())
1465 /* i18n-hint: These commands assist the user in finding a sound by ear. ...
1466 "Scrubbing" is variable-speed playback, ...
1467 "Seeking" is normal speed playback but with skips
1468 */
1469 return XO("Move to Seek");
1470 else
1471 /* i18n-hint: These commands assist the user in finding a sound by ear. ...
1472 "Scrubbing" is variable-speed playback, ...
1473 "Seeking" is normal speed playback but with skips
1474 */
1475 return XO("Move to Scrub");
1476#else
1477 if( clicked ) {
1478 // Since mouse is down, mention dragging first.
1479 // IsScrubbing is true if Scrubbing OR seeking.
1480 if( scrubber.IsScrubbing() )
1481 // User is dragging already, explain.
1482 return XO("Drag to Seek. Release to stop seeking.");
1483 else
1484 // User has clicked but not yet moved or released.
1485 return XO("Drag to Seek. Release and move to Scrub.");
1486 }
1487 // Since mouse is up, mention moving first.
1488 return XO("Move to Scrub. Drag to Seek.");
1489#endif
1490 }
1491
1492 const TranslatableString ScrubbingMessage(const Scrubber &scrubber, bool clicked)
1493 {
1494 if (scrubber.HasMark())
1495 return ContinueScrubbingMessage(scrubber, clicked);
1496 else
1497 return StartScrubbingMessage(scrubber);
1498 }
1499}
1500
1501void AdornedRulerPanel::OnIdle( wxIdleEvent &evt )
1502{
1503 evt.Skip();
1504 DoIdle();
1505}
1506
1508{
1509 bool changed = UpdateRects();
1510 changed = SetPanelSize() || changed;
1511
1512 auto &project = *mProject;
1513 auto &viewInfo = ViewInfo::Get( project );
1514 const auto &selectedRegion = viewInfo.selectedRegion;
1515 const auto &playRegion = viewInfo.playRegion;
1516
1517 changed = changed
1518 || mLastDrawnSelectedRegion != selectedRegion
1519 || mLastDrawnPlayRegion != std::pair{
1520 playRegion.GetLastActiveStart(), playRegion.GetLastActiveEnd() }
1521 || mLastDrawnH != viewInfo.hpos
1522 || mLastDrawnZoom != viewInfo.GetZoom()
1523 || mLastPlayRegionActive != viewInfo.playRegion.Active()
1524 ;
1525 if (changed)
1526 // Cause ruler redraw anyway, because we may be zooming or scrolling,
1527 // showing or hiding the scrub bar, etc.
1528 Refresh();
1529}
1530
1532{
1533 if (evt.type == AudioIOEvent::MONITOR)
1534 return;
1535 if ( evt.type == AudioIOEvent::CAPTURE ) {
1536 if (evt.on)
1537 {
1538 mIsRecording = true;
1539 this->CellularPanel::CancelDragging( false );
1541
1543 }
1544 else {
1545 mIsRecording = false;
1547 }
1548 }
1549
1550 if ( !evt.on )
1551 // So that the play region is updated
1553}
1554
1555void AdornedRulerPanel::OnPaint(wxPaintEvent & WXUNUSED(evt))
1556{
1557 const auto &viewInfo = ViewInfo::Get( *GetProject() );
1558 const auto &playRegion = viewInfo.playRegion;
1559 const auto playRegionBounds = std::pair{
1560 playRegion.GetLastActiveStart(), playRegion.GetLastActiveEnd() };
1561 mLastDrawnH = viewInfo.hpos;
1562 mLastDrawnZoom = viewInfo.GetZoom();
1563 mLastDrawnPlayRegion = playRegionBounds;
1564 mLastDrawnSelectedRegion = viewInfo.selectedRegion;
1565 // To do, note other fisheye state when we have that
1566
1567 wxPaintDC dc(this);
1568
1569 auto &backDC = GetBackingDCForRepaint();
1570
1571 DoDrawBackground(&backDC);
1572
1573 // Find play region rectangle, selected rectangle, and their overlap
1574 const auto rectP = PlayRegionRectangle(),
1575 rectS = SelectedRegionRectangle(),
1576 rectO = rectP.Intersect(rectS);
1577
1578 // What's left and right of the overlap? Assume same tops and bottoms
1579 const auto top = rectP.GetTop(),
1580 bottom = rectP.GetBottom();
1581 wxRect rectL{
1582 wxPoint{ 0, top }, wxPoint{ this->GetSize().GetWidth() - 1, bottom } };
1583 wxRect rectR = {};
1584 if (!rectO.IsEmpty()) {
1585 rectR = { wxPoint{ rectO.GetRight() + 1, top }, rectL.GetBottomRight() };
1586 rectL = { rectL.GetTopLeft(), wxPoint{ rectO.GetLeft() - 1, bottom } };
1587 }
1588
1589 DoDrawPlayRegion(&backDC, rectP, rectL, rectR);
1590 DoDrawOverlap(&backDC, rectO);
1591 DoDrawSelection(&backDC, rectS, rectL, rectR);
1592
1593 DoDrawPlayRegionLimits(&backDC, rectP);
1594
1595 DoDrawMarks(&backDC, true);
1596
1597 DoDrawEdge(&backDC);
1598
1599 DisplayBitmap(dc);
1600
1601 // Stroke extras direct to the client area,
1602 // maybe outside of the damaged area
1603 // As with TrackPanel, do not make a NEW wxClientDC or else Mac flashes badly!
1604 dc.DestroyClippingRegion();
1605 DrawOverlays(true, &dc);
1606}
1607
1608void AdornedRulerPanel::OnSize(wxSizeEvent &evt)
1609{
1610 mOuter = GetClientRect();
1611 if (mOuter.GetWidth() == 0 || mOuter.GetHeight() == 0)
1612 {
1613 return;
1614 }
1615
1616 UpdateRects();
1617
1619}
1620
1621void AdornedRulerPanel::OnLeave(wxMouseEvent& evt)
1622{
1623 evt.Skip();
1624 CallAfter([this]{
1626 });
1627}
1628
1630{
1631 if (message.appearance)
1632 return;
1634}
1635
1637{
1638 auto &selectedRegion = mViewInfo->selectedRegion;
1639 DoSelectionChange( selectedRegion );
1640}
1641
1643 const SelectedRegion &selectedRegion )
1644{
1645
1646 auto gAudioIO = AudioIOBase::Get();
1647 if ( !ViewInfo::Get( *mProject ).playRegion.Active() ) {
1648 // "Inactivated" play region follows the selection.
1649 SetPlayRegion( selectedRegion.t0(), selectedRegion.t1() );
1650 }
1651}
1652
1654{
1655 auto inner = mOuter;
1656 wxRect scrubZone;
1657 inner.x += LeftMargin;
1658 inner.width -= (LeftMargin + RightMargin);
1659
1660 auto top = &inner;
1661 auto bottom = &inner;
1662
1663 if (ShowingScrubRuler()) {
1664 scrubZone = inner;
1665 auto scrubHeight = std::min(scrubZone.height, (int)(ScrubHeight));
1666
1667 int topHeight;
1668#ifdef SCRUB_ABOVE
1669 top = &scrubZone, topHeight = scrubHeight;
1670#else
1671 auto qpHeight = scrubZone.height - scrubHeight;
1672 bottom = &scrubZone, topHeight = qpHeight;
1673 // Increase scrub zone height so that hit testing finds it and
1674 // not QP region, when on bottom 'edge'.
1675 scrubZone.height+=BottomMargin;
1676#endif
1677
1678 top->height = topHeight;
1679 bottom->height -= topHeight;
1680 bottom->y += topHeight;
1681 }
1682
1683 top->y += TopMargin;
1684 top->height -= TopMargin;
1685
1686 bottom->height -= BottomMargin;
1687
1688 if (!ShowingScrubRuler())
1689 scrubZone = inner;
1690
1691 if ( inner == mInner && scrubZone == mScrubZone )
1692 // no changes
1693 return false;
1694
1695 mInner = inner;
1696 mScrubZone = scrubZone;
1697
1698 mRuler.SetBounds(mInner.GetLeft(),
1699 mInner.GetTop(),
1700 mInner.GetRight(),
1701 mInner.GetBottom());
1702
1703 return true;
1704}
1705
1706double AdornedRulerPanel::Pos2Time(int p, bool ignoreFisheye) const
1707{
1709 , ignoreFisheye
1710 );
1711}
1712
1713int AdornedRulerPanel::Time2Pos(double t, bool ignoreFisheye) const
1714{
1716 , ignoreFisheye
1717 );
1718}
1719
1720bool AdornedRulerPanel::IsWithinMarker(int mousePosX, double markerTime)
1721{
1722 if (markerTime < 0)
1723 return false;
1724
1725 int pixelPos = Time2Pos(markerTime);
1726 int boundLeft = pixelPos - SELECT_TOLERANCE_PIXEL;
1727 int boundRight = pixelPos + SELECT_TOLERANCE_PIXEL;
1728
1729 return mousePosX >= boundLeft && mousePosX < boundRight;
1730}
1731
1732#ifdef QUICK_PLAY_HANDLE
1733auto AdornedRulerPanel::QPHandle::Click(
1734 const TrackPanelMouseEvent &event, AudacityProject *pProject) -> Result
1735{
1736 auto result = CommonRulerHandle::Click(event, pProject);
1737 if (!( result & RefreshCode::Cancelled )) {
1738 if (mClicked == Button::Left) {
1739 if (!mParent)
1741
1742 auto &scrubber = Scrubber::Get( *pProject );
1743 if(scrubber.HasMark()) {
1744 // We can't stop scrubbing yet (see comments in Bug 1391),
1745 // but we can pause it.
1746 ProjectAudioManager::Get( *pProject ).OnPause();
1747 }
1748
1749 // Store the initial play region state
1750 const auto &viewInfo = ViewInfo::Get( *pProject );
1751 const auto &playRegion = viewInfo.playRegion;
1752 mParent->mOldPlayRegion = playRegion;
1753
1754 // Save old selection, in case drag of selection is cancelled
1755 mOldSelection = ViewInfo::Get( *pProject ).selectedRegion;
1756
1757 mParent->HandleQPClick( event.event, mX );
1758 mParent->HandleQPDrag( event.event, mX );
1759 }
1760 }
1761
1762 return result;
1763}
1764
1765void AdornedRulerPanel::HandleQPClick(wxMouseEvent &evt, wxCoord mousePosX)
1766{
1767 // Temporarily inactivate play region
1768 if (mOldPlayRegion.Active() && evt.LeftDown()) {
1769 //mPlayRegionLock = true;
1771 }
1772
1775 bool isWithinStart = IsWithinMarker(mousePosX, mOldPlayRegion.GetStart());
1776 bool isWithinEnd = IsWithinMarker(mousePosX, mOldPlayRegion.GetEnd());
1777
1778 if (isWithinStart || isWithinEnd) {
1779 // If Quick-Play is playing from a point, we need to treat it as a click
1780 // not as dragging.
1781 if (mOldPlayRegion.Empty())
1783 // otherwise check which marker is nearer
1784 else {
1785 // Don't compare times, compare positions.
1786 //if (fabs(mQuickPlayPos[0] - mPlayRegionStart) < fabs(mQuickPlayPos[0] - mPlayRegionEnd))
1787 auto start = mOldPlayRegion.GetStart();
1788 auto end = mOldPlayRegion.GetEnd();
1789 if (abs(Time2Pos(mQuickPlayPos[0]) - Time2Pos(start)) <
1790 abs(Time2Pos(mQuickPlayPos[0]) - Time2Pos(end)))
1792 else
1794 }
1795 }
1796 else {
1797 // Clicked but not yet dragging
1799 }
1800}
1801
1802auto AdornedRulerPanel::QPHandle::Drag(
1803 const TrackPanelMouseEvent &event, AudacityProject *pProject) -> Result
1804{
1805 auto result = CommonRulerHandle::Drag(event, pProject);
1806 if (!( result & RefreshCode::Cancelled )) {
1807 if (mClicked == Button::Left) {
1808 if ( mParent ) {
1809 mX = event.event.m_x;
1810 mParent->UpdateQuickPlayPos( mX );
1811 mParent->HandleQPDrag( event.event, mX );
1812 }
1813 }
1814 }
1815 return result;
1816}
1817
1818void AdornedRulerPanel::HandleQPDrag(wxMouseEvent &/*event*/, wxCoord mousePosX)
1819{
1820 bool isWithinClick =
1821 (mLeftDownClickUnsnapped >= 0) &&
1823 bool isWithinStart = IsWithinMarker(mousePosX, mOldPlayRegion.GetStart());
1824 bool isWithinEnd = IsWithinMarker(mousePosX, mOldPlayRegion.GetEnd());
1825 bool canDragSel = !mOldPlayRegion.Active() && mPlayRegionDragsSelection;
1826 auto &viewInfo = ViewInfo::Get( *GetProject() );
1827 auto &playRegion = viewInfo.playRegion;
1828
1829 switch (mMouseEventState)
1830 {
1831 case mesNone:
1832 // If close to either end of play region, snap to closest
1833 if (isWithinStart || isWithinEnd) {
1836 else
1838 }
1839 break;
1841 // Don't start dragging until beyond tolerance initial playback start
1842 if (!mIsDragging && isWithinStart)
1844 else
1845 mIsDragging = true;
1846 // avoid accidental tiny selection
1847 if (isWithinEnd)
1849 playRegion.SetStart( mQuickPlayPos[0] );
1850 if (canDragSel) {
1852 }
1853 break;
1855 if (!mIsDragging && isWithinEnd) {
1857 }
1858 else
1859 mIsDragging = true;
1860 if (isWithinStart) {
1862 }
1863 playRegion.SetEnd( mQuickPlayPos[0] );
1864 if (canDragSel) {
1866 }
1867 break;
1869
1870 // Don't start dragging until mouse is beyond tolerance of initial click.
1871 if (isWithinClick || mLeftDownClick == -1) {
1873 playRegion.SetTimes(mLeftDownClick, mLeftDownClick);
1874 }
1875 else {
1877 }
1878 break;
1880 if (isWithinClick) {
1882 }
1883
1885 playRegion.SetTimes( mQuickPlayPos[0], mLeftDownClick );
1886 else
1887 playRegion.SetTimes( mLeftDownClick, mQuickPlayPos[0] );
1888 if (canDragSel) {
1890 }
1891 break;
1892 }
1893 Refresh();
1894 Update();
1895}
1896#endif
1897
1899 const TrackPanelMouseState &, AudacityProject *pProject)
1901{
1902 auto &scrubber = Scrubber::Get( *pProject );
1903 auto message = ScrubbingMessage(scrubber, mClicked == Button::Left);
1904
1905 mParent->SetNumGuides(1);
1906 return {
1907 message,
1908 {},
1909 // Tooltip is same as status message, or blank
1910 mParent ? message : TranslatableString{},
1911 };
1912}
1913
1914#ifdef QUICK_PLAY_HANDLE
1915auto AdornedRulerPanel::QPHandle::Preview(
1916 const TrackPanelMouseState &state, AudacityProject *pProject)
1918{
1919 mParent->SetNumGuides(1);
1920 TranslatableString tooltip;
1921 #if 0
1922 if (mParent) {
1923 if (!mParent->mQuickPlayEnabled)
1924 tooltip = XO("Quick-Play disabled");
1925 else
1926 tooltip = XO("Quick-Play enabled");
1927 }
1928 #endif
1929
1930 TranslatableString message;
1931 auto &scrubber = Scrubber::Get( *pProject );
1932 const bool scrubbing = scrubber.HasMark();
1933 if (scrubbing)
1934 // Don't distinguish zones
1935 message = ScrubbingMessage(scrubber, false);
1936 else
1937 // message = Insert timeline status bar message here
1938 ;
1939
1940 static wxCursor cursorHand{ wxCURSOR_HAND };
1941 static wxCursor cursorSizeWE{ wxCURSOR_SIZEWE };
1942
1943 bool showArrows = false;
1944 if (mParent)
1945 showArrows =
1946 (mClicked == Button::Left)
1947 || mParent->IsWithinMarker(
1948 state.state.m_x, mParent->mOldPlayRegion.GetStart())
1949 || mParent->IsWithinMarker(
1950 state.state.m_x, mParent->mOldPlayRegion.GetEnd());
1951
1952 return {
1953 message,
1954 showArrows ? &cursorSizeWE : &cursorHand,
1955 tooltip,
1956 };
1957}
1958
1960 const TrackPanelMouseEvent &event, AudacityProject *pProject,
1961 wxWindow *pParent)
1962 -> Result
1963{
1964 // Keep a shared pointer to self. Otherwise *this might get deleted
1965 // in HandleQPRelease on Windows! Because there is an event-loop yield
1966 // stopping playback, which caused OnCaptureLost to be called, which caused
1967 // clearing of CellularPanel targets!
1968 auto saveMe = mParent->mQPCell->mHolder.lock();
1969
1970 auto result = CommonRulerHandle::Release(event, pProject, pParent);
1971 if (!( result & RefreshCode::Cancelled )) {
1972 if (mClicked == Button::Left) {
1973 if ( mParent ) {
1974 mParent->HandleQPRelease( event.event );
1975 // Update the hot zones for cursor changes
1976 const auto &viewInfo = ViewInfo::Get( *pProject );
1977 const auto &playRegion = viewInfo.playRegion;
1978 mParent->mOldPlayRegion = playRegion;
1979 }
1980 }
1981 }
1982 return result;
1983}
1984
1985void AdornedRulerPanel::HandleQPRelease(wxMouseEvent &evt)
1986{
1987 auto &viewInfo = ViewInfo::Get(*GetProject());
1988 auto &playRegion = viewInfo.playRegion;
1989 playRegion.Order();
1990
1991 const double t0 = mTracks->GetStartTime();
1992 const double t1 = mTracks->GetEndTime();
1993 const auto &selectedRegion = viewInfo.selectedRegion;
1994 const double sel0 = selectedRegion.t0();
1995 const double sel1 = selectedRegion.t1();
1996
1997 // We want some audio in the selection, but we allow a dragged
1998 // region to include selected white-space and space before audio start.
1999 if (evt.ShiftDown() && playRegion.Empty()) {
2000 // Looping the selection or project.
2001 // Disable if track selection is in white-space beyond end of tracks and
2002 // play position is outside of track contents.
2003 if (((sel1 < t0) || (sel0 > t1)) &&
2004 ((playRegion.GetStart() < t0) || (playRegion.GetStart() > t1))) {
2006 }
2007 }
2008 // Disable if beyond end.
2009 else if (playRegion.GetStart() >= t1) {
2011 }
2012 // Disable if empty selection before start.
2013 // (allow Quick-Play region to include 'pre-roll' white space)
2014 else if (
2015 playRegion.GetEnd() - playRegion.GetStart() > 0.0 &&
2016 playRegion.GetEnd() < t0
2017 ) {
2019 }
2020
2022 mIsDragging = false;
2023 mLeftDownClick = -1;
2024
2025 auto cleanup = finally( [&] {
2026 if (mOldPlayRegion.Active()) {
2027 // Restore Locked Play region
2028 SetPlayRegion(mOldPlayRegion.GetStart(), mOldPlayRegion.GetEnd());
2029 SelectUtilities::ActivatePlayRegion(*mProject);
2030 // and release local lock
2031 mOldPlayRegion.SetActive( false );
2032 }
2033 } );
2034
2035 StartQPPlay(!evt.ShiftDown(), evt.ControlDown());
2036}
2037
2038auto AdornedRulerPanel::QPHandle::Cancel(AudacityProject *pProject) -> Result
2039{
2040 auto result = CommonRulerHandle::Cancel(pProject);
2041
2042 if (mClicked == Button::Left) {
2043 if( mParent ) {
2044 ViewInfo::Get( *pProject ).selectedRegion = mOldSelection;
2045 mParent->mMouseEventState = mesNone;
2046 mParent->SetPlayRegion(
2047 mParent->mOldPlayRegion.GetStart(), mParent->mOldPlayRegion.GetEnd());
2048 if (mParent->mOldPlayRegion.Active()) {
2049 // Restore Locked Play region
2051 // and release local lock
2052 mParent->mOldPlayRegion.SetActive( false );
2053 }
2054 }
2055 }
2056
2057 return result;
2058}
2059#endif
2060
2062 bool newDefault, bool cutPreview, const double *pStartTime)
2063{
2064 const double t0 = mTracks->GetStartTime();
2065 const double t1 = mTracks->GetEndTime();
2066 auto &viewInfo = ViewInfo::Get( *mProject );
2067 const auto &playRegion = viewInfo.playRegion;
2068 const auto &selectedRegion = viewInfo.selectedRegion;
2069 const double sel0 = selectedRegion.t0();
2070 const double sel1 = selectedRegion.t1();
2071
2072 // Start / Restart playback on left click.
2073 bool startPlaying = true; // = (playRegion.GetStart() >= 0);
2074
2075 if (startPlaying) {
2076 bool loopEnabled = true;
2077 auto oldStart = std::max(0.0, playRegion.GetStart());
2078 double start = oldStart, end = 0;
2079
2080 if (playRegion.Empty()) {
2081 // Play either a selection or the project.
2082 if (oldStart > sel0 && oldStart < sel1) {
2083 // we are in a selection, so use the selection
2084 start = sel0;
2085 end = sel1;
2086 } // not in a selection, so use the project
2087 else {
2088 start = t0;
2089 end = t1;
2090 }
2091 }
2092 else
2093 end = std::max(start, playRegion.GetEnd());
2094
2095 // Looping a tiny selection may freeze, so just play it once.
2096 loopEnabled = ((end - start) > 0.001)? true : false;
2097
2098 newDefault = (loopEnabled && newDefault);
2099 if (newDefault)
2100 cutPreview = false;
2101 auto options = ProjectAudioIO::GetDefaultOptions(*mProject, newDefault);
2102
2103 if (!cutPreview) {
2104 if (pStartTime)
2105 options.pStartTime.emplace(*pStartTime);
2106 }
2107 else
2108 options.envelope = nullptr;
2109
2110 auto mode =
2111 cutPreview ? PlayMode::cutPreviewPlay
2112 : newDefault ? PlayMode::loopedPlay
2114
2115 // Stop only after deciding where to start again, because an event
2116 // callback may change the play region back to the selection
2117 auto &projectAudioManager = ProjectAudioManager::Get( *mProject );
2118 projectAudioManager.Stop();
2119
2120 // Don't change play region, assume caller set it as needed
2121 // playRegion.SetTimes( start, end );
2122 // Refresh();
2123
2124 projectAudioManager.PlayPlayRegion((SelectedRegion(start, end)),
2125 options, mode,
2126 false);
2127
2128 }
2129}
2130
2131#if 0
2132// This version toggles ruler state indirectly via the scrubber
2133// to ensure that all the places where the state is shown update.
2134// For example buttons and menus must update.
2135void AdornedRulerPanel::OnToggleScrubRulerFromMenu(wxCommandEvent&)
2136{
2137 auto &scrubber = Scrubber::Get( *mProject );
2138 scrubber.OnToggleScrubRuler(*mProject);
2139}
2140#endif
2141
2142
2144{
2145 const auto oldSize = GetSize();
2146 wxSize size { oldSize.GetWidth(), GetRulerHeight(ShowingScrubRuler()) };
2147 if ( size != oldSize ) {
2148 SetSize(size);
2149 SetMinSize(size);
2150 PostSizeEventToParent();
2151 return true;
2152 }
2153 else
2154 return false;
2155}
2156
2158{
2159 auto pCellularPanel =
2160 dynamic_cast<CellularPanel*>( &GetProjectPanel( *GetProject() ) );
2161 if ( !pCellularPanel ) {
2162 wxASSERT( false );
2163 }
2164 else
2165 pCellularPanel->DrawOverlays( false );
2166 DrawOverlays( false );
2167}
2168
2170{
2171 auto common = [this](
2172 AButton &button, const CommandID &commandName, const TranslatableString &label) {
2173 ComponentInterfaceSymbol command{ commandName, label };
2174 ToolBar::SetButtonToolTip( *mProject, button, &command, 1u );
2175 button.SetLabel( Verbatim( button.GetToolTipText() ) );
2176
2177 button.UpdateStatus();
2178 };
2179
2180 {
2181 auto timelineOptionsButton = static_cast<AButton*>(FindWindow(OnTogglePinnedStateID));
2182 timelineOptionsButton->PopUp();
2183 // Bug 1584: Tooltip now shows what clicking will do.
2184 // Bug 2357: Action of button (and hence tooltip wording) updated.
2185 const auto label = XO("Timeline Options");
2186 common(*timelineOptionsButton, wxT("PinnedHead"), label);
2187 }
2188}
2189
2190void AdornedRulerPanel::OnPinnedButton(wxCommandEvent & /*event*/)
2191{
2193}
2194
2195void AdornedRulerPanel::OnTogglePinnedState(wxCommandEvent & /*event*/)
2196{
2199}
2200
2202{
2203 // Invoked for mouse-over preview events, or dragging, or scrub position
2204 // polling updates. Remember x coordinates, converted to times, for
2205 // drawing of guides.
2206
2207 // Keep Quick-Play within usable track area. (Dependent on zoom)
2208 const auto &viewInfo = ViewInfo::Get( *mProject );
2209 auto width = viewInfo.GetTracksUsableWidth();
2210 mousePosX = std::max(mousePosX, viewInfo.GetLeftOffset());
2211 mousePosX = std::min(mousePosX, viewInfo.GetLeftOffset() + width - 1);
2212 const auto time = Pos2Time(mousePosX);
2213
2214 for (size_t ii = 0; ii < mNumGuides; ++ii) {
2216 time + mQuickPlayOffset[ii];
2217 HandleSnapping(ii);
2218 }
2219}
2220
2221// Pop-up menus
2222
2223void AdornedRulerPanel::ShowMenu(const wxPoint & pos)
2224{
2225 const auto &viewInfo = ViewInfo::Get( *GetProject() );
2226 const auto &playRegion = viewInfo.playRegion;
2227 wxMenu rulerMenu;
2228
2229 {
2230 auto item = rulerMenu.AppendRadioItem(OnMinutesAndSecondsID,
2231 _("Minutes and Seconds"));
2233 }
2234
2235 {
2236 auto item = rulerMenu.AppendRadioItem(OnBeatsAndMeasuresID,
2237 _("Beats and Measures"));
2239 }
2240
2241 rulerMenu.AppendSeparator();
2242
2243 auto pDrag = rulerMenu.AppendCheckItem(OnSyncQuickPlaySelID, _("Setting a loop region also makes an audio selection"));
2244 pDrag->Check(mPlayRegionDragsSelection && playRegion.Active());
2245 pDrag->Enable(playRegion.Active());
2246
2247 {
2248 auto item = rulerMenu.AppendCheckItem(OnTogglePlayRegionID,
2250 item->Check(playRegion.Active());
2251 }
2252
2253 {
2254 auto item = rulerMenu.Append(OnClearPlayRegionID,
2255 /* i18n-hint Clear is a verb */
2256 _("Clear Loop"));
2257 }
2258
2259 {
2260 auto item = rulerMenu.Append(OnSetPlayRegionToSelectionID,
2261 _("Set Loop To Selection"));
2262 }
2263
2264 rulerMenu.AppendSeparator();
2265
2266 rulerMenu.AppendCheckItem(OnAutoScrollID, _("Scroll view to playhead"))->
2268
2269 rulerMenu.AppendCheckItem(OnTogglePinnedStateID, _("Continuous scrolling"))->
2271
2272 BasicMenu::Handle{ &rulerMenu }.Popup(
2274 { pos.x, pos.y }
2275 );
2276}
2277
2278void AdornedRulerPanel::ShowScrubMenu(const wxPoint & pos)
2279{
2280 auto &scrubber = Scrubber::Get( *mProject );
2281 PushEventHandler(&scrubber);
2282 auto cleanup = finally([this]{ PopEventHandler(); });
2283
2284 wxMenu rulerMenu;
2285 scrubber.PopulatePopupMenu(rulerMenu);
2286 BasicMenu::Handle{ &rulerMenu }.Popup(
2288 { pos.x, pos.y }
2289 );
2290}
2291
2293{
2294 auto &viewInfo = ViewInfo::Get( project );
2295 const auto &playRegion = viewInfo.playRegion;
2296 auto &selectedRegion = viewInfo.selectedRegion;
2297 selectedRegion.setT0(playRegion.GetStart(), false);
2298 selectedRegion.setT1(playRegion.GetEnd(), true);
2299}
2300
2302{
2303 // Play region dragging can snap to selection boundaries
2304 const auto &selectedRegion = ViewInfo::Get(*GetProject()).selectedRegion;
2305 SnapPointArray candidates;
2307 candidates = {
2308 SnapPoint{ selectedRegion.t0() },
2309 SnapPoint{ selectedRegion.t1() },
2310 };
2311 SnapManager snapManager{ *mProject, *mTracks, *mViewInfo, move(candidates) };
2312 auto results = snapManager.Snap(nullptr, mQuickPlayPos[index], false);
2313 mQuickPlayPos[index] = results.outTime;
2314 mIsSnapped[index] = results.Snapped();
2315}
2316
2318{
2319 int id = event.GetId();
2320 TimeDisplayMode changeFlag = mTimeDisplayMode;
2321 wxASSERT(id == OnMinutesAndSecondsID || id == OnBeatsAndMeasuresID);
2324
2326
2327 if (changeFlag != mTimeDisplayMode)
2328 Refresh();
2329}
2330
2332{
2334 gPrefs->Write(wxT("/QuickPlay/DragSelection"), mPlayRegionDragsSelection);
2335 gPrefs->Flush();
2336}
2337
2339{
2341 gPrefs->Write(wxT("/GUI/AutoScroll"), false);
2342 else
2343 gPrefs->Write(wxT("/GUI/AutoScroll"), true);
2344
2345 gPrefs->Flush();
2346
2348}
2349
2350
2352{
2354}
2355
2357{
2359}
2360
2362{
2364}
2365
2366
2368 MenuChoice choice, const wxPoint *pPosition)
2369{
2370 wxPoint position;
2371 if(pPosition)
2372 position = *pPosition;
2373 else
2374 {
2375 auto rect = GetRect();
2376 //Old code put menu too low down. y position applied twice.
2377 //position = { rect.GetLeft() + 1, rect.GetBottom() + 1 };
2378
2379 // The cell does not pass in the mouse or button position.
2380 // We happen to know this is the pin/unpin button
2381 // so these magic values 'fix a bug' - but really the cell should
2382 // pass more information to work with in.
2383 position = { rect.GetLeft() + 38, rect.GetHeight()/2 + 1 };
2384 }
2385
2386 switch (choice) {
2388 ShowMenu(position);
2390 break;
2391 case MenuChoice::Scrub:
2392 ShowScrubMenu(position); break;
2393 default:
2394 return;
2395 }
2396}
2397
2398using ColorId = decltype(clrTrackInfo);
2399
2401{
2402 return clrTrackInfo;
2403}
2404
2406{
2407 return clrTrackPanelText;
2408}
2409
2411{
2412 return TimelineTextColor();
2413}
2414
2416{
2417 return isActive ? clrRulerBackground : clrClipAffordanceInactiveBrush;
2418}
2419
2420static inline wxColour AlphaBlend(ColorId fg, ColorId bg, double alpha)
2421{
2422 const auto &fgc = theTheme.Colour(fg);
2423 const auto &bgc = theTheme.Colour(bg);
2424 return wxColour{
2425 wxColour::AlphaBlend(fgc.Red(), bgc.Red(), alpha),
2426 wxColour::AlphaBlend(fgc.Green(), bgc.Green(), alpha),
2427 wxColour::AlphaBlend(fgc.Blue(), bgc.Blue(), alpha)
2428 };
2429}
2430
2432{
2433 // Draw AdornedRulerPanel border
2435 dc->DrawRectangle( mInner );
2436
2437 if (ShowingScrubRuler()) {
2438 // Let's distinguish the scrubbing area by using a themable
2439 // colour and a line to set it off.
2440 AColor::UseThemeColour(dc, clrScrubRuler, TimelineTextColor() );
2441 wxRect ScrubRect = mScrubZone;
2442 ScrubRect.Inflate( 1,0 );
2443 dc->DrawRectangle(ScrubRect);
2444 }
2445}
2446
2448{
2449 wxRect r = mOuter;
2450 r.width -= RightMargin;
2451 r.height -= BottomMargin;
2452 AColor::BevelTrackInfo( *dc, true, r );
2453
2454 // Black stroke at bottom
2455 dc->SetPen( *wxBLACK_PEN );
2456 AColor::Line( *dc, mOuter.x,
2457 mOuter.y + mOuter.height - 1,
2458 mOuter.x + mOuter.width - 1 ,
2459 mOuter.y + mOuter.height - 1 );
2460}
2461
2462void AdornedRulerPanel::DoDrawMarks(wxDC * dc, bool /*text */ )
2463{
2464 const double min = Pos2Time(0);
2465 const double hiddenMin = Pos2Time(0, true);
2466 const double max = Pos2Time(mInner.width);
2467 const double hiddenMax = Pos2Time(mInner.width, true);
2468
2470 mRuler.SetRange( min, max, hiddenMin, hiddenMax );
2472 {
2473 mRuler.SetTickLengths({ 5, 3, 1 });
2474 }
2476 {
2477 mRuler.SetTickLengths({ 4, 2, 2 });
2478 }
2479 mRuler.Draw( *dc );
2480}
2481
2483{
2484 Refresh();
2485}
2486
2488{
2489 const auto &viewInfo = ViewInfo::Get(*mProject);
2490 const auto &playRegion = viewInfo.playRegion;
2491 const auto t0 = playRegion.GetLastActiveStart(),
2492 t1 = playRegion.GetLastActiveEnd();
2493 return RegionRectangle(t0, t1);
2494}
2495
2497{
2498 const auto &viewInfo = ViewInfo::Get(*mProject);
2499 const auto &selectedRegion = viewInfo.selectedRegion;
2500 const auto t0 = selectedRegion.t0(), t1 = selectedRegion.t1();
2501 return RegionRectangle(t0, t1);
2502}
2503
2504wxRect AdornedRulerPanel::RegionRectangle(double t0, double t1) const
2505{
2506 int p0 = -1, p1 = -1;
2507 if (t0 == t1)
2508 // Make the rectangle off-screen horizontally, but set the height
2509 ;
2510 else {
2511 p0 = max(1, Time2Pos(t0));
2512 p1 = min(mInner.width, Time2Pos(t1));
2513 }
2514
2515 const int left = p0, top = mInner.y, right = p1, bottom = mInner.GetBottom();
2516 return { wxPoint{left, top}, wxPoint{right, bottom} };
2517}
2518
2520 wxDC * dc, const wxRect &rectP, const wxRect &rectL, const wxRect &rectR)
2521{
2522 const auto &viewInfo = ViewInfo::Get(*mProject);
2523 const auto& playRegion = viewInfo.playRegion;
2524
2525 const bool isActive = (mLastPlayRegionActive = playRegion.Active());
2526
2527 if (playRegion.IsLastActiveRegionClear())
2528 return;
2529
2530 // Paint the selected region bolder if independently varying, else dim
2531 const auto color = TimelineLoopRegionColor(isActive);
2532 dc->SetBrush( wxBrush( theTheme.Colour( color )) );
2533 dc->SetPen( wxPen( theTheme.Colour( color )) );
2534
2535 dc->DrawRectangle( rectP.Intersect(rectL) );
2536 dc->DrawRectangle( rectP.Intersect(rectR) );
2537}
2538
2539void AdornedRulerPanel::DoDrawPlayRegionLimits(wxDC * dc, const wxRect &rect)
2540{
2541 // Color the edges of the play region like the ticks and numbers
2542 ADCChanger cleanup( dc );
2543 const auto edgeColour = theTheme.Colour(TimelineLimitsColor());
2544 dc->SetPen( { edgeColour } );
2545 dc->SetBrush( { edgeColour } );
2546
2547 constexpr int side = 7;
2548 constexpr int sideLessOne = side - 1;
2549
2550 // Paint two shapes, each a line plus triangle at bottom
2551 const auto left = rect.GetLeft(),
2552 right = rect.GetRight(),
2553 bottom = rect.GetBottom(),
2554 top = rect.GetTop();
2555 {
2556 wxPoint points[]{
2557 {left, bottom - sideLessOne},
2558 {left - sideLessOne, bottom},
2559 {left, bottom},
2560 {left, top},
2561 };
2562 dc->DrawPolygon( 4, points );
2563 }
2564
2565 {
2566 wxPoint points[]{
2567 {right, top},
2568 {right, bottom},
2569 {right + sideLessOne, bottom},
2570 {right, bottom - sideLessOne},
2571 };
2572 dc->DrawPolygon( 4, points );
2573 }
2574}
2575
2576constexpr double SelectionOpacity = 0.2;
2577
2578void AdornedRulerPanel::DoDrawOverlap(wxDC * dc, const wxRect &rect)
2579{
2580 dc->SetBrush( wxBrush{ AlphaBlend(
2582 SelectionOpacity) } );
2583 dc->SetPen( *wxTRANSPARENT_PEN );
2584 dc->DrawRectangle( rect );
2585}
2586
2588 wxDC * dc, const wxRect &rectS, const wxRect &rectL, const wxRect &rectR)
2589{
2590 dc->SetBrush( wxBrush{ AlphaBlend(
2592 dc->SetPen( *wxTRANSPARENT_PEN );
2593 dc->DrawRectangle( rectS.Intersect(rectL) );
2594 dc->DrawRectangle( rectS.Intersect(rectR) );
2595}
2596
2598{
2599 return ProperRulerHeight + (showScrubBar ? ScrubHeight : 0);
2600}
2601
2603{
2604 if (mLeftOffset != offset) {
2605 mLeftOffset = offset;
2606 mUpdater.SetData(mViewInfo, offset);
2608 }
2609}
2610
2611// Draws the scrubbing/seeking indicator.
2613 wxDC * dc, wxCoord xx, int width, bool scrub, bool seek)
2614{
2615 ADCChanger changer(dc); // Undo pen and brush changes at function exit
2616
2617 wxPoint tri[ 3 ];
2618 if (seek) {
2619 auto height = IndicatorHeightForWidth(width);
2620 // Make four triangles
2621 const int TriangleWidth = width * 3 / 8;
2622
2623 // Double-double headed, left-right
2624 auto yy = ShowingScrubRuler()
2625 ? mScrubZone.y
2626 : (mInner.GetBottom() + 1) - 1 /* bevel */ - height;
2627 tri[ 0 ].x = xx - IndicatorOffset;
2628 tri[ 0 ].y = yy;
2629 tri[ 1 ].x = xx - IndicatorOffset;
2630 tri[ 1 ].y = yy + height;
2631 tri[ 2 ].x = xx - TriangleWidth;
2632 tri[ 2 ].y = yy + height / 2;
2633 dc->DrawPolygon( 3, tri );
2634
2635 tri[ 0 ].x -= TriangleWidth;
2636 tri[ 1 ].x -= TriangleWidth;
2637 tri[ 2 ].x -= TriangleWidth;
2638 dc->DrawPolygon( 3, tri );
2639
2640 tri[ 0 ].x = tri[ 1 ].x = xx + IndicatorOffset;
2641 tri[ 2 ].x = xx + TriangleWidth;
2642 dc->DrawPolygon( 3, tri );
2643
2644
2645 tri[ 0 ].x += TriangleWidth;
2646 tri[ 1 ].x += TriangleWidth;
2647 tri[ 2 ].x += TriangleWidth;
2648 dc->DrawPolygon( 3, tri );
2649 }
2650 else if (scrub) {
2651 auto height = IndicatorHeightForWidth(width);
2652 const int IndicatorHalfWidth = width / 2;
2653
2654 // Double headed, left-right
2655 auto yy = ShowingScrubRuler()
2656 ? mScrubZone.y
2657 : (mInner.GetBottom() + 1) - 1 /* bevel */ - height;
2658 tri[ 0 ].x = xx - IndicatorOffset;
2659 tri[ 0 ].y = yy;
2660 tri[ 1 ].x = xx - IndicatorOffset;
2661 tri[ 1 ].y = yy + height;
2662 tri[ 2 ].x = xx - IndicatorHalfWidth;
2663 tri[ 2 ].y = yy + height / 2;
2664 dc->DrawPolygon( 3, tri );
2665 tri[ 0 ].x = tri[ 1 ].x = xx + IndicatorOffset;
2666 tri[ 2 ].x = xx + IndicatorHalfWidth;
2667 dc->DrawPolygon( 3, tri );
2668 }
2669}
2670
2672 double playRegionStart, double playRegionEnd)
2673{
2674 // This is called by AudacityProject to make the play region follow
2675 // the current selection. But while the user is selecting a play region
2676 // with the mouse directly in the ruler, changes from outside are blocked.
2678 return;
2679
2680 auto &viewInfo = ViewInfo::Get( *GetProject() );
2681 auto &playRegion = viewInfo.playRegion;
2682 playRegion.SetTimes( playRegionStart, playRegionEnd );
2683
2684 Refresh();
2685}
2686
2688{
2689 ProjectAudioManager::Get( *mProject ).Stop();
2690
2691 auto &viewInfo = ViewInfo::Get( *GetProject() );
2692 auto &playRegion = viewInfo.playRegion;
2693 playRegion.Clear();
2694
2695 Refresh();
2696}
2697
2698void AdornedRulerPanel::GetMaxSize(wxCoord *width, wxCoord *height)
2699{
2700 mRuler.GetMaxSize(width, height);
2701}
2702
2704
2706 s_AcceptsFocus = true;
2707 return TempAllowFocus{ &s_AcceptsFocus };
2708}
2709
2711{
2712 nn = std::min(nn, MAX_GUIDES);
2713 // If increasing the number of guides, reinitialize newer ones
2714 for (size_t ii = mNumGuides; ii < nn; ++ii) {
2715 mQuickPlayOffset[ii] = 0;
2716 mQuickPlayPosUnsnapped[ii] = 0;
2717 mQuickPlayPos[ii] = 0;
2718 mIsSnapped[ii] = false;
2719 }
2720 mNumGuides = nn;
2721}
2722
2724{
2725 auto temp = TemporarilyAllowFocus();
2726 SetFocus();
2727}
2728
2729// Second-level subdivision includes quick-play region and maybe the scrub bar
2730// and also shaves little margins above and below
2732 explicit Subgroup( const AdornedRulerPanel &ruler ) : mRuler{ ruler } {}
2733 Subdivision Children( const wxRect & ) override
2734 {
2735 return { Axis::Y, ( mRuler.ShowingScrubRuler() )
2736 ? Refinement{
2737 { mRuler.mInner.GetTop(), mRuler.mQPCell },
2738 { mRuler.mScrubZone.GetTop(), mRuler.mScrubbingCell },
2739 { mRuler.mScrubZone.GetBottom() + 1, nullptr }
2740 }
2741 : Refinement{
2742 { mRuler.mInner.GetTop(), mRuler.mQPCell },
2743 { mRuler.mInner.GetBottom() + 1, nullptr }
2744 }
2745 };
2746 }
2748};
2749
2750// Top-level subdivision shaves little margins off left and right
2752 explicit MainGroup( const AdornedRulerPanel &ruler ) : mRuler{ ruler } {}
2753 Subdivision Children( const wxRect & ) override
2754 { return { Axis::X, Refinement{
2755 // Subgroup is a throwaway object
2756 { mRuler.mInner.GetLeft(), std::make_shared< Subgroup >( mRuler ) },
2757 { mRuler.mInner.GetRight() + 1, nullptr }
2758 } }; }
2760};
2761
2763{
2764 auto &scrubber = Scrubber::Get( *GetProject() );
2765 return scrubber.ShowsBar();
2766}
2767
2768// CellularPanel implementation
2769std::shared_ptr<TrackPanelNode> AdornedRulerPanel::Root()
2770{
2771 // Root is a throwaway object
2772 return std::make_shared< MainGroup >( *this );
2773}
2774
2776{
2777 return mProject;
2778}
2779
2780std::shared_ptr<TrackPanelCell> AdornedRulerPanel::GetFocusedCell()
2781{
2782 // No switching of focus yet to the other, scrub zone
2783 return mQPCell;
2784}
2785
2787{
2788}
2789
2791 TrackPanelCell *, TrackPanelCell *, unsigned refreshResult)
2792{
2793 if (refreshResult & RefreshCode::RefreshAll)
2794 Refresh(); // Overlays will be repainted too
2795 else if (refreshResult & RefreshCode::DrawOverlays)
2796 DrawBothOverlays(); // cheaper redrawing of guidelines only
2797}
2798
2800{
2801 ProjectStatus::Get( *GetProject() ).Set(message);
2802}
2803
2805{
2806 if (!mOverlay) {
2807 mOverlay =
2808 std::make_shared<TrackPanelGuidelineOverlay>( mProject );
2809 auto pCellularPanel =
2810 dynamic_cast<CellularPanel*>( &GetProjectPanel( *GetProject() ) );
2811 if ( !pCellularPanel ) {
2812 wxASSERT( false );
2813 }
2814 else
2815 pCellularPanel->AddOverlay( mOverlay );
2816 this->AddOverlay( mOverlay->mPartner );
2817 }
2818}
2819
2821{
2825
2826 auto &project = *mProject;
2827 // Update button image
2829
2830 auto &scrubber = Scrubber::Get( project );
2831 if (scrubber.HasMark())
2832 scrubber.SetScrollScrubbing(value);
2833}
2834
2836{
2837 return mTimeDisplayMode;
2838}
2839
2841{
2842 if (mTimeDisplayMode == type)
2843 return;
2844
2845 mTimeDisplayMode = type;
2847 Refresh();
2848}
2849
2850// Attach menu item
2851
2852#include "CommandContext.h"
2853#include "CommonCommandFlags.h"
2854
2855namespace {
2857{
2859}
2860
2861using namespace MenuRegistry;
2863 Command( wxT("PinnedHead"), XXO("Continuous scrolling"),
2865 // Switching of scrolling on and off is permitted
2866 // even during transport
2868 Options{}.CheckTest([](const AudacityProject&){
2870 { wxT("Transport/Other/Options/Part2"), { OrderingHint::Begin, {} } }
2871};
2872}
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:9
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:188
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:159
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:472
static void Line(wxDC &dc, wxCoord x1, wxCoord y1, wxCoord x2, wxCoord y2)
Definition: AColor.cpp:187
static void SnapGuidePen(wxDC *dc)
Definition: AColor.cpp:488
static void Light(wxDC *dc, bool selected, bool highlight=false)
Definition: AColor.cpp:413
static void BevelTrackInfo(wxDC &dc, bool up, const wxRect &r, bool highlight=false)
Definition: AColor.cpp:340
static void UseThemeColour(wxDC *dc, int iBrush, int iPen=-1, int alpha=255)
Definition: AColor.cpp:372
Makes temporary drawing context changes that you back out of, RAII style.
Definition: OverlayPanel.h:72
CommonCell(AdornedRulerPanel *parent, MenuChoice menuChoice)
unsigned DoContextMenu(const wxRect &, wxWindow *, const wxPoint *pPosition, AudacityProject *) final
HitTestPreview DefaultPreview(const TrackPanelMouseState &, const AudacityProject *) override
Result Click(const TrackPanelMouseEvent &event, AudacityProject *) override
Result Drag(const TrackPanelMouseEvent &, AudacityProject *) override
double Time(AudacityProject &project) const
bool HandlesRightClick() override
Whether the handle has any special right-button handling.
CommonRulerHandle(AdornedRulerPanel *pParent, wxCoord xx, MenuChoice menuChoice)
std::shared_ptr< const Channel > FindChannel() const override
Result Release(const TrackPanelMouseEvent &event, AudacityProject *, wxWindow *) override
wxWeakRef< AdornedRulerPanel > mParent
void StartPlay(AudacityProject &project, const wxMouseEvent &event)
Result Cancel(AudacityProject *) override
static UIHandle::Result NeedChangeHighlight(const CommonRulerHandle &oldState, const CommonRulerHandle &newState)
void Enter(bool, AudacityProject *) override
void DoStartAdjust(AudacityProject &project, double) override
MovePlayRegionHandle(AdornedRulerPanel *pParent, wxCoord xx)
void DoAdjust(AudacityProject &project) override
NewPlayRegionHandle(AdornedRulerPanel *pParent, wxCoord xx)
void DoAdjust(AudacityProject &project) override
void DoStartAdjust(AudacityProject &project, double time) override
virtual void DoAdjust(AudacityProject &)=0
virtual void DoStartAdjust(AudacityProject &, double)=0
Result Drag(const TrackPanelMouseEvent &event, AudacityProject *pProject) override
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:341
Subclass & Get(const RegisteredFactory &key)
Get reference to an attachment, creating on demand if not present, down-cast it to Subclass.
Definition: ClientData.h:317
void Assign(const RegisteredFactory &key, ReplacementPointer &&replacement)
Reassign Site's pointer to ClientData.
Definition: ClientData.h:363
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:616
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
static bool ShouldScrubPinned()
Definition: Scrubbing.cpp:151
bool IsScrubbing() const
static Scrubber & Get(AudacityProject &project)
Definition: Scrubbing.cpp:188
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:991
double GetStartTime() const
Return the least start time of the tracks, or 0 when no tracks.
Definition: Track.cpp:985
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:347
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:36
Result mChangeHighlight
Definition: UIHandle.h:147
unsigned Result
Definition: UIHandle.h:39
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
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)
std::shared_ptr< const Channel > FindChannel() const override
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:382
void CallAfter(Action action)
Schedule an action to be done later, and in the main thread.
Definition: BasicUI.cpp:208
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
auto end(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:159
Namespace containing an enum 'what to do on a refresh?'.
Definition: RefreshCode.h:16
void SetPlayRegionToSelection(AudacityProject &project)
void ActivatePlayRegion(AudacityProject &project)
void InactivatePlayRegion(AudacityProject &project)
void ClearPlayRegion(AudacityProject &project)
void TogglePlayRegion(AudacityProject &project)
double GetPlayHeadFraction(const AudacityProject *pProject, wxCoord xx)
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
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:112
Window placement information for wxWidgetsBasicUI can be constructed from a wxWindow pointer.