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;
744 if (mParent->mTimelineToolTip)
745 tooltip = XO("Timeline actions disabled during recording");
746
747 static wxCursor cursor{ wxCURSOR_DEFAULT };
748 return {
749 {},
750 &cursor,
751 tooltip,
752 };
753 }
754
756 const wxRect &,
757 wxWindow *, const wxPoint *pPosition, AudacityProject*) final
758 {
759 mParent->ShowContextMenu(mMenuChoice, pPosition);
760 return 0;
761 }
762
763protected:
766};
767
768#undef QUICK_PLAY_HANDLE
769#ifdef QUICK_PLAY_HANDLE
770class AdornedRulerPanel::QPHandle final : public CommonRulerHandle
771{
772public:
773 explicit
774 QPHandle( AdornedRulerPanel *pParent, wxCoord xx )
776 {
777 }
778
779private:
781 const TrackPanelMouseEvent &event, AudacityProject *pProject) override;
782
783 Result Drag(
784 const TrackPanelMouseEvent &event, AudacityProject *pProject) override;
785
787 const TrackPanelMouseState &state, AudacityProject *pProject)
788 override;
789
791 const TrackPanelMouseEvent &event, AudacityProject *pProject,
792 wxWindow *pParent) override;
793
794 Result Cancel(AudacityProject *pProject) override;
795
796 SelectedRegion mOldSelection;
797};
798#endif
799
800static std::unique_ptr<wxCursor> handOpenCursor = nullptr;
801
803public:
805 : PlayRegionAdjustingHandle( pParent, xx, MenuChoice::QuickPlay, handOpenCursor.get(), 2 )
806 {
807 if (!handOpenCursor) {
808 handOpenCursor = MakeCursor(wxCURSOR_HAND, RearrangeCursorXpm, 16, 16);
810 }
811 }
812
814 {
815 mParent->mQuickPlayOffset[0] = 0;
816 mParent->mQuickPlayOffset[1] = 0;
817 }
818
819private:
820 void DoStartAdjust(AudacityProject &project, double) override
821 {
822 // Ignore the snapping of the time at the mouse
823 const auto time = Time(project);
824 auto &playRegion = ViewInfo::Get(project).playRegion;
825 mParent->mQuickPlayOffset[0] = playRegion.GetStart() - time;
826 mParent->mQuickPlayOffset[1] = playRegion.GetEnd() - time;
827 }
828
830 {
831 // Move the whole play region rigidly (usually)
832 // though the length might change slightly if only one edge snaps
833 const auto times = SnappedTimes(project);
834
835 auto &playRegion = ViewInfo::Get(project).playRegion;
836 playRegion.SetTimes(times.first, times.second);
837 }
838};
839
841public:
843 AdornedRulerPanel *pParent, wxCoord xx, bool hitLeft )
844 : PlayRegionAdjustingHandle( pParent, xx, MenuChoice::QuickPlay, {wxCURSOR_SIZEWE} )
845 , mHitLeft{ hitLeft }
846 {
847 }
848
849private:
850 void DoStartAdjust(AudacityProject &project, double time) override
851 {
852 }
853
855 {
856 const auto time = SnappedTime(project, 0);
857
858 // Change the play region
859 // The check whether this new time should be start or end isn't
860 // important. The accessors for PlayRegion use min and max of stored
861 // values.
862 auto &playRegion = ViewInfo::Get(project).playRegion;
863 if (mHitLeft)
864 playRegion.SetStart(time);
865 else
866 playRegion.SetEnd(time);
867 }
868
869 bool mHitLeft = false;
870};
871
873public:
875 : PlayRegionAdjustingHandle( pParent, xx, MenuChoice::QuickPlay, {wxCURSOR_DEFAULT} )
876 {
877 }
878
879private:
880 void DoStartAdjust(AudacityProject &project, double time) override
881 {
882 auto &playRegion = ViewInfo::Get(project).playRegion;
883 playRegion.SetTimes(time, time);
884 }
885
887 {
888 const auto time = SnappedTime(project, 0);
889
890 // Change the play region
891 // The check whether this new time should be start or end isn't
892 // important. The accessors for PlayRegion use min and max of stored
893 // values.
894 auto &playRegion = ViewInfo::Get(project).playRegion;
895 playRegion.SetEnd(time);
896 }
897};
898
899namespace
900{
901
902wxCoord GetPlayHeadX( const AudacityProject *pProject )
903{
904 const auto &viewInfo = ViewInfo::Get( *pProject );
905 auto width = viewInfo.GetTracksUsableWidth();
906 return viewInfo.GetLeftOffset()
908}
909
910double GetPlayHeadFraction( const AudacityProject *pProject, wxCoord xx )
911{
912 const auto &viewInfo = ViewInfo::Get( *pProject );
913 auto width = viewInfo.GetTracksUsableWidth();
914 auto fraction = (xx - viewInfo.GetLeftOffset()) / double(width);
915 return std::max(0.0, std::min(1.0, fraction));
916}
917
918// Handle for dragging the pinned play head, which so far does not need
919// to be a friend of the AdornedRulerPanel class, so we don't make it nested.
921{
922public:
923 PlayheadHandle( AdornedRulerPanel &parent, wxCoord xx )
924 : mpParent{ &parent }
925 , mX( xx )
926 {}
927
928 std::shared_ptr<const Channel> FindChannel() const override
929 { return nullptr; }
930
932 const PlayheadHandle &oldState, const PlayheadHandle &newState)
933 {
934 if (oldState.mX != newState.mX)
936 return 0;
937 }
938
939 static std::shared_ptr<PlayheadHandle>
941 const AudacityProject *pProject, AdornedRulerPanel &parent, wxCoord xx )
942 {
943 if( Scrubber::Get( *pProject )
944 .IsTransportingPinned() &&
945 ProjectAudioIO::Get( *pProject ).IsAudioActive() )
946 {
947 const auto targetX = GetPlayHeadX( pProject );
948 if ( abs( xx - targetX ) <= SELECT_TOLERANCE_PIXEL )
949 return std::make_shared<PlayheadHandle>( parent, xx );
950 }
951 return {};
952 }
953
954protected:
956 const TrackPanelMouseEvent &event, AudacityProject *) override
957 {
958 if (event.event.LeftDClick()) {
959 // Restore default position on double click
961
963 // Do not start a drag
965 }
966 // Fix for Bug 2357
967 if (!event.event.LeftIsDown())
969
971 return 0;
972 }
973
975 const TrackPanelMouseEvent &event, AudacityProject *pProject) override
976 {
977
978 auto value = GetPlayHeadFraction(pProject, event.event.m_x );
981 }
982
985 override
986 {
987 mpParent->SetNumGuides(1);
988 static wxCursor cursor{ wxCURSOR_SIZEWE };
989 return {
990 XO( "Click and drag to adjust, double-click to reset" ),
991 &cursor,
992 /* i18n-hint: This text is a tooltip on the icon (of a pin) representing
993 the temporal position in the audio. */
994 XO( "Record/Play head" )
995 };
996 }
997
999 const TrackPanelMouseEvent &event, AudacityProject *pProject,
1000 wxWindow *) override
1001 {
1002 auto value = GetPlayHeadFraction(pProject, event.event.m_x );
1005 }
1006
1008 {
1011 }
1012
1013 void Enter(bool, AudacityProject *) override
1014 {
1015 mChangeHighlight = RefreshCode::DrawOverlays;
1016 }
1017
1019 wxCoord mX;
1020 double mOrigPreference {};
1021};
1022
1023}
1024
1026{
1027public:
1028 explicit
1030 : AdornedRulerPanel::CommonCell{ parent, MenuChoice::QuickPlay }
1031 {}
1032
1033 std::vector<UIHandlePtr> HitTest(
1034 const TrackPanelMouseState &state,
1035 const AudacityProject *pProject) override;
1036
1037 // Return shared_ptr to self, stored in parent
1038 std::shared_ptr<TrackPanelCell> ContextMenuDelegate() override
1039 { return mParent->mQPCell; }
1040
1041 bool Clicked() const {
1042#ifdef QUICK_PLAY_HANDLE
1043 if (auto ptr = mHolder.lock())
1044 return ptr->Clicked();
1045#endif
1046 return false;
1047 }
1048
1049#ifdef QUICK_PLAY_HANDLE
1050 std::weak_ptr<QPHandle> mHolder;
1051#endif
1052
1053 std::weak_ptr<ResizePlayRegionHandle> mResizePlayRegionHolder;
1054 std::weak_ptr<MovePlayRegionHandle> mMovePlayRegionHolder;
1055 std::weak_ptr<NewPlayRegionHandle> mNewPlayRegionHolder;
1056 std::weak_ptr<PlayheadHandle> mPlayheadHolder;
1057};
1058
1059std::vector<UIHandlePtr> AdornedRulerPanel::QPCell::HitTest(
1060 const TrackPanelMouseState &state,
1061 const AudacityProject *pProject)
1062{
1063 // Creation of overlays on demand here -- constructor of AdornedRulerPanel
1064 // is too early to do it
1065 mParent->CreateOverlays();
1066
1067 std::vector<UIHandlePtr> results;
1068 auto xx = state.state.m_x;
1069
1070#ifdef EXPERIMENTAL_DRAGGABLE_PLAY_HEAD
1071 {
1072 // Allow click and drag on the play head even while recording
1073 // Make this handle more prominent then the quick play handle
1074 auto result = PlayheadHandle::HitTest( pProject, *mParent, xx );
1075 if (result) {
1076 result = AssignUIHandlePtr( mPlayheadHolder, result );
1077 results.push_back( result );
1078 }
1079 }
1080#endif
1081
1082 // Disable mouse actions on Timeline while recording.
1083 if (!mParent->mIsRecording) {
1084 mParent->UpdateQuickPlayPos( xx );
1085
1086 #if 0
1087 auto result = std::make_shared<QPHandle>( mParent, xx );
1088 result = AssignUIHandlePtr( mHolder, result );
1089 results.push_back( result );
1090 #endif
1091 }
1092
1093 // High priority hit is a handle to change the existing play region
1094 bool hitLeft = false;
1095 const auto &playRegion = ViewInfo::Get(*pProject).playRegion;
1096 if ((hitLeft =
1097 mParent->IsWithinMarker(xx, playRegion.GetLastActiveStart())) ||
1098 mParent->IsWithinMarker(xx, playRegion.GetLastActiveEnd()))
1099 {
1100 auto result =
1101 std::make_shared<ResizePlayRegionHandle>( mParent, xx, hitLeft );
1102 result = AssignUIHandlePtr( mResizePlayRegionHolder, result );
1103 results.push_back(result);
1104 }
1105
1106 // Middle priority hit is a handle to change the existing play region at
1107 // both ends, but only when the play region is active
1108 if (auto time = mParent->Pos2Time(xx);
1109 playRegion.Active() &&
1110 time >= playRegion.GetStart() &&
1111 time <= playRegion.GetEnd())
1112 {
1113 auto result =
1114 std::make_shared<MovePlayRegionHandle>( mParent, xx );
1115 result = AssignUIHandlePtr( mMovePlayRegionHolder, result );
1116 results.push_back(result);
1117 }
1118
1119 // Lowest priority hit is a handle to drag a completely new play region
1120 {
1121 auto result = std::make_shared<NewPlayRegionHandle>( mParent, xx );
1122 result = AssignUIHandlePtr( mNewPlayRegionHolder, result );
1123 results.push_back(result);
1124 }
1125
1126 return results;
1127}
1128
1130{
1131public:
1132 explicit
1133 ScrubbingHandle( AdornedRulerPanel *pParent, wxCoord xx )
1134 : CommonRulerHandle( pParent, xx, MenuChoice::Scrub )
1135 {
1136 }
1137
1138private:
1140 const TrackPanelMouseEvent &event, AudacityProject *pProject) override
1141 {
1142 auto result = CommonRulerHandle::Click(event, pProject);
1143 if (!( result & RefreshCode::Cancelled )) {
1144 if (mClicked == Button::Left) {
1145 auto &scrubber = Scrubber::Get( *pProject );
1146 // only if scrubbing is allowed now
1147 bool canScrub =
1148 scrubber.CanScrub() &&
1149 mParent &&
1150 mParent->ShowingScrubRuler();
1151
1152 if (!canScrub)
1154 if (!scrubber.HasMark()) {
1155 // Asynchronous scrub poller gets activated here
1156 scrubber.MarkScrubStart(
1157 event.event.m_x, Scrubber::ShouldScrubPinned(), false);
1158 }
1159 }
1160 }
1161 return result;
1162 }
1163
1165 const TrackPanelMouseEvent &event, AudacityProject *pProject) override
1166 {
1167 auto result = CommonRulerHandle::Drag(event, pProject);
1168 if (!( result & RefreshCode::Cancelled )) {
1169 // Nothing needed here. The scrubber works by polling mouse state
1170 // after the start has been marked.
1171 }
1172 return result;
1173 }
1174
1176 const TrackPanelMouseState &state, AudacityProject *pProject)
1177 override;
1178
1180 const TrackPanelMouseEvent &event, AudacityProject *pProject,
1181 wxWindow *pParent) override {
1182 auto result = CommonRulerHandle::Release(event, pProject, pParent);
1183 if (!( result & RefreshCode::Cancelled )) {
1184 // Nothing needed here either. The scrub poller may have decided to
1185 // seek because a drag happened before button up, or it may decide
1186 // to start a scrub, as it watches mouse movement after the button up.
1187 }
1188 return result;
1189 }
1190
1191 Result Cancel(AudacityProject *pProject) override
1192 {
1193 auto result = CommonRulerHandle::Cancel(pProject);
1194
1195 if (mClicked == Button::Left) {
1196 auto &scrubber = Scrubber::Get( *pProject );
1197 scrubber.Cancel();
1198
1199 ProjectAudioManager::Get( *pProject ).Stop();
1200 }
1201
1202 return result;
1203 }
1204};
1205
1207{
1208public:
1209 explicit
1211 : AdornedRulerPanel::CommonCell{ parent, MenuChoice::Scrub }
1212 {}
1213
1214 std::vector<UIHandlePtr> HitTest(
1215 const TrackPanelMouseState &state,
1216 const AudacityProject *pProject) override;
1217
1218 // Return shared_ptr to self, stored in parent
1219 std::shared_ptr<TrackPanelCell> ContextMenuDelegate() override
1220 { return mParent->mScrubbingCell; }
1221
1222 bool Hit() const { return !mHolder.expired(); }
1223 bool Clicked() const {
1224 if (auto ptr = mHolder.lock())
1225 return ptr->Clicked();
1226 return false;
1227 }
1228
1229private:
1230 std::weak_ptr<ScrubbingHandle> mHolder;
1231};
1232
1234 const TrackPanelMouseState &state, const AudacityProject *)
1235{
1236 // Creation of overlays on demand here -- constructor of AdornedRulerPanel
1237 // is too early to do it
1238 mParent->CreateOverlays();
1239
1240 std::vector<UIHandlePtr> results;
1241
1242 // Disable mouse actions on Timeline while recording.
1243 if (!mParent->mIsRecording) {
1244 auto xx = state.state.m_x;
1245 mParent->UpdateQuickPlayPos( xx );
1246 auto result = std::make_shared<ScrubbingHandle>( mParent, xx );
1247 result = AssignUIHandlePtr( mHolder, result );
1248 results.push_back( result );
1249 }
1250
1251 return results;
1252}
1253
1254namespace{
1255AttachedWindows::RegisteredFactory sKey{
1256[]( AudacityProject &project ) -> wxWeakRef< wxWindow > {
1257 auto &viewInfo = ViewInfo::Get( project );
1258 auto &window = ProjectWindow::Get( project );
1259
1260 return safenew AdornedRulerPanel( &project, window.GetTrackListWindow(),
1261 wxID_ANY,
1262 wxDefaultPosition,
1263 wxSize( -1, AdornedRulerPanel::GetRulerHeight(false) ),
1264 &viewInfo );
1265}
1266};
1267}
1268
1270{
1272}
1273
1275 const AudacityProject &project )
1276{
1277 return Get( const_cast< AudacityProject & >( project ) );
1278}
1279
1281{
1282 auto *pPanel = GetAttachedWindows(project).Find( sKey );
1283 if (pPanel) {
1284 pPanel->wxWindow::Destroy();
1286 }
1287}
1288
1290 wxWindow *parent,
1291 wxWindowID id,
1292 const wxPoint& pos,
1293 const wxSize& size,
1294 ViewInfo *viewinfo
1295) : CellularPanel(parent, id, pos, size, viewinfo)
1296 , mProject { project }
1297 , mUpdater { ProjectTimeRuler::Get(*project).GetUpdater() }
1298 , mRuler { ProjectTimeRuler::Get(*project).GetRuler() }
1299{
1300 SetLayoutDirection(wxLayout_LeftToRight);
1301
1302 mQPCell = std::make_shared<QPCell>( this );
1303 mScrubbingCell = std::make_shared<ScrubbingCell>( this );
1304
1305 for (auto &button : mButtons)
1306 button = nullptr;
1307
1308 SetLabel( XO("Timeline") );
1309 SetName();
1310 SetBackgroundStyle(wxBG_STYLE_PAINT);
1311
1312 mLeftOffset = 0;
1313 mIndTime = -1;
1314
1315 mLeftDownClick = -1;
1317 mIsDragging = false;
1318
1319 mOuter = GetClientRect();
1320
1322
1324
1325 mRuler.SetLabelEdges( false );
1326
1328
1329 mIsRecording = false;
1330
1331 mTimelineToolTip = !!gPrefs->Read(wxT("/QuickPlay/ToolTips"), 1L);
1332 mPlayRegionDragsSelection = (gPrefs->Read(wxT("/QuickPlay/DragSelection"), 0L) == 1)? true : false;
1333
1334#if wxUSE_TOOLTIPS
1335 wxToolTip::Enable(true);
1336#endif
1337
1340
1341 // Delay until after CommandManager has been populated:
1343
1346
1347 // Bind event that updates the play region
1350
1352 mRuler.Subscribe([this](auto) { Refresh(); });
1353
1354 // And call it once to initialize it
1356}
1357
1359{
1360}
1361
1362void AdornedRulerPanel::Refresh( bool eraseBackground, const wxRect *rect )
1363{
1364 CellularPanel::Refresh( eraseBackground, rect );
1366}
1367
1369{
1370 if (mNeedButtonUpdate) {
1371 // Visit this block once only in the lifetime of this panel
1372 mNeedButtonUpdate = false;
1373 // Do this first time setting of button status texts
1374 // when we are sure the CommandManager is initialized.
1376 }
1377
1378 // Update button texts for language change
1380
1381 mTimelineToolTip = !!gPrefs->Read(wxT("/QuickPlay/ToolTips"), 1L);
1382
1383#ifdef EXPERIMENTAL_SCROLLING_LIMITS
1384#ifdef EXPERIMENTAL_TWO_TONE_TIME_RULER
1385 {
1386 auto scrollBeyondZero = ScrollingPreference.Read();
1387 mRuler.SetTwoTone(scrollBeyondZero);
1388 }
1389#endif
1390#endif
1391
1393 Refresh();
1394 // Update();
1395}
1396
1398{
1399 // TODO: Should we do this to destroy the grabber??
1400 // Get rid of any children we may have
1401 // DestroyChildren();
1402
1404 SetBackgroundColour(theTheme.Colour( clrMedium ));
1405
1406 for (auto & button : mButtons) {
1407 if (button)
1408 button->Destroy();
1409 button = nullptr;
1410 }
1411
1412 size_t iButton = 0;
1413 // Make the short row of time ruler push buttons.
1414 // Don't bother with sizers. Their sizes and positions are fixed.
1415 // Add a grabber converted to a spacer.
1416 // This makes it visually clearer that the button is a button.
1417
1418 wxPoint position( 1, 0 );
1419
1420 Grabber * pGrabber = safenew Grabber(this, {});
1421 pGrabber->SetAsSpacer( true );
1422 //pGrabber->SetSize( 10, 27 ); // default is 10,27
1423 pGrabber->SetPosition( position );
1424
1425 position.x = 12;
1426
1427 auto size = theTheme.ImageSize( bmpRecoloredUpSmall );
1428 size.y = std::min(size.y, GetRulerHeight(false));
1429
1430 auto buttonMaker = [&]
1431 (wxWindowID id, teBmps bitmap, bool toggle)
1432 {
1433 const auto button =
1435 this,
1436 bmpRecoloredUpSmall, bmpRecoloredDownSmall,
1437 bmpRecoloredUpHiliteSmall, bmpRecoloredHiliteSmall,
1438 bitmap, bitmap, bitmap,
1439 id, position, toggle, size
1440 );
1441
1442 position.x += size.GetWidth();
1443 mButtons[iButton++] = button;
1444 return button;
1445 };
1446 auto button = buttonMaker(OnTogglePinnedStateID, bmpPlayPointerPinned, true);
1448 *button, 3,
1449 bmpRecoloredUpSmall, bmpRecoloredDownSmall,
1450 bmpRecoloredUpHiliteSmall, bmpRecoloredHiliteSmall,
1451 //bmpUnpinnedPlayHead, bmpUnpinnedPlayHead, bmpUnpinnedPlayHead,
1452 bmpRecordPointer, bmpRecordPointer, bmpRecordPointer,
1453 size);
1455 *button, 2,
1456 bmpRecoloredUpSmall, bmpRecoloredDownSmall,
1457 bmpRecoloredUpHiliteSmall, bmpRecoloredHiliteSmall,
1458 //bmpUnpinnedPlayHead, bmpUnpinnedPlayHead, bmpUnpinnedPlayHead,
1459 bmpRecordPointerPinned, bmpRecordPointerPinned, bmpRecordPointerPinned,
1460 size);
1462 *button, 1,
1463 bmpRecoloredUpSmall, bmpRecoloredDownSmall,
1464 bmpRecoloredUpHiliteSmall, bmpRecoloredHiliteSmall,
1465 //bmpUnpinnedPlayHead, bmpUnpinnedPlayHead, bmpUnpinnedPlayHead,
1466 bmpPlayPointer, bmpPlayPointer, bmpPlayPointer,
1467 size);
1468
1470}
1471
1473{
1475}
1476
1477namespace {
1479 {
1480#if 0
1481 if(scrubber.Seeks())
1482 /* i18n-hint: These commands assist the user in finding a sound by ear. ...
1483 "Scrubbing" is variable-speed playback, ...
1484 "Seeking" is normal speed playback but with skips
1485 */
1486 return XO("Click or drag to begin Seek");
1487 else
1488 /* i18n-hint: These commands assist the user in finding a sound by ear. ...
1489 "Scrubbing" is variable-speed playback, ...
1490 "Seeking" is normal speed playback but with skips
1491 */
1492 return XO("Click or drag to begin Scrub");
1493#else
1494 /* i18n-hint: These commands assist the user in finding a sound by ear. ...
1495 "Scrubbing" is variable-speed playback, ...
1496 "Seeking" is normal speed playback but with skips
1497 */
1498 return XO("Click & move to Scrub. Click & drag to Seek.");
1499#endif
1500 }
1501
1503 const Scrubber &scrubber, bool clicked)
1504 {
1505#if 0
1506 if(scrubber.Seeks())
1507 /* i18n-hint: These commands assist the user in finding a sound by ear. ...
1508 "Scrubbing" is variable-speed playback, ...
1509 "Seeking" is normal speed playback but with skips
1510 */
1511 return XO("Move to Seek");
1512 else
1513 /* i18n-hint: These commands assist the user in finding a sound by ear. ...
1514 "Scrubbing" is variable-speed playback, ...
1515 "Seeking" is normal speed playback but with skips
1516 */
1517 return XO("Move to Scrub");
1518#else
1519 if( clicked ) {
1520 // Since mouse is down, mention dragging first.
1521 // IsScrubbing is true if Scrubbing OR seeking.
1522 if( scrubber.IsScrubbing() )
1523 // User is dragging already, explain.
1524 return XO("Drag to Seek. Release to stop seeking.");
1525 else
1526 // User has clicked but not yet moved or released.
1527 return XO("Drag to Seek. Release and move to Scrub.");
1528 }
1529 // Since mouse is up, mention moving first.
1530 return XO("Move to Scrub. Drag to Seek.");
1531#endif
1532 }
1533
1534 const TranslatableString ScrubbingMessage(const Scrubber &scrubber, bool clicked)
1535 {
1536 if (scrubber.HasMark())
1537 return ContinueScrubbingMessage(scrubber, clicked);
1538 else
1539 return StartScrubbingMessage(scrubber);
1540 }
1541}
1542
1543void AdornedRulerPanel::OnIdle( wxIdleEvent &evt )
1544{
1545 evt.Skip();
1546 DoIdle();
1547}
1548
1550{
1551 bool changed = UpdateRects();
1552 changed = SetPanelSize() || changed;
1553
1554 auto &project = *mProject;
1555 auto &viewInfo = ViewInfo::Get( project );
1556 const auto &selectedRegion = viewInfo.selectedRegion;
1557 const auto &playRegion = viewInfo.playRegion;
1558
1559 changed = changed
1560 || mLastDrawnSelectedRegion != selectedRegion
1561 || mLastDrawnPlayRegion != std::pair{
1562 playRegion.GetLastActiveStart(), playRegion.GetLastActiveEnd() }
1563 || mLastDrawnH != viewInfo.hpos
1564 || mLastDrawnZoom != viewInfo.GetZoom()
1565 || mLastPlayRegionActive != viewInfo.playRegion.Active()
1566 ;
1567 if (changed)
1568 // Cause ruler redraw anyway, because we may be zooming or scrolling,
1569 // showing or hiding the scrub bar, etc.
1570 Refresh();
1571}
1572
1574{
1575 if (evt.type == AudioIOEvent::MONITOR)
1576 return;
1577 if ( evt.type == AudioIOEvent::CAPTURE ) {
1578 if (evt.on)
1579 {
1580 mIsRecording = true;
1581 this->CellularPanel::CancelDragging( false );
1583
1585 }
1586 else {
1587 mIsRecording = false;
1589 }
1590 }
1591
1592 if ( !evt.on )
1593 // So that the play region is updated
1595}
1596
1597void AdornedRulerPanel::OnPaint(wxPaintEvent & WXUNUSED(evt))
1598{
1599 const auto &viewInfo = ViewInfo::Get( *GetProject() );
1600 const auto &playRegion = viewInfo.playRegion;
1601 const auto playRegionBounds = std::pair{
1602 playRegion.GetLastActiveStart(), playRegion.GetLastActiveEnd() };
1603 mLastDrawnH = viewInfo.hpos;
1604 mLastDrawnZoom = viewInfo.GetZoom();
1605 mLastDrawnPlayRegion = playRegionBounds;
1606 mLastDrawnSelectedRegion = viewInfo.selectedRegion;
1607 // To do, note other fisheye state when we have that
1608
1609 wxPaintDC dc(this);
1610
1611 auto &backDC = GetBackingDCForRepaint();
1612
1613 DoDrawBackground(&backDC);
1614
1615 // Find play region rectangle, selected rectangle, and their overlap
1616 const auto rectP = PlayRegionRectangle(),
1617 rectS = SelectedRegionRectangle(),
1618 rectO = rectP.Intersect(rectS);
1619
1620 // What's left and right of the overlap? Assume same tops and bottoms
1621 const auto top = rectP.GetTop(),
1622 bottom = rectP.GetBottom();
1623 wxRect rectL{
1624 wxPoint{ 0, top }, wxPoint{ this->GetSize().GetWidth() - 1, bottom } };
1625 wxRect rectR = {};
1626 if (!rectO.IsEmpty()) {
1627 rectR = { wxPoint{ rectO.GetRight() + 1, top }, rectL.GetBottomRight() };
1628 rectL = { rectL.GetTopLeft(), wxPoint{ rectO.GetLeft() - 1, bottom } };
1629 }
1630
1631 DoDrawPlayRegion(&backDC, rectP, rectL, rectR);
1632 DoDrawOverlap(&backDC, rectO);
1633 DoDrawSelection(&backDC, rectS, rectL, rectR);
1634
1635 DoDrawPlayRegionLimits(&backDC, rectP);
1636
1637 DoDrawMarks(&backDC, true);
1638
1639 DoDrawEdge(&backDC);
1640
1641 DisplayBitmap(dc);
1642
1643 // Stroke extras direct to the client area,
1644 // maybe outside of the damaged area
1645 // As with TrackPanel, do not make a NEW wxClientDC or else Mac flashes badly!
1646 dc.DestroyClippingRegion();
1647 DrawOverlays(true, &dc);
1648}
1649
1650void AdornedRulerPanel::OnSize(wxSizeEvent &evt)
1651{
1652 mOuter = GetClientRect();
1653 if (mOuter.GetWidth() == 0 || mOuter.GetHeight() == 0)
1654 {
1655 return;
1656 }
1657
1658 UpdateRects();
1659
1661}
1662
1663void AdornedRulerPanel::OnLeave(wxMouseEvent& evt)
1664{
1665 evt.Skip();
1666 CallAfter([this]{
1668 });
1669}
1670
1672{
1673 if (message.appearance)
1674 return;
1676}
1677
1679{
1680 auto &selectedRegion = mViewInfo->selectedRegion;
1681 DoSelectionChange( selectedRegion );
1682}
1683
1685 const SelectedRegion &selectedRegion )
1686{
1687
1688 auto gAudioIO = AudioIOBase::Get();
1689 if ( !ViewInfo::Get( *mProject ).playRegion.Active() ) {
1690 // "Inactivated" play region follows the selection.
1691 SetPlayRegion( selectedRegion.t0(), selectedRegion.t1() );
1692 }
1693}
1694
1696{
1697 auto inner = mOuter;
1698 wxRect scrubZone;
1699 inner.x += LeftMargin;
1700 inner.width -= (LeftMargin + RightMargin);
1701
1702 auto top = &inner;
1703 auto bottom = &inner;
1704
1705 if (ShowingScrubRuler()) {
1706 scrubZone = inner;
1707 auto scrubHeight = std::min(scrubZone.height, (int)(ScrubHeight));
1708
1709 int topHeight;
1710#ifdef SCRUB_ABOVE
1711 top = &scrubZone, topHeight = scrubHeight;
1712#else
1713 auto qpHeight = scrubZone.height - scrubHeight;
1714 bottom = &scrubZone, topHeight = qpHeight;
1715 // Increase scrub zone height so that hit testing finds it and
1716 // not QP region, when on bottom 'edge'.
1717 scrubZone.height+=BottomMargin;
1718#endif
1719
1720 top->height = topHeight;
1721 bottom->height -= topHeight;
1722 bottom->y += topHeight;
1723 }
1724
1725 top->y += TopMargin;
1726 top->height -= TopMargin;
1727
1728 bottom->height -= BottomMargin;
1729
1730 if (!ShowingScrubRuler())
1731 scrubZone = inner;
1732
1733 if ( inner == mInner && scrubZone == mScrubZone )
1734 // no changes
1735 return false;
1736
1737 mInner = inner;
1738 mScrubZone = scrubZone;
1739
1740 mRuler.SetBounds(mInner.GetLeft(),
1741 mInner.GetTop(),
1742 mInner.GetRight(),
1743 mInner.GetBottom());
1744
1745 return true;
1746}
1747
1748double AdornedRulerPanel::Pos2Time(int p, bool ignoreFisheye) const
1749{
1751 , ignoreFisheye
1752 );
1753}
1754
1755int AdornedRulerPanel::Time2Pos(double t, bool ignoreFisheye) const
1756{
1758 , ignoreFisheye
1759 );
1760}
1761
1762bool AdornedRulerPanel::IsWithinMarker(int mousePosX, double markerTime)
1763{
1764 if (markerTime < 0)
1765 return false;
1766
1767 int pixelPos = Time2Pos(markerTime);
1768 int boundLeft = pixelPos - SELECT_TOLERANCE_PIXEL;
1769 int boundRight = pixelPos + SELECT_TOLERANCE_PIXEL;
1770
1771 return mousePosX >= boundLeft && mousePosX < boundRight;
1772}
1773
1774#ifdef QUICK_PLAY_HANDLE
1775auto AdornedRulerPanel::QPHandle::Click(
1776 const TrackPanelMouseEvent &event, AudacityProject *pProject) -> Result
1777{
1778 auto result = CommonRulerHandle::Click(event, pProject);
1779 if (!( result & RefreshCode::Cancelled )) {
1780 if (mClicked == Button::Left) {
1781 if (!mParent)
1783
1784 auto &scrubber = Scrubber::Get( *pProject );
1785 if(scrubber.HasMark()) {
1786 // We can't stop scrubbing yet (see comments in Bug 1391),
1787 // but we can pause it.
1788 ProjectAudioManager::Get( *pProject ).OnPause();
1789 }
1790
1791 // Store the initial play region state
1792 const auto &viewInfo = ViewInfo::Get( *pProject );
1793 const auto &playRegion = viewInfo.playRegion;
1794 mParent->mOldPlayRegion = playRegion;
1795
1796 // Save old selection, in case drag of selection is cancelled
1797 mOldSelection = ViewInfo::Get( *pProject ).selectedRegion;
1798
1799 mParent->HandleQPClick( event.event, mX );
1800 mParent->HandleQPDrag( event.event, mX );
1801 }
1802 }
1803
1804 return result;
1805}
1806
1807void AdornedRulerPanel::HandleQPClick(wxMouseEvent &evt, wxCoord mousePosX)
1808{
1809 // Temporarily inactivate play region
1810 if (mOldPlayRegion.Active() && evt.LeftDown()) {
1811 //mPlayRegionLock = true;
1813 }
1814
1817 bool isWithinStart = IsWithinMarker(mousePosX, mOldPlayRegion.GetStart());
1818 bool isWithinEnd = IsWithinMarker(mousePosX, mOldPlayRegion.GetEnd());
1819
1820 if (isWithinStart || isWithinEnd) {
1821 // If Quick-Play is playing from a point, we need to treat it as a click
1822 // not as dragging.
1823 if (mOldPlayRegion.Empty())
1825 // otherwise check which marker is nearer
1826 else {
1827 // Don't compare times, compare positions.
1828 //if (fabs(mQuickPlayPos[0] - mPlayRegionStart) < fabs(mQuickPlayPos[0] - mPlayRegionEnd))
1829 auto start = mOldPlayRegion.GetStart();
1830 auto end = mOldPlayRegion.GetEnd();
1831 if (abs(Time2Pos(mQuickPlayPos[0]) - Time2Pos(start)) <
1832 abs(Time2Pos(mQuickPlayPos[0]) - Time2Pos(end)))
1834 else
1836 }
1837 }
1838 else {
1839 // Clicked but not yet dragging
1841 }
1842}
1843
1844auto AdornedRulerPanel::QPHandle::Drag(
1845 const TrackPanelMouseEvent &event, AudacityProject *pProject) -> Result
1846{
1847 auto result = CommonRulerHandle::Drag(event, pProject);
1848 if (!( result & RefreshCode::Cancelled )) {
1849 if (mClicked == Button::Left) {
1850 if ( mParent ) {
1851 mX = event.event.m_x;
1852 mParent->UpdateQuickPlayPos( mX );
1853 mParent->HandleQPDrag( event.event, mX );
1854 }
1855 }
1856 }
1857 return result;
1858}
1859
1860void AdornedRulerPanel::HandleQPDrag(wxMouseEvent &/*event*/, wxCoord mousePosX)
1861{
1862 bool isWithinClick =
1863 (mLeftDownClickUnsnapped >= 0) &&
1865 bool isWithinStart = IsWithinMarker(mousePosX, mOldPlayRegion.GetStart());
1866 bool isWithinEnd = IsWithinMarker(mousePosX, mOldPlayRegion.GetEnd());
1867 bool canDragSel = !mOldPlayRegion.Active() && mPlayRegionDragsSelection;
1868 auto &viewInfo = ViewInfo::Get( *GetProject() );
1869 auto &playRegion = viewInfo.playRegion;
1870
1871 switch (mMouseEventState)
1872 {
1873 case mesNone:
1874 // If close to either end of play region, snap to closest
1875 if (isWithinStart || isWithinEnd) {
1878 else
1880 }
1881 break;
1883 // Don't start dragging until beyond tolerance initial playback start
1884 if (!mIsDragging && isWithinStart)
1886 else
1887 mIsDragging = true;
1888 // avoid accidental tiny selection
1889 if (isWithinEnd)
1891 playRegion.SetStart( mQuickPlayPos[0] );
1892 if (canDragSel) {
1894 }
1895 break;
1897 if (!mIsDragging && isWithinEnd) {
1899 }
1900 else
1901 mIsDragging = true;
1902 if (isWithinStart) {
1904 }
1905 playRegion.SetEnd( mQuickPlayPos[0] );
1906 if (canDragSel) {
1908 }
1909 break;
1911
1912 // Don't start dragging until mouse is beyond tolerance of initial click.
1913 if (isWithinClick || mLeftDownClick == -1) {
1915 playRegion.SetTimes(mLeftDownClick, mLeftDownClick);
1916 }
1917 else {
1919 }
1920 break;
1922 if (isWithinClick) {
1924 }
1925
1927 playRegion.SetTimes( mQuickPlayPos[0], mLeftDownClick );
1928 else
1929 playRegion.SetTimes( mLeftDownClick, mQuickPlayPos[0] );
1930 if (canDragSel) {
1932 }
1933 break;
1934 }
1935 Refresh();
1936 Update();
1937}
1938#endif
1939
1941 const TrackPanelMouseState &, AudacityProject *pProject)
1943{
1944 auto &scrubber = Scrubber::Get( *pProject );
1945 auto message = ScrubbingMessage(scrubber, mClicked == Button::Left);
1946
1947 mParent->SetNumGuides(1);
1948 return {
1949 message,
1950 {},
1951 // Tooltip is same as status message, or blank
1952 ((mParent && mParent->mTimelineToolTip) ? message : TranslatableString{}),
1953 };
1954}
1955
1956#ifdef QUICK_PLAY_HANDLE
1957auto AdornedRulerPanel::QPHandle::Preview(
1958 const TrackPanelMouseState &state, AudacityProject *pProject)
1960{
1961 mParent->SetNumGuides(1);
1962 TranslatableString tooltip;
1963 #if 0
1964 if (mParent && mParent->mTimelineToolTip) {
1965 if (!mParent->mQuickPlayEnabled)
1966 tooltip = XO("Quick-Play disabled");
1967 else
1968 tooltip = XO("Quick-Play enabled");
1969 }
1970 #endif
1971
1972 TranslatableString message;
1973 auto &scrubber = Scrubber::Get( *pProject );
1974 const bool scrubbing = scrubber.HasMark();
1975 if (scrubbing)
1976 // Don't distinguish zones
1977 message = ScrubbingMessage(scrubber, false);
1978 else
1979 // message = Insert timeline status bar message here
1980 ;
1981
1982 static wxCursor cursorHand{ wxCURSOR_HAND };
1983 static wxCursor cursorSizeWE{ wxCURSOR_SIZEWE };
1984
1985 bool showArrows = false;
1986 if (mParent)
1987 showArrows =
1988 (mClicked == Button::Left)
1989 || mParent->IsWithinMarker(
1990 state.state.m_x, mParent->mOldPlayRegion.GetStart())
1991 || mParent->IsWithinMarker(
1992 state.state.m_x, mParent->mOldPlayRegion.GetEnd());
1993
1994 return {
1995 message,
1996 showArrows ? &cursorSizeWE : &cursorHand,
1997 tooltip,
1998 };
1999}
2000
2002 const TrackPanelMouseEvent &event, AudacityProject *pProject,
2003 wxWindow *pParent)
2004 -> Result
2005{
2006 // Keep a shared pointer to self. Otherwise *this might get deleted
2007 // in HandleQPRelease on Windows! Because there is an event-loop yield
2008 // stopping playback, which caused OnCaptureLost to be called, which caused
2009 // clearing of CellularPanel targets!
2010 auto saveMe = mParent->mQPCell->mHolder.lock();
2011
2012 auto result = CommonRulerHandle::Release(event, pProject, pParent);
2013 if (!( result & RefreshCode::Cancelled )) {
2014 if (mClicked == Button::Left) {
2015 if ( mParent ) {
2016 mParent->HandleQPRelease( event.event );
2017 // Update the hot zones for cursor changes
2018 const auto &viewInfo = ViewInfo::Get( *pProject );
2019 const auto &playRegion = viewInfo.playRegion;
2020 mParent->mOldPlayRegion = playRegion;
2021 }
2022 }
2023 }
2024 return result;
2025}
2026
2027void AdornedRulerPanel::HandleQPRelease(wxMouseEvent &evt)
2028{
2029 auto &viewInfo = ViewInfo::Get(*GetProject());
2030 auto &playRegion = viewInfo.playRegion;
2031 playRegion.Order();
2032
2033 const double t0 = mTracks->GetStartTime();
2034 const double t1 = mTracks->GetEndTime();
2035 const auto &selectedRegion = viewInfo.selectedRegion;
2036 const double sel0 = selectedRegion.t0();
2037 const double sel1 = selectedRegion.t1();
2038
2039 // We want some audio in the selection, but we allow a dragged
2040 // region to include selected white-space and space before audio start.
2041 if (evt.ShiftDown() && playRegion.Empty()) {
2042 // Looping the selection or project.
2043 // Disable if track selection is in white-space beyond end of tracks and
2044 // play position is outside of track contents.
2045 if (((sel1 < t0) || (sel0 > t1)) &&
2046 ((playRegion.GetStart() < t0) || (playRegion.GetStart() > t1))) {
2048 }
2049 }
2050 // Disable if beyond end.
2051 else if (playRegion.GetStart() >= t1) {
2053 }
2054 // Disable if empty selection before start.
2055 // (allow Quick-Play region to include 'pre-roll' white space)
2056 else if (
2057 playRegion.GetEnd() - playRegion.GetStart() > 0.0 &&
2058 playRegion.GetEnd() < t0
2059 ) {
2061 }
2062
2064 mIsDragging = false;
2065 mLeftDownClick = -1;
2066
2067 auto cleanup = finally( [&] {
2068 if (mOldPlayRegion.Active()) {
2069 // Restore Locked Play region
2070 SetPlayRegion(mOldPlayRegion.GetStart(), mOldPlayRegion.GetEnd());
2071 SelectUtilities::ActivatePlayRegion(*mProject);
2072 // and release local lock
2073 mOldPlayRegion.SetActive( false );
2074 }
2075 } );
2076
2077 StartQPPlay(!evt.ShiftDown(), evt.ControlDown());
2078}
2079
2080auto AdornedRulerPanel::QPHandle::Cancel(AudacityProject *pProject) -> Result
2081{
2082 auto result = CommonRulerHandle::Cancel(pProject);
2083
2084 if (mClicked == Button::Left) {
2085 if( mParent ) {
2086 ViewInfo::Get( *pProject ).selectedRegion = mOldSelection;
2087 mParent->mMouseEventState = mesNone;
2088 mParent->SetPlayRegion(
2089 mParent->mOldPlayRegion.GetStart(), mParent->mOldPlayRegion.GetEnd());
2090 if (mParent->mOldPlayRegion.Active()) {
2091 // Restore Locked Play region
2093 // and release local lock
2094 mParent->mOldPlayRegion.SetActive( false );
2095 }
2096 }
2097 }
2098
2099 return result;
2100}
2101#endif
2102
2104 bool newDefault, bool cutPreview, const double *pStartTime)
2105{
2106 const double t0 = mTracks->GetStartTime();
2107 const double t1 = mTracks->GetEndTime();
2108 auto &viewInfo = ViewInfo::Get( *mProject );
2109 const auto &playRegion = viewInfo.playRegion;
2110 const auto &selectedRegion = viewInfo.selectedRegion;
2111 const double sel0 = selectedRegion.t0();
2112 const double sel1 = selectedRegion.t1();
2113
2114 // Start / Restart playback on left click.
2115 bool startPlaying = true; // = (playRegion.GetStart() >= 0);
2116
2117 if (startPlaying) {
2118 bool loopEnabled = true;
2119 auto oldStart = std::max(0.0, playRegion.GetStart());
2120 double start = oldStart, end = 0;
2121
2122 if (playRegion.Empty()) {
2123 // Play either a selection or the project.
2124 if (oldStart > sel0 && oldStart < sel1) {
2125 // we are in a selection, so use the selection
2126 start = sel0;
2127 end = sel1;
2128 } // not in a selection, so use the project
2129 else {
2130 start = t0;
2131 end = t1;
2132 }
2133 }
2134 else
2135 end = std::max(start, playRegion.GetEnd());
2136
2137 // Looping a tiny selection may freeze, so just play it once.
2138 loopEnabled = ((end - start) > 0.001)? true : false;
2139
2140 newDefault = (loopEnabled && newDefault);
2141 if (newDefault)
2142 cutPreview = false;
2143 auto options = ProjectAudioIO::GetDefaultOptions(*mProject, newDefault);
2144
2145 if (!cutPreview) {
2146 if (pStartTime)
2147 options.pStartTime.emplace(*pStartTime);
2148 }
2149 else
2150 options.envelope = nullptr;
2151
2152 auto mode =
2153 cutPreview ? PlayMode::cutPreviewPlay
2154 : newDefault ? PlayMode::loopedPlay
2156
2157 // Stop only after deciding where to start again, because an event
2158 // callback may change the play region back to the selection
2159 auto &projectAudioManager = ProjectAudioManager::Get( *mProject );
2160 projectAudioManager.Stop();
2161
2162 // Don't change play region, assume caller set it as needed
2163 // playRegion.SetTimes( start, end );
2164 // Refresh();
2165
2166 projectAudioManager.PlayPlayRegion((SelectedRegion(start, end)),
2167 options, mode,
2168 false);
2169
2170 }
2171}
2172
2173#if 0
2174// This version toggles ruler state indirectly via the scrubber
2175// to ensure that all the places where the state is shown update.
2176// For example buttons and menus must update.
2177void AdornedRulerPanel::OnToggleScrubRulerFromMenu(wxCommandEvent&)
2178{
2179 auto &scrubber = Scrubber::Get( *mProject );
2180 scrubber.OnToggleScrubRuler(*mProject);
2181}
2182#endif
2183
2184
2186{
2187 const auto oldSize = GetSize();
2188 wxSize size { oldSize.GetWidth(), GetRulerHeight(ShowingScrubRuler()) };
2189 if ( size != oldSize ) {
2190 SetSize(size);
2191 SetMinSize(size);
2192 PostSizeEventToParent();
2193 return true;
2194 }
2195 else
2196 return false;
2197}
2198
2200{
2201 auto pCellularPanel =
2202 dynamic_cast<CellularPanel*>( &GetProjectPanel( *GetProject() ) );
2203 if ( !pCellularPanel ) {
2204 wxASSERT( false );
2205 }
2206 else
2207 pCellularPanel->DrawOverlays( false );
2208 DrawOverlays( false );
2209}
2210
2212{
2213 auto common = [this](
2214 AButton &button, const CommandID &commandName, const TranslatableString &label) {
2215 ComponentInterfaceSymbol command{ commandName, label };
2216 ToolBar::SetButtonToolTip( *mProject, button, &command, 1u );
2217 button.SetLabel( Verbatim( button.GetToolTipText() ) );
2218
2219 button.UpdateStatus();
2220 };
2221
2222 {
2223 // The button always reflects the pinned head preference, even though
2224 // there is also a Playback preference that may overrule it for scrubbing
2226 auto pinButton = static_cast<AButton*>(FindWindow(OnTogglePinnedStateID));
2227 if( !state )
2228 pinButton->PopUp();
2229 else
2230 pinButton->PushDown();
2231 auto gAudioIO = AudioIO::Get();
2232 pinButton->SetAlternateIdx(
2233 (gAudioIO->IsCapturing() ? 2 : 0) + (state ? 0 : 1));
2234 // Bug 1584: Tooltip now shows what clicking will do.
2235 // Bug 2357: Action of button (and hence tooltip wording) updated.
2236 const auto label = XO("Timeline Options");
2237 common(*pinButton, wxT("PinnedHead"), label);
2238 }
2239}
2240
2241void AdornedRulerPanel::OnPinnedButton(wxCommandEvent & /*event*/)
2242{
2244}
2245
2246void AdornedRulerPanel::OnTogglePinnedState(wxCommandEvent & /*event*/)
2247{
2250}
2251
2253{
2254 // Invoked for mouse-over preview events, or dragging, or scrub position
2255 // polling updates. Remember x coordinates, converted to times, for
2256 // drawing of guides.
2257
2258 // Keep Quick-Play within usable track area. (Dependent on zoom)
2259 const auto &viewInfo = ViewInfo::Get( *mProject );
2260 auto width = viewInfo.GetTracksUsableWidth();
2261 mousePosX = std::max(mousePosX, viewInfo.GetLeftOffset());
2262 mousePosX = std::min(mousePosX, viewInfo.GetLeftOffset() + width - 1);
2263 const auto time = Pos2Time(mousePosX);
2264
2265 for (size_t ii = 0; ii < mNumGuides; ++ii) {
2267 time + mQuickPlayOffset[ii];
2268 HandleSnapping(ii);
2269 }
2270}
2271
2272// Pop-up menus
2273
2274void AdornedRulerPanel::ShowMenu(const wxPoint & pos)
2275{
2276 const auto &viewInfo = ViewInfo::Get( *GetProject() );
2277 const auto &playRegion = viewInfo.playRegion;
2278 wxMenu rulerMenu;
2279
2280 {
2281 auto item = rulerMenu.AppendRadioItem(OnMinutesAndSecondsID,
2282 _("Minutes and Seconds"));
2284 }
2285
2286 {
2287 auto item = rulerMenu.AppendRadioItem(OnBeatsAndMeasuresID,
2288 _("Beats and Measures"));
2290 }
2291
2292 rulerMenu.AppendSeparator();
2293
2294 auto pDrag = rulerMenu.AppendCheckItem(OnSyncQuickPlaySelID, _("Setting a loop region also makes an audio selection"));
2295 pDrag->Check(mPlayRegionDragsSelection && playRegion.Active());
2296 pDrag->Enable(playRegion.Active());
2297
2298 rulerMenu.AppendCheckItem(OnAutoScrollID, _("Update display while playing"))->
2300
2301 {
2302 auto item = rulerMenu.AppendCheckItem(OnTogglePlayRegionID,
2304 item->Check(playRegion.Active());
2305 }
2306
2307 {
2308 auto item = rulerMenu.Append(OnClearPlayRegionID,
2309 /* i18n-hint Clear is a verb */
2310 _("Clear Loop"));
2311 }
2312
2313 {
2314 auto item = rulerMenu.Append(OnSetPlayRegionToSelectionID,
2315 _("Set Loop To Selection"));
2316 }
2317
2318 rulerMenu.AppendSeparator();
2319 rulerMenu.AppendCheckItem(OnTogglePinnedStateID, _("Pinned Play Head"))->
2321
2322 BasicMenu::Handle{ &rulerMenu }.Popup(
2324 { pos.x, pos.y }
2325 );
2326}
2327
2328void AdornedRulerPanel::ShowScrubMenu(const wxPoint & pos)
2329{
2330 auto &scrubber = Scrubber::Get( *mProject );
2331 PushEventHandler(&scrubber);
2332 auto cleanup = finally([this]{ PopEventHandler(); });
2333
2334 wxMenu rulerMenu;
2335 scrubber.PopulatePopupMenu(rulerMenu);
2336 BasicMenu::Handle{ &rulerMenu }.Popup(
2338 { pos.x, pos.y }
2339 );
2340}
2341
2343{
2344 auto &viewInfo = ViewInfo::Get( project );
2345 const auto &playRegion = viewInfo.playRegion;
2346 auto &selectedRegion = viewInfo.selectedRegion;
2347 selectedRegion.setT0(playRegion.GetStart(), false);
2348 selectedRegion.setT1(playRegion.GetEnd(), true);
2349}
2350
2352{
2353 // Play region dragging can snap to selection boundaries
2354 const auto &selectedRegion = ViewInfo::Get(*GetProject()).selectedRegion;
2355 SnapPointArray candidates;
2357 candidates = {
2358 SnapPoint{ selectedRegion.t0() },
2359 SnapPoint{ selectedRegion.t1() },
2360 };
2361 SnapManager snapManager{ *mProject, *mTracks, *mViewInfo, move(candidates) };
2362 auto results = snapManager.Snap(nullptr, mQuickPlayPos[index], false);
2363 mQuickPlayPos[index] = results.outTime;
2364 mIsSnapped[index] = results.Snapped();
2365}
2366
2368{
2369 int id = event.GetId();
2370 TimeDisplayMode changeFlag = mTimeDisplayMode;
2371 wxASSERT(id == OnMinutesAndSecondsID || id == OnBeatsAndMeasuresID);
2374
2376
2377 if (changeFlag != mTimeDisplayMode)
2378 Refresh();
2379}
2380
2382{
2384 gPrefs->Write(wxT("/QuickPlay/DragSelection"), mPlayRegionDragsSelection);
2385 gPrefs->Flush();
2386}
2387
2388#if 0
2389void AdornedRulerPanel::OnTimelineToolTips(wxCommandEvent&)
2390{
2391 mTimelineToolTip = (mTimelineToolTip)? false : true;
2392 gPrefs->Write(wxT("/QuickPlay/ToolTips"), mTimelineToolTip);
2393 gPrefs->Flush();
2394}
2395#endif
2396
2397
2399{
2401 gPrefs->Write(wxT("/GUI/AutoScroll"), false);
2402 else
2403 gPrefs->Write(wxT("/GUI/AutoScroll"), true);
2404
2405 gPrefs->Flush();
2406
2408}
2409
2410
2412{
2414}
2415
2417{
2419}
2420
2422{
2424}
2425
2426
2428 MenuChoice choice, const wxPoint *pPosition)
2429{
2430 wxPoint position;
2431 if(pPosition)
2432 position = *pPosition;
2433 else
2434 {
2435 auto rect = GetRect();
2436 //Old code put menu too low down. y position applied twice.
2437 //position = { rect.GetLeft() + 1, rect.GetBottom() + 1 };
2438
2439 // The cell does not pass in the mouse or button position.
2440 // We happen to know this is the pin/unpin button
2441 // so these magic values 'fix a bug' - but really the cell should
2442 // pass more information to work with in.
2443 position = { rect.GetLeft() + 38, rect.GetHeight()/2 + 1 };
2444 }
2445
2446 switch (choice) {
2448 ShowMenu(position);
2450 break;
2451 case MenuChoice::Scrub:
2452 ShowScrubMenu(position); break;
2453 default:
2454 return;
2455 }
2456}
2457
2458using ColorId = decltype(clrTrackInfo);
2459
2461{
2462 return clrTrackInfo;
2463}
2464
2466{
2467 return clrTrackPanelText;
2468}
2469
2471{
2472 return TimelineTextColor();
2473}
2474
2476{
2477 return isActive ? clrRulerBackground : clrClipAffordanceInactiveBrush;
2478}
2479
2480static inline wxColour AlphaBlend(ColorId fg, ColorId bg, double alpha)
2481{
2482 const auto &fgc = theTheme.Colour(fg);
2483 const auto &bgc = theTheme.Colour(bg);
2484 return wxColour{
2485 wxColour::AlphaBlend(fgc.Red(), bgc.Red(), alpha),
2486 wxColour::AlphaBlend(fgc.Green(), bgc.Green(), alpha),
2487 wxColour::AlphaBlend(fgc.Blue(), bgc.Blue(), alpha)
2488 };
2489}
2490
2492{
2493 // Draw AdornedRulerPanel border
2495 dc->DrawRectangle( mInner );
2496
2497 if (ShowingScrubRuler()) {
2498 // Let's distinguish the scrubbing area by using a themable
2499 // colour and a line to set it off.
2500 AColor::UseThemeColour(dc, clrScrubRuler, TimelineTextColor() );
2501 wxRect ScrubRect = mScrubZone;
2502 ScrubRect.Inflate( 1,0 );
2503 dc->DrawRectangle(ScrubRect);
2504 }
2505}
2506
2508{
2509 wxRect r = mOuter;
2510 r.width -= RightMargin;
2511 r.height -= BottomMargin;
2512 AColor::BevelTrackInfo( *dc, true, r );
2513
2514 // Black stroke at bottom
2515 dc->SetPen( *wxBLACK_PEN );
2516 AColor::Line( *dc, mOuter.x,
2517 mOuter.y + mOuter.height - 1,
2518 mOuter.x + mOuter.width - 1 ,
2519 mOuter.y + mOuter.height - 1 );
2520}
2521
2522void AdornedRulerPanel::DoDrawMarks(wxDC * dc, bool /*text */ )
2523{
2524 const double min = Pos2Time(0);
2525 const double hiddenMin = Pos2Time(0, true);
2526 const double max = Pos2Time(mInner.width);
2527 const double hiddenMax = Pos2Time(mInner.width, true);
2528
2530 mRuler.SetRange( min, max, hiddenMin, hiddenMax );
2532 {
2533 mRuler.SetTickLengths({ 5, 3, 1 });
2534 }
2536 {
2537 mRuler.SetTickLengths({ 4, 2, 2 });
2538 }
2539 mRuler.Draw( *dc );
2540}
2541
2543{
2544 Refresh();
2545}
2546
2548{
2549 const auto &viewInfo = ViewInfo::Get(*mProject);
2550 const auto &playRegion = viewInfo.playRegion;
2551 const auto t0 = playRegion.GetLastActiveStart(),
2552 t1 = playRegion.GetLastActiveEnd();
2553 return RegionRectangle(t0, t1);
2554}
2555
2557{
2558 const auto &viewInfo = ViewInfo::Get(*mProject);
2559 const auto &selectedRegion = viewInfo.selectedRegion;
2560 const auto t0 = selectedRegion.t0(), t1 = selectedRegion.t1();
2561 return RegionRectangle(t0, t1);
2562}
2563
2564wxRect AdornedRulerPanel::RegionRectangle(double t0, double t1) const
2565{
2566 int p0 = -1, p1 = -1;
2567 if (t0 == t1)
2568 // Make the rectangle off-screen horizontally, but set the height
2569 ;
2570 else {
2571 p0 = max(1, Time2Pos(t0));
2572 p1 = min(mInner.width, Time2Pos(t1));
2573 }
2574
2575 const int left = p0, top = mInner.y, right = p1, bottom = mInner.GetBottom();
2576 return { wxPoint{left, top}, wxPoint{right, bottom} };
2577}
2578
2580 wxDC * dc, const wxRect &rectP, const wxRect &rectL, const wxRect &rectR)
2581{
2582 const auto &viewInfo = ViewInfo::Get(*mProject);
2583 const auto& playRegion = viewInfo.playRegion;
2584
2585 const bool isActive = (mLastPlayRegionActive = playRegion.Active());
2586
2587 if (playRegion.IsLastActiveRegionClear())
2588 return;
2589
2590 // Paint the selected region bolder if independently varying, else dim
2591 const auto color = TimelineLoopRegionColor(isActive);
2592 dc->SetBrush( wxBrush( theTheme.Colour( color )) );
2593 dc->SetPen( wxPen( theTheme.Colour( color )) );
2594
2595 dc->DrawRectangle( rectP.Intersect(rectL) );
2596 dc->DrawRectangle( rectP.Intersect(rectR) );
2597}
2598
2599void AdornedRulerPanel::DoDrawPlayRegionLimits(wxDC * dc, const wxRect &rect)
2600{
2601 // Color the edges of the play region like the ticks and numbers
2602 ADCChanger cleanup( dc );
2603 const auto edgeColour = theTheme.Colour(TimelineLimitsColor());
2604 dc->SetPen( { edgeColour } );
2605 dc->SetBrush( { edgeColour } );
2606
2607 constexpr int side = 7;
2608 constexpr int sideLessOne = side - 1;
2609
2610 // Paint two shapes, each a line plus triangle at bottom
2611 const auto left = rect.GetLeft(),
2612 right = rect.GetRight(),
2613 bottom = rect.GetBottom(),
2614 top = rect.GetTop();
2615 {
2616 wxPoint points[]{
2617 {left, bottom - sideLessOne},
2618 {left - sideLessOne, bottom},
2619 {left, bottom},
2620 {left, top},
2621 };
2622 dc->DrawPolygon( 4, points );
2623 }
2624
2625 {
2626 wxPoint points[]{
2627 {right, top},
2628 {right, bottom},
2629 {right + sideLessOne, bottom},
2630 {right, bottom - sideLessOne},
2631 };
2632 dc->DrawPolygon( 4, points );
2633 }
2634}
2635
2636constexpr double SelectionOpacity = 0.2;
2637
2638void AdornedRulerPanel::DoDrawOverlap(wxDC * dc, const wxRect &rect)
2639{
2640 dc->SetBrush( wxBrush{ AlphaBlend(
2642 SelectionOpacity) } );
2643 dc->SetPen( *wxTRANSPARENT_PEN );
2644 dc->DrawRectangle( rect );
2645}
2646
2648 wxDC * dc, const wxRect &rectS, const wxRect &rectL, const wxRect &rectR)
2649{
2650 dc->SetBrush( wxBrush{ AlphaBlend(
2652 dc->SetPen( *wxTRANSPARENT_PEN );
2653 dc->DrawRectangle( rectS.Intersect(rectL) );
2654 dc->DrawRectangle( rectS.Intersect(rectR) );
2655}
2656
2658{
2659 return ProperRulerHeight + (showScrubBar ? ScrubHeight : 0);
2660}
2661
2663{
2664 if (mLeftOffset != offset) {
2665 mLeftOffset = offset;
2666 mUpdater.SetData(mViewInfo, offset);
2668 }
2669}
2670
2671// Draws the scrubbing/seeking indicator.
2673 wxDC * dc, wxCoord xx, int width, bool scrub, bool seek)
2674{
2675 ADCChanger changer(dc); // Undo pen and brush changes at function exit
2676
2677 wxPoint tri[ 3 ];
2678 if (seek) {
2679 auto height = IndicatorHeightForWidth(width);
2680 // Make four triangles
2681 const int TriangleWidth = width * 3 / 8;
2682
2683 // Double-double headed, left-right
2684 auto yy = ShowingScrubRuler()
2685 ? mScrubZone.y
2686 : (mInner.GetBottom() + 1) - 1 /* bevel */ - height;
2687 tri[ 0 ].x = xx - IndicatorOffset;
2688 tri[ 0 ].y = yy;
2689 tri[ 1 ].x = xx - IndicatorOffset;
2690 tri[ 1 ].y = yy + height;
2691 tri[ 2 ].x = xx - TriangleWidth;
2692 tri[ 2 ].y = yy + height / 2;
2693 dc->DrawPolygon( 3, tri );
2694
2695 tri[ 0 ].x -= TriangleWidth;
2696 tri[ 1 ].x -= TriangleWidth;
2697 tri[ 2 ].x -= TriangleWidth;
2698 dc->DrawPolygon( 3, tri );
2699
2700 tri[ 0 ].x = tri[ 1 ].x = xx + IndicatorOffset;
2701 tri[ 2 ].x = xx + TriangleWidth;
2702 dc->DrawPolygon( 3, tri );
2703
2704
2705 tri[ 0 ].x += TriangleWidth;
2706 tri[ 1 ].x += TriangleWidth;
2707 tri[ 2 ].x += TriangleWidth;
2708 dc->DrawPolygon( 3, tri );
2709 }
2710 else if (scrub) {
2711 auto height = IndicatorHeightForWidth(width);
2712 const int IndicatorHalfWidth = width / 2;
2713
2714 // Double headed, left-right
2715 auto yy = ShowingScrubRuler()
2716 ? mScrubZone.y
2717 : (mInner.GetBottom() + 1) - 1 /* bevel */ - height;
2718 tri[ 0 ].x = xx - IndicatorOffset;
2719 tri[ 0 ].y = yy;
2720 tri[ 1 ].x = xx - IndicatorOffset;
2721 tri[ 1 ].y = yy + height;
2722 tri[ 2 ].x = xx - IndicatorHalfWidth;
2723 tri[ 2 ].y = yy + height / 2;
2724 dc->DrawPolygon( 3, tri );
2725 tri[ 0 ].x = tri[ 1 ].x = xx + IndicatorOffset;
2726 tri[ 2 ].x = xx + IndicatorHalfWidth;
2727 dc->DrawPolygon( 3, tri );
2728 }
2729}
2730
2732 double playRegionStart, double playRegionEnd)
2733{
2734 // This is called by AudacityProject to make the play region follow
2735 // the current selection. But while the user is selecting a play region
2736 // with the mouse directly in the ruler, changes from outside are blocked.
2738 return;
2739
2740 auto &viewInfo = ViewInfo::Get( *GetProject() );
2741 auto &playRegion = viewInfo.playRegion;
2742 playRegion.SetTimes( playRegionStart, playRegionEnd );
2743
2744 Refresh();
2745}
2746
2748{
2749 ProjectAudioManager::Get( *mProject ).Stop();
2750
2751 auto &viewInfo = ViewInfo::Get( *GetProject() );
2752 auto &playRegion = viewInfo.playRegion;
2753 playRegion.Clear();
2754
2755 Refresh();
2756}
2757
2758void AdornedRulerPanel::GetMaxSize(wxCoord *width, wxCoord *height)
2759{
2760 mRuler.GetMaxSize(width, height);
2761}
2762
2764
2766 s_AcceptsFocus = true;
2767 return TempAllowFocus{ &s_AcceptsFocus };
2768}
2769
2771{
2772 nn = std::min(nn, MAX_GUIDES);
2773 // If increasing the number of guides, reinitialize newer ones
2774 for (size_t ii = mNumGuides; ii < nn; ++ii) {
2775 mQuickPlayOffset[ii] = 0;
2776 mQuickPlayPosUnsnapped[ii] = 0;
2777 mQuickPlayPos[ii] = 0;
2778 mIsSnapped[ii] = false;
2779 }
2780 mNumGuides = nn;
2781}
2782
2784{
2785 auto temp = TemporarilyAllowFocus();
2786 SetFocus();
2787}
2788
2789// Second-level subdivision includes quick-play region and maybe the scrub bar
2790// and also shaves little margins above and below
2792 explicit Subgroup( const AdornedRulerPanel &ruler ) : mRuler{ ruler } {}
2793 Subdivision Children( const wxRect & ) override
2794 {
2795 return { Axis::Y, ( mRuler.ShowingScrubRuler() )
2796 ? Refinement{
2797 { mRuler.mInner.GetTop(), mRuler.mQPCell },
2798 { mRuler.mScrubZone.GetTop(), mRuler.mScrubbingCell },
2799 { mRuler.mScrubZone.GetBottom() + 1, nullptr }
2800 }
2801 : Refinement{
2802 { mRuler.mInner.GetTop(), mRuler.mQPCell },
2803 { mRuler.mInner.GetBottom() + 1, nullptr }
2804 }
2805 };
2806 }
2808};
2809
2810// Top-level subdivision shaves little margins off left and right
2812 explicit MainGroup( const AdornedRulerPanel &ruler ) : mRuler{ ruler } {}
2813 Subdivision Children( const wxRect & ) override
2814 { return { Axis::X, Refinement{
2815 // Subgroup is a throwaway object
2816 { mRuler.mInner.GetLeft(), std::make_shared< Subgroup >( mRuler ) },
2817 { mRuler.mInner.GetRight() + 1, nullptr }
2818 } }; }
2820};
2821
2823{
2824 auto &scrubber = Scrubber::Get( *GetProject() );
2825 return scrubber.ShowsBar();
2826}
2827
2828// CellularPanel implementation
2829std::shared_ptr<TrackPanelNode> AdornedRulerPanel::Root()
2830{
2831 // Root is a throwaway object
2832 return std::make_shared< MainGroup >( *this );
2833}
2834
2836{
2837 return mProject;
2838}
2839
2840
2842{
2843 // No switching of focus yet to the other, scrub zone
2844 return mQPCell.get();
2845}
2846
2847
2849{
2850}
2851
2852
2854 TrackPanelCell *, TrackPanelCell *, unsigned refreshResult)
2855{
2856 if (refreshResult & RefreshCode::RefreshAll)
2857 Refresh(); // Overlays will be repainted too
2858 else if (refreshResult & RefreshCode::DrawOverlays)
2859 DrawBothOverlays(); // cheaper redrawing of guidelines only
2860}
2861
2863{
2864 ProjectStatus::Get( *GetProject() ).Set(message);
2865}
2866
2868{
2869 if (!mOverlay) {
2870 mOverlay =
2871 std::make_shared<TrackPanelGuidelineOverlay>( mProject );
2872 auto pCellularPanel =
2873 dynamic_cast<CellularPanel*>( &GetProjectPanel( *GetProject() ) );
2874 if ( !pCellularPanel ) {
2875 wxASSERT( false );
2876 }
2877 else
2878 pCellularPanel->AddOverlay( mOverlay );
2879 this->AddOverlay( mOverlay->mPartner );
2880 }
2881}
2882
2884{
2888
2889 auto &project = *mProject;
2890 // Update button image
2892
2893 auto &scrubber = Scrubber::Get( project );
2894 if (scrubber.HasMark())
2895 scrubber.SetScrollScrubbing(value);
2896}
2897
2899{
2900 return mTimeDisplayMode;
2901}
2902
2904{
2905 if (mTimeDisplayMode == type)
2906 return;
2907
2908 mTimeDisplayMode = type;
2910 Refresh();
2911}
2912
2913// Attach menu item
2914
2915#include "CommandContext.h"
2916#include "CommonCommandFlags.h"
2917
2918namespace {
2920{
2922}
2923
2924using namespace MenuRegistry;
2926 Command( wxT("PinnedHead"), XXO("Enable pinned play &head"),
2928 // Switching of scrolling on and off is permitted
2929 // even during transport
2931 Options{}.CheckTest([](const AudacityProject&){
2933 { wxT("Transport/Other/Options/Part2"), { OrderingHint::Begin, {} } }
2934};
2935}
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")
int teBmps
#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: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
BoolSetting ScrollingPreference
Definition: ViewInfo.cpp:322
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
TrackPanelCell * GetFocusedCell() 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
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:93
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:532
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:99
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
void SetTwoTone(bool twoTone)
Definition: Ruler.cpp:99
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
bool Read(T *pVar) const
overload of Read returning a boolean that is true if the value was previously defined *‍/
Definition: Prefs.h:205
Definition: Snap.h:31
wxColour & Colour(int iIndex)
wxSize ImageSize(int iIndex)
bool WriteEnum(TimeDisplayMode value)
static void MakeAlternateImages(AButton &button, int idx, teBmps eUp, teBmps eDown, teBmps eHilite, teBmps eDownHi, teBmps eStandardUp, teBmps eStandardDown, teBmps eDisabled, wxSize size)
Definition: ToolBar.cpp:945
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:311
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)
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.