Audacity 3.2.0
ProjectWindow.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3Audacity: A Digital Audio Editor
4
5ProjectWindow.cpp
6
7Paul Licameli split from AudacityProject.cpp
8
9**********************************************************************/
10
11#include "ProjectWindow.h"
12
13
14
15#include "ActiveProject.h"
16#include "AllThemeResources.h"
17#include "AudioIO.h"
18#include "Menus.h"
19#include "Project.h"
20#include "ProjectAudioIO.h"
21#include "ProjectFileIO.h"
22#include "ProjectWindows.h"
23#include "ProjectStatus.h"
24#include "ProjectSnap.h"
25#include "RefreshCode.h"
27#include "TrackPanelAx.h"
28#include "UndoManager.h"
29#include "ViewInfo.h"
30#include "WaveClip.h"
31#include "WaveTrack.h"
33#include "prefs/ThemePrefs.h"
34#include "prefs/TracksPrefs.h"
36#include "tracks/ui/Scrubbing.h"
37#include "tracks/ui/TrackView.h"
38#include "wxPanelWrapper.h"
39#include "WindowAccessible.h"
40
41#include "ThemedWrappers.h"
42
43#include <wx/app.h>
44#include <wx/display.h>
45#include <wx/scrolbar.h>
46#include <wx/sizer.h>
47#include <wx/splitter.h>
48#include <wx/wupdlock.h>
49
50#include "TrackPanel.h"
51
52namespace
53{
54#ifdef HAS_AUDIOCOM_UPLOAD
55 constexpr int DEFAULT_WINDOW_WIDTH = 1180;
56#else
57 constexpr int DEFAULT_WINDOW_WIDTH = 1120;
58#endif
59 constexpr int DEFAULT_WINDOW_HEIGHT = 674;
60}
61
62BoolSetting ProjectWindowMaximized{ L"/Window/Maximized", false };
63BoolSetting ProjectWindowIconized{ L"/Window/Iconized", false };
64IntSetting ProjectWindowX{ L"/Window/X", 0 };
65IntSetting ProjectWindowY{ L"/Window/Y", 0 };
68IntSetting ProjectWindowNormalX{ L"/Window/Normal_X", 0 };
69IntSetting ProjectWindowNormalY{ L"/Window/Normal_Y", 0 };
72
73// Returns the screen containing a rectangle, or -1 if none does.
74int ScreenContaining( wxRect & r ){
75 unsigned int n = wxDisplay::GetCount();
76 for(unsigned int i = 0;i<n;i++){
77 wxDisplay d(i);
78 wxRect scr = d.GetClientArea();
79 if( scr.Contains( r ) )
80 return (int)i;
81 }
82 return -1;
83}
84
85// true IFF TL and BR corners are on a connected display.
86// Does not need to check all four. We just need to check that
87// the window probably is straddling screens in a sensible way.
88// If the user wants to use mixed landscape and portrait, they can.
89bool CornersOnScreen( wxRect & r ){
90 if( wxDisplay::GetFromPoint( r.GetTopLeft() ) == wxNOT_FOUND) return false;
91 if( wxDisplay::GetFromPoint( r.GetBottomRight() ) == wxNOT_FOUND) return false;
92 return true;
93}
94
95// true iff we have enough of the top bar to be able to reposition the window.
96bool IsWindowAccessible(wxRect *requestedRect)
97{
98 wxDisplay display;
99 wxRect targetTitleRect(requestedRect->GetLeftTop(), requestedRect->GetBottomRight());
100 // Hackery to approximate a window top bar size from a window size.
101 // and exclude the open/close and borders.
102 targetTitleRect.x += 15;
103 targetTitleRect.width -= 100;
104 if (targetTitleRect.width < 165) targetTitleRect.width = 165;
105 targetTitleRect.height = 15;
106 int targetBottom = targetTitleRect.GetBottom();
107 int targetRight = targetTitleRect.GetRight();
108 // This looks like overkill to check each and every pixel in the ranges.
109 // and decide that if any is visible on screen we are OK.
110 for (int i = targetTitleRect.GetLeft(); i < targetRight; i++) {
111 for (int j = targetTitleRect.GetTop(); j < targetBottom; j++) {
112 int monitor = display.GetFromPoint(wxPoint(i, j));
113 if (monitor != wxNOT_FOUND) {
114 return TRUE;
115 }
116 }
117 }
118 return FALSE;
119}
120
121// BG: The default size and position of the first window
122void GetDefaultWindowRect(wxRect *defRect)
123{
124 *defRect = wxGetClientDisplayRect();
125
126 int width = DEFAULT_WINDOW_WIDTH;
127 int height = DEFAULT_WINDOW_HEIGHT;
128
129 //These conditional values assist in improving placement and size
130 //of NEW windows on different platforms.
131#ifdef __WXGTK__
132 height += 20;
133#endif
134
135#ifdef __WXMSW__
136 height += 40;
137#endif
138
139#ifdef __WXMAC__
140 height += 55;
141#endif
142
143 // Use screen size where it is smaller than the values we would like.
144 // Otherwise use the values we would like, and centred.
145 if (width < defRect->width)
146 {
147 defRect->x = (defRect->width - width)/2;
148 defRect->width = width;
149 }
150
151 if (height < defRect->height)
152 {
153 defRect->y = (defRect->height - height)/2;
154 // Bug 1119 workaround
155 // Small adjustment for very small Mac screens.
156 // If there is only a tiny space at the top
157 // then instead of vertical centre, align to bottom.
158 const int pixelsFormenu = 60;
159 if( defRect->y < pixelsFormenu )
160 defRect->y *=2;
161 defRect->height = height;
162 }
163}
164
165// BG: Calculate where to place the next window (could be the first window)
166// BG: Does not store X and Y in prefs. This is intentional.
167//
168// LL: This should NOT need to be this complicated...FIXME
169void GetNextWindowPlacement(wxRect *nextRect, bool *pMaximized, bool *pIconized)
170{
171 int inc = 25;
172
173 wxRect defaultRect;
174 GetDefaultWindowRect(&defaultRect);
175
176 *pMaximized = ProjectWindowMaximized.Read();
177 *pIconized = ProjectWindowIconized.Read();
178
179 wxRect windowRect;
180 windowRect.x = ProjectWindowX.ReadWithDefault(defaultRect.x);
181 windowRect.y = ProjectWindowY.ReadWithDefault(defaultRect.y);
182 windowRect.width = ProjectWindowWidth.ReadWithDefault(defaultRect.width);
183 windowRect.height = ProjectWindowHeight.ReadWithDefault(defaultRect.height);
184
185 wxRect normalRect;
186 normalRect.x = ProjectWindowNormalX.ReadWithDefault(defaultRect.x);
187 normalRect.y = ProjectWindowNormalY.ReadWithDefault(defaultRect.y);
188 normalRect.width = ProjectWindowNormalWidth.ReadWithDefault(defaultRect.width);
189 normalRect.height = ProjectWindowNormalHeight.ReadWithDefault(defaultRect.height);
190
191 // Workaround 2.1.1 and earlier bug on OSX...affects only normalRect, but let's just
192 // validate for all rects and plats
193 if (normalRect.width == 0 || normalRect.height == 0) {
194 normalRect = defaultRect;
195 }
196 if (windowRect.width == 0 || windowRect.height == 0) {
197 windowRect = defaultRect;
198 }
199
200
201 wxRect screenRect( wxGetClientDisplayRect());
202#if defined(__WXMAC__)
203
204 // On OSX, the top of the window should never be less than the menu height,
205 // so something is amiss if it is
206 if (normalRect.y < screenRect.y) {
207 normalRect = defaultRect;
208 }
209 if (windowRect.y < screenRect.y) {
210 windowRect = defaultRect;
211 }
212#endif
213
214 // IF projects empty, THEN it's the first window.
215 // It lands where the config says it should, and can straddle screen.
216 if (AllProjects{}.empty()) {
217 if (*pMaximized || *pIconized) {
218 *nextRect = normalRect;
219 }
220 else {
221 *nextRect = windowRect;
222 }
223 // Resize, for example if one monitor that was on is now off.
224 if (!CornersOnScreen( wxRect(*nextRect).Deflate( 32, 32 ))) {
225 *nextRect = defaultRect;
226 }
227 if (!IsWindowAccessible(nextRect)) {
228 *nextRect = defaultRect;
229 }
230 // Do not trim the first project window down.
231 // All corners are on screen (or almost so), and
232 // the rect may straddle screens.
233 return;
234 }
235
236
237 // ELSE a subsequent NEW window. It will NOT straddle screens.
238
239 // We don't mind being 32 pixels off the screen in any direction.
240 // Make sure initial sizes (pretty much) fit within the display bounds
241 // We used to trim the sizes which could result in ridiculously small windows.
242 // contributing to bug 1243.
243 // Now instead if the window significantly doesn't fit the screen, we use the default
244 // window instead, which we know does.
245 if (ScreenContaining( wxRect(normalRect).Deflate( 32, 32 ))<0) {
246 normalRect = defaultRect;
247 }
248 if (ScreenContaining( wxRect(windowRect).Deflate( 32, 32 ) )<0) {
249 windowRect = defaultRect;
250 }
251
252 bool validWindowSize = false;
253 ProjectWindow * validProject = NULL;
254 for ( auto iter = AllProjects{}.rbegin(), end = AllProjects{}.rend();
255 iter != end; ++iter
256 ) {
257 auto pProject = *iter;
258 if (!GetProjectFrame( *pProject ).IsIconized()) {
259 validWindowSize = true;
260 validProject = &ProjectWindow::Get( *pProject );
261 break;
262 }
263 }
264 if (validWindowSize) {
265 *nextRect = validProject->GetRect();
266 *pMaximized = validProject->IsMaximized();
267 *pIconized = validProject->IsIconized();
268 // Do not straddle screens.
269 if (ScreenContaining( wxRect(*nextRect).Deflate( 32, 32 ) )<0) {
270 *nextRect = defaultRect;
271 }
272 }
273 else {
274 *nextRect = normalRect;
275 }
276
277 //Placement depends on the increments
278 nextRect->x += inc;
279 nextRect->y += inc;
280
281 // defaultrect is a rectangle on the first screen. It's the right fallback to
282 // use most of the time if things are not working out right with sizing.
283 // windowRect is a saved rectangle size.
284 // normalRect seems to be a substitute for windowRect when iconized or maximised.
285
286 // Windows can say that we are off screen when actually we are not.
287 // On Windows 10 I am seeing miscalculation by about 6 pixels.
288 // To fix this we allow some sloppiness on the edge being counted as off screen.
289 // This matters most when restoring very carefully sized windows that are maximised
290 // in one dimension (height or width) but not both.
291 const int edgeSlop = 10;
292
293 // Next four lines are getting the rectangle for the screen that contains the
294 // top left corner of nextRect (and defaulting to rect of screen 0 otherwise).
295 wxPoint p = nextRect->GetLeftTop();
296 int scr = std::max( 0, wxDisplay::GetFromPoint( p ));
297 wxDisplay d( scr );
298 screenRect = d.GetClientArea();
299
300 // Now we (possibly) start trimming our rectangle down.
301 // Have we hit the right side of the screen?
302 wxPoint bottomRight = nextRect->GetBottomRight();
303 if (bottomRight.x > (screenRect.GetRight()+edgeSlop)) {
304 int newWidth = screenRect.GetWidth() - nextRect->GetLeft();
305 if (newWidth < defaultRect.GetWidth()) {
306 nextRect->x = windowRect.x;
307 nextRect->y = windowRect.y;
308 nextRect->width = windowRect.width;
309 }
310 else {
311 nextRect->width = newWidth;
312 }
313 }
314
315 // Have we hit the bottom of the screen?
316 bottomRight = nextRect->GetBottomRight();
317 if (bottomRight.y > (screenRect.GetBottom()+edgeSlop)) {
318 nextRect->y -= inc;
319 bottomRight = nextRect->GetBottomRight();
320 if (bottomRight.y > (screenRect.GetBottom()+edgeSlop)) {
321 nextRect->SetBottom(screenRect.GetBottom());
322 }
323 }
324
325 // After all that we could have a window that does not have a visible
326 // top bar. [It is unlikely, but something might have gone wrong]
327 // If so, use the safe fallback size.
328 if (!IsWindowAccessible(nextRect)) {
329 *nextRect = defaultRect;
330 }
331}
332
333namespace {
334
335// This wrapper prevents the scrollbars from retaining focus after being
336// used. Otherwise, the only way back to the track panel is to click it
337// and that causes your original location to be lost.
338class ScrollBar final : public wxScrollBar
339{
340public:
341 ScrollBar(wxWindow* parent, wxWindowID id, long style)
342 : wxScrollBar(parent, id, wxDefaultPosition, wxDefaultSize, style)
343 {
344 }
345
346 void OnSetFocus(wxFocusEvent & e)
347 {
348 wxWindow *w = e.GetWindow();
349 if (w != NULL) {
350 w->SetFocus();
351 }
352 }
353
354 void SetScrollbar(int position, int thumbSize,
355 int range, int pageSize,
356 bool refresh = true) override;
357
358private:
359 DECLARE_EVENT_TABLE()
360};
361
362void ScrollBar::SetScrollbar(int position, int thumbSize,
363 int range, int pageSize,
364 bool refresh)
365{
366 // Mitigate flashing of scrollbars by refreshing only when something really changes.
367
368 // PRL: This may have been made unnecessary by other fixes for flashing, see
369 // commit ac05b190bee7dd0000bce56edb0e5e26185c972f
370
371 auto changed =
372 position != GetThumbPosition() ||
373 thumbSize != GetThumbSize() ||
374 range != GetRange() ||
375 pageSize != GetPageSize();
376 if (!changed)
377 return;
378
379 wxScrollBar::SetScrollbar(position, thumbSize, range, pageSize, refresh);
380}
381
382BEGIN_EVENT_TABLE(ScrollBar, wxScrollBar)
383 EVT_SET_FOCUS(ScrollBar::OnSetFocus)
385
386// Common mouse wheel handling in track panel cells, moved here to avoid
387// compilation dependencies on Track, TrackPanel, and Scrubbing at low levels
388// which made cycles
389static CommonTrackPanelCell::MouseWheelHook::Scope scope{
390// Need a bit of memory from one call to the next
391[mVertScrollRemainder = 0.0](
392 const TrackPanelMouseEvent &evt, AudacityProject *pProject )
393mutable -> unsigned {
394 using namespace RefreshCode;
395
396 if ( TrackList::Get( *pProject ).empty() )
397 // Scrolling and Zoom in and out commands are disabled when there are no tracks.
398 // This should be disabled too for consistency. Otherwise
399 // you do see changes in the time ruler.
400 return Cancelled;
401
402 unsigned result = RefreshAll;
403 const wxMouseEvent &event = evt.event;
404 auto &viewInfo = ViewInfo::Get( *pProject );
405 Scrubber &scrubber = Scrubber::Get( *pProject );
406 auto &window = ProjectWindow::Get( *pProject );
407 const auto steps = evt.steps;
408
409 if (event.ShiftDown()
410 // Don't pan during smooth scrolling. That would conflict with keeping
411 // the play indicator centered.
412 && !scrubber.IsScrollScrubbing()
413 )
414 {
415 // MM: Scroll left/right when used with Shift key down
416 window.TP_ScrollWindow(
417 viewInfo.OffsetTimeByPixels(
418 viewInfo.PositionToTime(0), 50.0 * -steps));
419 }
420 else if (event.CmdDown())
421 {
422#if 0
423 // JKC: Alternative scroll wheel zooming code
424 // using AudacityProject zooming, which is smarter,
425 // it keeps selections on screen and centred if it can,
426 // also this ensures mousewheel and zoom buttons give same result.
427 double ZoomFactor = pow(2.0, steps);
428 AudacityProject *p = GetProject();
429 if( steps > 0 )
430 // PRL: Track panel refresh may be needed if you reenable this
431 // code, but we don't want this file dependent on TrackPanel.cpp
432 p->ZoomInByFactor( ZoomFactor );
433 else
434 p->ZoomOutByFactor( ZoomFactor );
435#endif
436 // MM: Zoom in/out when used with Control key down
437 // We're converting pixel positions to times,
438 // counting pixels from the left edge of the track.
439 int trackLeftEdge = viewInfo.GetLeftOffset();
440
441 // Time corresponding to mouse position
442 wxCoord xx;
443 double center_h;
444 double mouse_h = viewInfo.PositionToTime(event.m_x, trackLeftEdge);
445
446 // Scrubbing? Expand or contract about the center, ignoring mouse position
447 if (scrubber.IsScrollScrubbing())
448 center_h = viewInfo.h +
449 (viewInfo.GetScreenEndTime() - viewInfo.h) / 2.0;
450 // Zooming out? Focus on mouse.
451 else if( steps <= 0 )
452 center_h = mouse_h;
453 // No Selection? Focus on mouse.
454 else if((viewInfo.selectedRegion.t1() - viewInfo.selectedRegion.t0() ) < 0.00001 )
455 center_h = mouse_h;
456 // Before Selection? Focus on left
457 else if( mouse_h < viewInfo.selectedRegion.t0() )
458 center_h = viewInfo.selectedRegion.t0();
459 // After Selection? Focus on right
460 else if( mouse_h > viewInfo.selectedRegion.t1() )
461 center_h = viewInfo.selectedRegion.t1();
462 // Inside Selection? Focus on mouse
463 else
464 center_h = mouse_h;
465
466 xx = viewInfo.TimeToPosition(center_h, trackLeftEdge);
467
468 // Time corresponding to last (most far right) audio.
469 double audioEndTime = TrackList::Get( *pProject ).GetEndTime();
470
471// Disabled this code to fix Bug 1923 (tricky to wheel-zoom right of waveform).
472#if 0
473 // When zooming in empty space, it's easy to 'lose' the waveform.
474 // This prevents it.
475 // IF zooming in
476 if (steps > 0)
477 {
478 // IF mouse is to right of audio
479 if (center_h > audioEndTime)
480 // Zooming brings far right of audio to mouse.
481 center_h = audioEndTime;
482 }
483#endif
484
485 wxCoord xTrackEnd = viewInfo.TimeToPosition( audioEndTime );
486 viewInfo.ZoomBy(pow(2.0, steps/4.0));
487
488 double new_center_h = viewInfo.PositionToTime(xx, trackLeftEdge);
489 viewInfo.h += (center_h - new_center_h);
490
491 // If wave has gone off screen, bring it back.
492 // This means that the end of the track stays where it was.
493 if( viewInfo.h > audioEndTime )
494 viewInfo.h += audioEndTime - viewInfo.PositionToTime( xTrackEnd );
495
496
497 result |= FixScrollbars;
498 }
499 else
500 {
501#ifdef EXPERIMENTAL_SCRUBBING_SCROLL_WHEEL
502 if (scrubber.IsScrubbing()) {
503 scrubber.HandleScrollWheel(steps);
504 evt.event.Skip(false);
505 }
506 else
507#endif
508 {
509 // MM: Scroll up/down when used without modifier keys
510 double lines = steps * 4 + mVertScrollRemainder;
511 mVertScrollRemainder = lines - floor(lines);
512 lines = floor(lines);
513 auto didSomething = window.TP_ScrollUpDown((int)-lines);
514 if (!didSomething)
515 result |= Cancelled;
516 }
517 }
518
519 return result;
520} };
521
522AttachedWindows::RegisteredFactory sProjectWindowKey{
523 []( AudacityProject &parent ) -> wxWeakRef< wxWindow > {
524 wxRect wndRect;
525 bool bMaximized = false;
526 bool bIconized = false;
527 GetNextWindowPlacement(&wndRect, &bMaximized, &bIconized);
528
529 auto pWindow = safenew ProjectWindow(
530 nullptr, -1,
531 wxDefaultPosition,
532 wxSize(wndRect.width, wndRect.height),
533 parent
534 );
535
536 auto &window = *pWindow;
537 // wxGTK3 seems to need to require creating the window using default position
538 // and then manually positioning it.
539 window.SetPosition(wndRect.GetPosition());
540
541 if(bMaximized) {
542 window.Maximize(true);
543 }
544 else if (bIconized) {
545 // if the user close down and iconized state we could start back up and iconized state
546 // window.Iconize(TRUE);
547 }
548
549 return pWindow;
550 }
551};
552
553}
554
556{
558}
559
561{
562 return Get( const_cast< AudacityProject & >( project ) );
563}
564
566{
567 return pProject
569 : nullptr;
570}
571
573{
574 return Find( const_cast< AudacityProject * >( pProject ) );
575}
576
578{
579 auto& project = context.project;
580 auto& window = ProjectWindow::Get(project);
581
582 window.Reset();
583}
584
586{
587 return mNextWindowID++;
588}
589
590enum {
591 FirstID = 1000,
592
593 // Window controls
594
597
599};
600
601ProjectWindow::ProjectWindow(wxWindow * parent, wxWindowID id,
602 const wxPoint & pos,
603 const wxSize & size, AudacityProject &project)
604 : ProjectWindowBase{ parent, id, pos, size, project }
605{
607
608 constexpr auto EffectsPanelMaxWidth { 500 };
609 constexpr auto TrackPanelMinWidth { 250 };
610
611 // Two sub-windows need to be made before Init(),
612 // so that this constructor can complete, and then TrackPanel and
613 // AdornedRulerPanel can retrieve those windows from this in their
614 // factory functions
615
616 // PRL: this panel groups the top tool dock and the ruler into one
617 // tab cycle.
618 // Must create it with non-default width equal to the main window width,
619 // or else the device toolbar doesn't make initial widths of the choice
620 // controls correct.
622 this, wxID_ANY, wxDefaultPosition,
623 wxSize{ this->GetSize().GetWidth(), -1 }
624 };
625 mTopPanel->SetLabel( "Top Panel" );// Not localised
626 mTopPanel->SetLayoutDirection(wxLayout_LeftToRight);
627 mTopPanel->SetAutoLayout(true);
628#ifdef EXPERIMENTAL_DA2
629 mTopPanel->SetBackgroundColour(theTheme.Colour( clrMedium ));
630#endif
631
632 auto container = safenew wxSplitterWindow(this, wxID_ANY,
633 wxDefaultPosition,
634 wxDefaultSize,
635 wxNO_BORDER | wxSP_LIVE_UPDATE | wxSP_THIN_SASH);
636 container->Bind(wxEVT_SPLITTER_DOUBLECLICKED, [](wxSplitterEvent& event){
637 //"The default behaviour is to unsplit the window"
638 event.Veto();//do noting instead
639 });
640 container->Bind(wxEVT_SPLITTER_SASH_POS_CHANGING, [=](wxSplitterEvent& event){
641 if(event.GetSashPosition() > EffectsPanelMaxWidth)
642 //Prevents left panel from expanding further
643 event.SetSashPosition(-1);
644 });
645 mContainerWindow = container;
646
648 wxDefaultPosition,
649 wxDefaultSize,
650 wxNO_BORDER);
651 mTrackListWindow->SetMinSize({TrackPanelMinWidth, -1});
652 mTrackListWindow->SetSizer( safenew wxBoxSizer(wxVERTICAL) );
653 mTrackListWindow->SetLabel("Main Panel");// Not localized.
654 mTrackListWindow->SetLayoutDirection(wxLayout_LeftToRight);
655
657
658#ifdef EXPERIMENTAL_DA2
659 mTrackListWindow->SetBackgroundColour(theTheme.Colour( clrMedium ));
660#endif
661
662 mPlaybackScroller = std::make_unique<PlaybackScroller>( &project );
663
664 // PRL: Old comments below. No longer observing the ordering that it
665 // recommends. ProjectWindow::OnActivate puts the focus directly into
666 // the TrackPanel, which avoids the problems.
667 // LLL: When Audacity starts or becomes active after returning from
668 // another application, the first window that can accept focus
669 // will be given the focus even if we try to SetFocus(). By
670 // creating the scrollbars after the TrackPanel, we resolve
671 // several focus problems.
672
673 mHsbar = safenew ScrollBar(mTrackListWindow, HSBarID, wxSB_HORIZONTAL);
674 mVsbar = safenew ScrollBar(mTrackListWindow, VSBarID, wxSB_VERTICAL);
675#if wxUSE_ACCESSIBILITY
676 // so that name can be set on a standard control
677 mHsbar->SetAccessible(safenew WindowAccessible(mHsbar));
678 mVsbar->SetAccessible(safenew WindowAccessible(mVsbar));
679#endif
680 mHsbar->SetLayoutDirection(wxLayout_LeftToRight);
681 mHsbar->SetName(_("Horizontal Scrollbar"));
682 mVsbar->SetName(_("Vertical Scrollbar"));
683
685 .Subscribe([this](UndoRedoMessage message){
686 switch (message.type) {
687 case UndoRedoMessage::Pushed:
688 case UndoRedoMessage::Modified:
689 return OnUndoPushedModified();
690 case UndoRedoMessage::UndoOrRedo:
691 return OnUndoRedo();
692 case UndoRedoMessage::Reset:
693 return OnUndoReset();
694 default:
695 return;
696 }
697 });
698
701
702 // Subscribe to title changes published by ProjectFileIO
705
706 // Subscribe to snapping changes
708 ProjectSnap::Get(project).Subscribe([this](auto) { RedrawProject(); });
709 // And also establish my initial consistency with it
711}
712
714{
715 // Tool manager gives us capture sometimes
716 if(HasCapture())
717 ReleaseMouse();
718}
719
720BEGIN_EVENT_TABLE(ProjectWindow, wxFrame)
722 EVT_MOUSE_EVENTS(ProjectWindow::OnMouseEvent)
724 EVT_SIZE(ProjectWindow::OnSize)
725 EVT_SHOW(ProjectWindow::OnShow)
726 EVT_ICONIZE(ProjectWindow::OnIconize)
727 EVT_MOVE(ProjectWindow::OnMove)
728 EVT_ACTIVATE(ProjectWindow::OnActivate)
729 EVT_COMMAND_SCROLL_LINEUP(HSBarID, ProjectWindow::OnScrollLeftButton)
730 EVT_COMMAND_SCROLL_LINEDOWN(HSBarID, ProjectWindow::OnScrollRightButton)
731 EVT_COMMAND_SCROLL(HSBarID, ProjectWindow::OnScroll)
732 EVT_COMMAND_SCROLL(VSBarID, ProjectWindow::OnScroll)
733 // Fires for menu with ID #1...first menu defined
734 EVT_UPDATE_UI(1, ProjectWindow::OnUpdateUI)
735 EVT_COMMAND(wxID_ANY, EVT_TOOLBAR_UPDATED, ProjectWindow::OnToolBarUpdate)
736 //mchinen:multithreaded calls - may not be threadsafe with CommandEvent: may have to change.
738
739void ProjectWindow::ApplyUpdatedTheme()
740{
741 SetBackgroundColour(theTheme.Colour( clrMedium ));
742 ClearBackground();// For wxGTK.
743}
744
745void ProjectWindow::RedrawProject(const bool bForceWaveTracks /*= false*/)
746{
747 auto pThis = wxWeakRef<ProjectWindow>(this);
748 CallAfter( [pThis, bForceWaveTracks]{
749
750 if (!pThis)
751 return;
752 if (pThis->IsBeingDeleted())
753 return;
754
755 auto pProject = pThis->FindProject();
756 if (!pProject)
757 return;
758
759 auto &project = *pProject;
760 auto &tracks = TrackList::Get( project );
761 auto &trackPanel = GetProjectPanel( project );
762 pThis->FixScrollbars();
763 if (bForceWaveTracks)
764 {
765 for ( auto pWaveTrack : tracks.Any< WaveTrack >() )
766 for (const auto &clip: pWaveTrack->GetClips())
767 clip->MarkChanged();
768 }
769 trackPanel.Refresh(false);
770
771 });
772}
773
775{
776 auto pProject = FindProject();
777 if (!pProject)
778 return;
779 auto &project = *pProject;
780
781 if (message.appearance)
782 return;
783 this->ApplyUpdatedTheme();
784 auto &toolManager = ToolManager::Get( project );
785 toolManager.ForEach([](auto pToolBar){
786 if( pToolBar )
787 pToolBar->ReCreateButtons();
788 });
789}
790
792{
793 // Update status bar widths in case of language change
795}
796
798{
799 // Set a flag so we don't have to generate two update events
800 mAutoScrolling = true;
801
802 // Call our Scroll method which updates our ViewInfo variables
803 // to reflect the positions of the scrollbars
804 DoScroll();
805
806 mAutoScrolling = false;
807}
808
809#if defined(__WXMAC__)
810// const int sbarSpaceWidth = 15;
811// const int sbarControlWidth = 16;
812// const int sbarExtraLen = 1;
813const int sbarHjump = 30; //STM: This is how far the thumb jumps when the l/r buttons are pressed, or auto-scrolling occurs -- in pixels
814#elif defined(__WXMSW__)
815const int sbarSpaceWidth = 16;
816const int sbarControlWidth = 16;
817const int sbarExtraLen = 0;
818const int sbarHjump = 30; //STM: This is how far the thumb jumps when the l/r buttons are pressed, or auto-scrolling occurs -- in pixels
819#else // wxGTK, wxMOTIF, wxX11
820const int sbarSpaceWidth = 15;
821const int sbarControlWidth = 15;
822const int sbarExtraLen = 0;
823const int sbarHjump = 30; //STM: This is how far the thumb jumps when the l/r buttons are pressed, or auto-scrolling occurs -- in pixels
824#include "AllThemeResources.h"
825#endif
826
827// Make sure selection edge is in view
829{
830 auto pProject = FindProject();
831 if (!pProject)
832 return;
833 auto &project = *pProject;
834 auto &trackPanel = GetProjectPanel( project );
835 auto &viewInfo = ViewInfo::Get( project );
836 auto w = viewInfo.GetTracksUsableWidth();
837
838 int pixel = viewInfo.TimeToPosition(pos);
839 if (pixel < 0 || pixel >= w)
840 {
842 (viewInfo.OffsetTimeByPixels(pos, -(w / 2)));
843 trackPanel.Refresh(false);
844 }
845}
846
848{
849 auto pProject = FindProject();
850 if (!pProject)
851 return;
852 auto &project = *pProject;
853 auto &viewInfo = ViewInfo::Get( project );
854 ScrollIntoView(viewInfo.PositionToTime(x, viewInfo.GetLeftOffset()));
855}
856
862{
863 auto pProject = FindProject();
864 if (!pProject)
865 return;
866 auto &project = *pProject;
867 auto &viewInfo = ViewInfo::Get( project );
868 wxInt64 pos = mHsbar->GetThumbPosition();
869 // move at least one scroll increment
870 pos -= wxMax((wxInt64)(sbarHjump * viewInfo.sbarScale), 1);
871 pos = wxMax(pos, 0);
872 viewInfo.sbarH -= sbarHjump;
873 viewInfo.sbarH = std::max(viewInfo.sbarH,
874 -(wxInt64) PixelWidthBeforeTime(0.0));
875
876
877 if (pos != mHsbar->GetThumbPosition()) {
878 mHsbar->SetThumbPosition((int)pos);
880 }
881}
886
888{
889 auto pProject = FindProject();
890 if (!pProject)
891 return;
892 auto &project = *pProject;
893 auto &viewInfo = ViewInfo::Get( project );
894 wxInt64 pos = mHsbar->GetThumbPosition();
895 // move at least one scroll increment
896 // use wxInt64 for calculation to prevent temporary overflow
897 pos += wxMax((wxInt64)(sbarHjump * viewInfo.sbarScale), 1);
898 wxInt64 max = mHsbar->GetRange() - mHsbar->GetThumbSize();
899 pos = wxMin(pos, max);
900 viewInfo.sbarH += sbarHjump;
901 viewInfo.sbarH = std::min(viewInfo.sbarH,
902 viewInfo.sbarTotal
903 - (wxInt64) PixelWidthBeforeTime(0.0) - viewInfo.sbarScreen);
904
905 if (pos != mHsbar->GetThumbPosition()) {
906 mHsbar->SetThumbPosition((int)pos);
908 }
909}
910
911
915void ProjectWindow::OnScrollLeftButton(wxScrollEvent & /*event*/)
916{
917 auto pProject = FindProject();
918 if (!pProject)
919 return;
920 auto &project = *pProject;
921 auto &viewInfo = ViewInfo::Get( project );
922 wxInt64 pos = mHsbar->GetThumbPosition();
923 // move at least one scroll increment
924 pos -= wxMax((wxInt64)(sbarHjump * viewInfo.sbarScale), 1);
925 pos = wxMax(pos, 0);
926 viewInfo.sbarH -= sbarHjump;
927 viewInfo.sbarH = std::max(viewInfo.sbarH,
928 - (wxInt64) PixelWidthBeforeTime(0.0));
929
930 if (pos != mHsbar->GetThumbPosition()) {
931 mHsbar->SetThumbPosition((int)pos);
932 DoScroll();
933 }
934}
935
939void ProjectWindow::OnScrollRightButton(wxScrollEvent & /*event*/)
940{
941 auto pProject = FindProject();
942 if (!pProject)
943 return;
944 auto &project = *pProject;
945 auto &viewInfo = ViewInfo::Get( project );
946 wxInt64 pos = mHsbar->GetThumbPosition();
947 // move at least one scroll increment
948 // use wxInt64 for calculation to prevent temporary overflow
949 pos += wxMax((wxInt64)(sbarHjump * viewInfo.sbarScale), 1);
950 wxInt64 max = mHsbar->GetRange() - mHsbar->GetThumbSize();
951 pos = wxMin(pos, max);
952 viewInfo.sbarH += sbarHjump;
953 viewInfo.sbarH = std::min(viewInfo.sbarH,
954 viewInfo.sbarTotal
955 - (wxInt64) PixelWidthBeforeTime(0.0) - viewInfo.sbarScreen);
956
957 if (pos != mHsbar->GetThumbPosition()) {
958 mHsbar->SetThumbPosition((int)pos);
959 DoScroll();
960 }
961}
962
963
965{
966 auto pProject = FindProject();
967 if (!pProject)
968 return false;
969 auto &project = *pProject;
970 auto &scrubber = Scrubber::Get( project );
971 auto &viewInfo = ViewInfo::Get( project );
972 if (viewInfo.bScrollBeyondZero)
973 return true;
974
975 if (scrubber.HasMark() ||
976 ProjectAudioIO::Get( project ).IsAudioActive()) {
977 if (mPlaybackScroller) {
978 auto mode = mPlaybackScroller->GetMode();
979 if (mode == PlaybackScroller::Mode::Pinned ||
981 return true;
982 }
983 }
984
985 return false;
986}
987
989{
990 auto pProject = FindProject();
991 if (!pProject)
992 return 0;
993 auto &project = *pProject;
994 auto &tracks = TrackList::Get( project );
995 auto &viewInfo = ViewInfo::Get( project );
996 if (!MayScrollBeyondZero())
997 return 0;
998 const double screen = viewInfo.GetScreenEndTime() - viewInfo.h;
999 return std::min(tracks.GetStartTime(), -screen);
1000}
1001
1002// PRL: Bug1197: we seem to need to compute all in double, to avoid differing results on Mac
1003// That's why ViewInfo::TimeRangeToPixelWidth was defined, with some regret.
1004double ProjectWindow::PixelWidthBeforeTime(double scrollto) const
1005{
1006 auto pProject = FindProject();
1007 if (!pProject)
1008 return 0;
1009 auto &project = *pProject;
1010 auto &viewInfo = ViewInfo::Get( project );
1011 const double lowerBound = ScrollingLowerBoundTime();
1012 return
1013 // Ignoring fisheye is correct here
1014 viewInfo.TimeRangeToPixelWidth(scrollto - lowerBound);
1015}
1016
1018{
1019 auto pProject = FindProject();
1020 if (!pProject)
1021 return;
1022 auto &project = *pProject;
1023 auto &viewInfo = ViewInfo::Get( project );
1024 const auto unscaled = PixelWidthBeforeTime(scrollto);
1025 const int max = mHsbar->GetRange() - mHsbar->GetThumbSize();
1026 const int pos =
1027 std::min(max,
1028 std::max(0,
1029 (int)(floor(0.5 + unscaled * viewInfo.sbarScale))));
1030 mHsbar->SetThumbPosition(pos);
1031 viewInfo.sbarH = floor(0.5 + unscaled - PixelWidthBeforeTime(0.0));
1032 viewInfo.sbarH = std::max(viewInfo.sbarH,
1033 - (wxInt64) PixelWidthBeforeTime(0.0));
1034 viewInfo.sbarH = std::min(viewInfo.sbarH,
1035 viewInfo.sbarTotal
1036 - (wxInt64) PixelWidthBeforeTime(0.0) - viewInfo.sbarScreen);
1037}
1038
1039//
1040// This method, like the other methods prefaced with TP, handles TrackPanel
1041// 'callback'.
1042//
1044{
1045 SetHorizontalThumb(scrollto);
1046
1047 // Call our Scroll method which updates our ViewInfo variables
1048 // to reflect the positions of the scrollbars
1049 DoScroll();
1050}
1051
1052//
1053// Scroll vertically. This is called for example by the mouse wheel
1054// handler in Track Panel. A positive argument makes the window
1055// scroll down, while a negative argument scrolls up.
1056//
1058{
1059 int oldPos = mVsbar->GetThumbPosition();
1060 int pos = oldPos + delta;
1061 int max = mVsbar->GetRange() - mVsbar->GetThumbSize();
1062
1063 // Can be negative in case of only one track
1064 if (max < 0)
1065 max = 0;
1066
1067 if (pos > max)
1068 pos = max;
1069 else if (pos < 0)
1070 pos = 0;
1071
1072 if (pos != oldPos)
1073 {
1074 mVsbar->SetThumbPosition(pos);
1075
1076 DoScroll();
1077 return true;
1078 }
1079 else
1080 return false;
1081}
1082
1084{
1085 auto pProject = FindProject();
1086 if (!pProject)
1087 return;
1088 auto &project = *pProject;
1089 auto &tracks = TrackList::Get( project );
1090 auto &trackPanel = GetProjectPanel( project );
1091 auto &viewInfo = ViewInfo::Get( project );
1092
1093 bool refresh = false;
1094 bool rescroll = false;
1095
1096 int totalHeight = TrackView::GetTotalHeight( tracks ) + 32;
1097
1098 auto panelWidth = viewInfo.GetTracksUsableWidth();
1099 auto panelHeight = viewInfo.GetHeight();
1100
1101 // (From Debian...at least I think this is the change corresponding
1102 // to this comment)
1103 //
1104 // (2.) GTK critical warning "IA__gtk_range_set_range: assertion
1105 // 'min < max' failed" because of negative numbers as result of window
1106 // size checking. Added a sanity check that straightens up the numbers
1107 // in edge cases.
1108 if (panelWidth < 0) {
1109 panelWidth = 0;
1110 }
1111 if (panelHeight < 0) {
1112 panelHeight = 0;
1113 }
1114
1115 auto LastTime = std::numeric_limits<double>::lowest();
1116 for (const Track *track : tracks) {
1117 // Iterate over pending changed tracks if present.
1118 track = track->SubstitutePendingChangedTrack().get();
1119 LastTime = std::max( LastTime, track->GetEndTime() );
1120 }
1121 LastTime =
1122 std::max(LastTime, viewInfo.selectedRegion.t1());
1123
1124 const double screen =
1125 viewInfo.GetScreenEndTime() - viewInfo.h;
1126 const double halfScreen = screen / 2.0;
1127
1128 // If we can scroll beyond zero,
1129 // Add 1/2 of a screen of blank space to the end
1130 // and another 1/2 screen before the beginning
1131 // so that any point within the union of the selection and the track duration
1132 // may be scrolled to the midline.
1133 // May add even more to the end, so that you can always scroll the starting time to zero.
1134 const double lowerBound = ScrollingLowerBoundTime();
1135 const double additional = MayScrollBeyondZero()
1136 ? -lowerBound + std::max(halfScreen, screen - LastTime)
1137 : screen / 4.0;
1138
1139 viewInfo.total = LastTime + additional;
1140
1141 // Don't remove time from total that's still on the screen
1142 viewInfo.total = std::max(viewInfo.total, viewInfo.h + screen);
1143
1144 if (viewInfo.h < lowerBound) {
1145 viewInfo.h = lowerBound;
1146 rescroll = true;
1147 }
1148
1149 viewInfo.sbarTotal = (wxInt64) (viewInfo.GetTotalWidth());
1150 viewInfo.sbarScreen = (wxInt64)(panelWidth);
1151 viewInfo.sbarH = (wxInt64) (viewInfo.GetBeforeScreenWidth());
1152
1153 // PRL: Can someone else find a more elegant solution to bug 812, than
1154 // introducing this boolean member variable?
1155 // Setting mVSbar earlier, int HandlXMLTag, didn't succeed in restoring
1156 // the vertical scrollbar to its saved position. So defer that till now.
1157 // mbInitializingScrollbar should be true only at the start of the life
1158 // of an AudacityProject reopened from disk.
1160 viewInfo.vpos = mVsbar->GetThumbPosition() * viewInfo.scrollStep;
1161 }
1163
1164 if (viewInfo.vpos >= totalHeight)
1165 viewInfo.vpos = totalHeight - 1;
1166 if (viewInfo.vpos < 0)
1167 viewInfo.vpos = 0;
1168
1169 bool oldhstate;
1170 bool oldvstate;
1171 bool newhstate =
1172 (viewInfo.GetScreenEndTime() - viewInfo.h) < viewInfo.total;
1173 bool newvstate = panelHeight < totalHeight;
1174
1175#ifdef __WXGTK__
1176 oldhstate = mHsbar->IsShown();
1177 oldvstate = mVsbar->IsShown();
1178 mHsbar->Show(newhstate);
1179 mVsbar->Show(panelHeight < totalHeight);
1180#else
1181 oldhstate = mHsbar->IsEnabled();
1182 oldvstate = mVsbar->IsEnabled();
1183 mHsbar->Enable(newhstate);
1184 mVsbar->Enable(panelHeight < totalHeight);
1185#endif
1186
1187 if (panelHeight >= totalHeight && viewInfo.vpos != 0) {
1188 viewInfo.vpos = 0;
1189
1190 refresh = true;
1191 rescroll = false;
1192 }
1193 if (!newhstate && viewInfo.sbarH != 0) {
1194 viewInfo.sbarH = 0;
1195
1196 refresh = true;
1197 rescroll = false;
1198 }
1199
1200 // wxScrollbar only supports int values but we need a greater range, so
1201 // we scale the scrollbar coordinates on demand. We only do this if we
1202 // would exceed the int range, so we can always use the maximum resolution
1203 // available.
1204
1205 // Don't use the full 2^31 max int range but a bit less, so rounding
1206 // errors in calculations do not overflow max int
1207 wxInt64 maxScrollbarRange = (wxInt64)(2147483647 * 0.999);
1208 if (viewInfo.sbarTotal > maxScrollbarRange)
1209 viewInfo.sbarScale = ((double)maxScrollbarRange) / viewInfo.sbarTotal;
1210 else
1211 viewInfo.sbarScale = 1.0; // use maximum resolution
1212
1213 {
1214 int scaledSbarH = (int)(viewInfo.sbarH * viewInfo.sbarScale);
1215 int scaledSbarScreen = (int)(viewInfo.sbarScreen * viewInfo.sbarScale);
1216 int scaledSbarTotal = (int)(viewInfo.sbarTotal * viewInfo.sbarScale);
1217 const int offset =
1218 (int)(floor(0.5 + viewInfo.sbarScale * PixelWidthBeforeTime(0.0)));
1219
1220 mHsbar->SetScrollbar(scaledSbarH + offset, scaledSbarScreen, scaledSbarTotal,
1221 scaledSbarScreen, TRUE);
1222 }
1223
1224 // Vertical scrollbar
1225 mVsbar->SetScrollbar(viewInfo.vpos / viewInfo.scrollStep,
1226 panelHeight / viewInfo.scrollStep,
1227 totalHeight / viewInfo.scrollStep,
1228 panelHeight / viewInfo.scrollStep, TRUE);
1229
1230 if (refresh || (rescroll &&
1231 (viewInfo.GetScreenEndTime() - viewInfo.h) < viewInfo.total)) {
1232 trackPanel.Refresh(false);
1233 }
1234
1235 MenuManager::Get( project ).UpdateMenus();
1236
1237 if (oldhstate != newhstate || oldvstate != newvstate) {
1238 UpdateLayout();
1239 }
1240}
1241
1243{
1244 auto pProject = FindProject();
1245 if (!pProject)
1246 return;
1247 auto &project = *pProject;
1248 auto &trackPanel = GetProjectPanel( project );
1249 auto &toolManager = ToolManager::Get( project );
1250
1251 // 1. Layout panel, to get widths of the docks.
1252 Layout();
1253 // 2. Layout toolbars to pack the toolbars correctly in docks which
1254 // are now the correct width.
1255 toolManager.LayoutToolBars();
1256 // 3. Layout panel, to resize docks, in particular reducing the height
1257 // of any empty docks, or increasing the height of docks that need it.
1258 Layout();
1259
1260 // Bug 2455
1261 // The commented out code below is to calculate a nice minimum size for
1262 // the window. However on Ubuntu when the window is minimised it leads to
1263 // an insanely tall window.
1264 // Using a fixed min size fixes that.
1265 // However there is still something strange when minimised, as once
1266 // UpdateLayout is called once, when minimised, it gets called repeatedly.
1267#if 0
1268 // Retrieve size of this projects window
1269 wxSize mainsz = GetSize();
1270
1271 // Retrieve position of the track panel to use as the size of the top
1272 // third of the window
1273 wxPoint tppos = ClientToScreen(trackPanel.GetParent()->GetPosition());
1274
1275 // Retrieve position of bottom dock to use as the size of the bottom
1276 // third of the window
1277 wxPoint sbpos = ClientToScreen(toolManager.GetBotDock()->GetPosition());
1278
1279 // The "+ 50" is the minimum height of the TrackPanel
1280 SetMinSize( wxSize(250, (mainsz.y - sbpos.y) + tppos.y + 50));
1281#endif
1282 SetMinSize( wxSize(250, 250));
1283 SetMaxSize( wxSize(20000, 20000));
1284}
1285
1287{
1288 // Activate events can fire during window teardown, so just
1289 // ignore them.
1290 if (mIsDeleting) {
1291 return;
1292 }
1293
1294 CallAfter( [this]{
1295
1296 if (mIsDeleting)
1297 return;
1298
1299 FixScrollbars();
1300 UpdateLayout();
1301
1302 });
1303}
1304
1305
1307{
1308 return mIconized;
1309}
1310
1312{
1313 return mTrackListWindow;
1314}
1315
1316wxSplitterWindow* ProjectWindow::GetContainerWindow() noexcept
1317{
1318 return mContainerWindow;
1319}
1320
1322{
1323 return mTopPanel;
1324}
1325
1327{
1328 wxRect defaultRect;
1329 GetDefaultWindowRect(&defaultRect);
1330
1331 SetSize(defaultRect.width, defaultRect.height);
1332}
1333
1335{
1336 auto pProject = FindProject();
1337 if (!pProject)
1338 return;
1339 auto &project = *pProject;
1340 enum { nWidths = nStatusBarFields + 1 };
1341 int widths[ nWidths ]{ 0 };
1342 widths[ rateStatusBarField ] = 150;
1343 const auto statusBar = GetStatusBar();
1344 const auto &functions = ProjectStatus::GetStatusWidthFunctions();
1345 // Start from 1 not 0
1346 // Specifying a first column always of width 0 was needed for reasons
1347 // I forget now
1348 for ( int ii = 1; ii <= nStatusBarFields; ++ii ) {
1349 int &width = widths[ ii ];
1350 for ( const auto &function : functions ) {
1351 auto results =
1352 function( project, static_cast< StatusBarField >( ii ) );
1353 for ( const auto &string : results.first ) {
1354 int w;
1355 statusBar->GetTextExtent(string.Translation(), &w, nullptr);
1356 width = std::max<int>( width, w + results.second );
1357 }
1358 }
1359 }
1360 // The main status field is not fixed width
1361 widths[ mainStatusBarField ] = -1;
1362 statusBar->SetStatusWidths( nWidths, widths );
1363}
1364
1366{
1367 (void)show;//compiler food
1368#ifdef __WXMAC__
1369 // Save the focus so we can restore it to whatever had it before since
1370 // showing a previously hidden toolbar will cause the focus to be set to
1371 // its frame. If this is not done it will appear that activation events
1372 // aren't being sent to the project window since they are actually being
1373 // delivered to the last tool frame shown.
1374 wxWindow *focused = FindFocus();
1375
1376 // Find all the floating toolbars, and show or hide them
1377 const auto &children = GetChildren();
1378 for(const auto &child : children) {
1379 if (auto frame = dynamic_cast<ToolFrame*>(child)) {
1380 if (!show) {
1381 frame->Hide();
1382 }
1383 else if (frame->GetBar() &&
1384 frame->GetBar()->IsVisible() ) {
1385 frame->Show();
1386 }
1387 }
1388 }
1389
1390 // Restore the focus if needed
1391 if (focused) {
1392 focused->SetFocus();
1393 }
1394#endif
1395}
1396
1397void ProjectWindow::OnIconize(wxIconizeEvent &event)
1398{
1399 //JKC: On Iconizing we get called twice. Don't know
1400 // why but it does no harm.
1401 // Should we be returning true/false rather than
1402 // void return? I don't know.
1403 mIconized = event.IsIconized();
1404
1405#if defined(__WXMAC__)
1406 // Readdresses bug 1431 since a crash could occur when restoring iconized
1407 // floating toolbars due to recursion (bug 2411).
1409 if( !mIconized )
1410 {
1411 Raise();
1412 }
1413#endif
1414
1415 // VisibileProjectCount seems to be just a counter for debugging.
1416 // It's not used outside this function.
1417 auto VisibleProjectCount = std::count_if(
1419 []( const AllProjects::value_type &ptr ){
1420 return !GetProjectFrame( *ptr ).IsIconized();
1421 }
1422 );
1423 event.Skip();
1424
1425 // This step is to fix part of Bug 2040, where the BackingPanel
1426 // size was not restored after we leave Iconized state.
1427
1428 // Queue up a resize event using OnShow so that we
1429 // refresh the track panel. But skip this, if we're iconized.
1430 if( mIconized )
1431 return;
1432 wxShowEvent Evt;
1433 OnShow( Evt );
1434}
1435
1436void ProjectWindow::OnMove(wxMoveEvent & event)
1437{
1438 if (!this->IsMaximized() && !this->IsIconized())
1439 SetNormalizedWindowState(this->GetRect());
1440 event.Skip();
1441}
1442
1443void ProjectWindow::OnSize(wxSizeEvent & event)
1444{
1445 // (From Debian)
1446 //
1447 // (3.) GTK critical warning "IA__gdk_window_get_origin: assertion
1448 // 'GDK_IS_WINDOW (window)' failed": Received events of type wxSizeEvent
1449 // on the main project window cause calls to "ClientToScreen" - which is
1450 // not available until the window is first shown. So the class has to
1451 // keep track of wxShowEvent events and inhibit those actions until the
1452 // window is first shown.
1453 if (mShownOnce) {
1454 HandleResize();
1455 if (!this->IsMaximized() && !this->IsIconized())
1456 SetNormalizedWindowState(this->GetRect());
1457 }
1458 event.Skip();
1459}
1460
1461void ProjectWindow::OnShow(wxShowEvent & event)
1462{
1463 // Remember that the window has been shown at least once
1464 mShownOnce = true;
1465
1466 // (From Debian...see also TrackPanel::OnTimer and AudacityTimer::Notify)
1467 //
1468 // Description: Workaround for wxWidgets bug: Reentry in clipboard
1469 // The wxWidgets bug http://trac.wxwidgets.org/ticket/16636 prevents
1470 // us from doing clipboard operations in wxShowEvent and wxTimerEvent
1471 // processing because those event could possibly be processed during
1472 // the (not sufficiently protected) Yield() of a first clipboard
1473 // operation, causing reentry. Audacity had a workaround in place
1474 // for this problem (the class "CaptureEvents"), which however isn't
1475 // applicable with wxWidgets 3.0 because it's based on changing the
1476 // gdk event handler, a change that would be overridden by wxWidgets's
1477 // own gdk event handler change.
1478 // Instead, as a NEW workaround, specifically protect those processings
1479 // of wxShowEvent and wxTimerEvent that try to do clipboard operations
1480 // from being executed within Yield(). This is done by delaying their
1481 // execution by posting pure wxWidgets events - which are never executed
1482 // during Yield().
1483 // Author: Martin Stegh fer <[email protected]>
1484 // Bug-Debian: https://bugs.debian.org/765341
1485
1486 // the actual creation/showing of the window).
1487 // Post the event instead of calling OnSize(..) directly. This ensures that
1488 // this is a pure wxWidgets event (no GDK event behind it) and that it
1489 // therefore isn't processed within the YieldFor(..) of the clipboard
1490 // operations (workaround for Debian bug #765341).
1491 // QueueEvent() will take ownership of the event
1492 GetEventHandler()->QueueEvent(safenew wxSizeEvent(GetSize()));
1493
1494 // Further processing by default handlers
1495 event.Skip();
1496}
1497
1501void ProjectWindow::OnToolBarUpdate(wxCommandEvent & event)
1502{
1503 HandleResize();
1504
1505 event.Skip(false); /* No need to propagate any further */
1506}
1507
1509{
1510 RedrawProject();
1511}
1512
1514{
1515 HandleResize();
1516 RedrawProject();
1517}
1518
1520{
1521 HandleResize();
1522 // RedrawProject(); // Should we do this here too?
1523}
1524
1526{
1528 auto pProject = FindProject();
1529 if (!pProject)
1530 return;
1531 auto &project = *pProject;
1532 const auto &name = ProjectFileIO::Get(project).GetProjectTitle();
1533 if (name != GetTitle()) {
1534 SetTitle(name);
1535 SetName(name); // to make the nvda screen reader read the correct title
1536 }
1537 }
1538}
1539
1540void ProjectWindow::OnScroll(wxScrollEvent & WXUNUSED(event))
1541{
1542 auto pProject = FindProject();
1543 if (!pProject)
1544 return;
1545 auto &project = *pProject;
1546 auto &viewInfo = ViewInfo::Get( project );
1547 const wxInt64 offset = PixelWidthBeforeTime(0.0);
1548 viewInfo.sbarH =
1549 (wxInt64)(mHsbar->GetThumbPosition() / viewInfo.sbarScale) - offset;
1550 DoScroll();
1551
1552#ifndef __WXMAC__
1553 // Bug2179
1554 // This keeps the time ruler in sync with horizontal scrolling, without
1555 // making an undesirable compilation dependency of this source file on
1556 // the ruler
1557 wxTheApp->ProcessIdle();
1558#endif
1559}
1560
1562{
1563 auto pProject = FindProject();
1564 if (!pProject)
1565 return;
1566 auto &project = *pProject;
1567 auto &trackPanel = GetProjectPanel( project );
1568 auto &viewInfo = ViewInfo::Get( project );
1569 const double lowerBound = ScrollingLowerBoundTime();
1570
1571 auto width = viewInfo.GetTracksUsableWidth();
1572 viewInfo.SetBeforeScreenWidth(viewInfo.sbarH, width, lowerBound);
1573
1574
1575 if (MayScrollBeyondZero()) {
1576 enum { SCROLL_PIXEL_TOLERANCE = 10 };
1577 if (std::abs(viewInfo.TimeToPosition(0.0, 0
1578 )) < SCROLL_PIXEL_TOLERANCE) {
1579 // Snap the scrollbar to 0
1580 viewInfo.h = 0;
1581 SetHorizontalThumb(0.0);
1582 }
1583 }
1584
1585 viewInfo.vpos = mVsbar->GetThumbPosition() * viewInfo.scrollStep;
1586
1587 //mchinen: do not always set this project to be the active one.
1588 //a project may autoscroll while playing in the background
1589 //I think this is okay since OnMouseEvent has one of these.
1590 //SetActiveProject(this);
1591
1592 if (!mAutoScrolling) {
1593 trackPanel.Refresh(false);
1594 }
1595}
1596
1597void ProjectWindow::OnMenu(wxCommandEvent & event)
1598{
1599#ifdef __WXMSW__
1600 // Bug 1642: We can arrive here with bogus menu IDs, which we
1601 // proceed to process. So if bogus, don't.
1602 // The bogus menu IDs are probably generated by controls on the TrackPanel,
1603 // such as the Project Rate.
1604 // 17000 is the magic number at which we start our menu.
1605 // This code would probably NOT be OK on Mac, since we assign
1606 // some specific ID numbers.
1607 if( event.GetId() < 17000){
1608 event.Skip();
1609 return;
1610 }
1611#endif
1612 auto pProject = FindProject();
1613 if (!pProject)
1614 return;
1615 auto &project = *pProject;
1616 auto &commandManager = CommandManager::Get( project );
1617 bool handled = commandManager.HandleMenuID( project,
1618 event.GetId(), MenuManager::Get( project ).GetUpdateFlags(),
1619 false);
1620
1621 if (handled)
1622 event.Skip(false);
1623 else{
1624 event.ResumePropagation( 999 );
1625 event.Skip(true);
1626 }
1627}
1628
1629void ProjectWindow::OnUpdateUI(wxUpdateUIEvent & WXUNUSED(event))
1630{
1631 auto pProject = FindProject();
1632 if (!pProject)
1633 return;
1634 auto &project = *pProject;
1635 MenuManager::Get( project ).UpdateMenus();
1636}
1637
1638void ProjectWindow::OnActivate(wxActivateEvent & event)
1639{
1640 // Activate events can fire during window teardown, so just
1641 // ignore them.
1642 if (IsBeingDeleted()) {
1643 return;
1644 }
1645
1646 auto pProject = FindProject();
1647 if (!pProject)
1648 return;
1649 auto &project = *pProject;
1650
1651 mActive = event.GetActive();
1652
1653 // Under Windows, focus can be "lost" when returning to
1654 // Audacity from a different application.
1655 //
1656 // This was observed by minimizing all windows using WINDOWS+M and
1657 // then ALT+TAB to return to Audacity. Focus will be given to the
1658 // project window frame which is not at all useful.
1659 //
1660 // So, we use ToolManager's observation of focus changes in a wxEventFilter.
1661 // Then, when we receive the
1662 // activate event, we restore that focus to the child or the track
1663 // panel if no child had the focus (which probably should never happen).
1664 if (mActive) {
1665 auto &toolManager = ToolManager::Get( project );
1666 SetActiveProject( &project );
1667 if ( ! toolManager.RestoreFocus() )
1668 GetProjectPanel( project ).SetFocus();
1669 }
1670 event.Skip();
1671}
1672
1674{
1675 return mActive;
1676}
1677
1678void ProjectWindow::OnMouseEvent(wxMouseEvent & event)
1679{
1680 auto pProject = FindProject();
1681 if (!pProject)
1682 return;
1683 auto &project = *pProject;
1684 if (event.ButtonDown())
1685 SetActiveProject( &project );
1686}
1687
1689{
1690 auto pProject = FindProject();
1691 if (!pProject)
1692 return;
1693 auto &project = *pProject;
1694 auto &tracks = TrackList::Get( project );
1695 auto &trackPanel = GetProjectPanel( project );
1696
1697 DoZoomFit();
1698
1699 trackPanel.SetFocus();
1700 if (!pTrack)
1701 pTrack = *tracks.Selected().begin();
1702 if (!pTrack)
1703 pTrack = *tracks.Any().begin();
1704 if (pTrack) {
1705 TrackFocus::Get(project).Set(pTrack);
1706 pTrack->EnsureVisible();
1707 }
1708}
1709
1710// Utility function called by other zoom methods
1711void ProjectWindow::Zoom(double level)
1712{
1713 auto pProject = FindProject();
1714 if (!pProject)
1715 return;
1716 auto &project = *pProject;
1717 auto &viewInfo = ViewInfo::Get( project );
1718 viewInfo.SetZoom(level);
1719 FixScrollbars();
1720 // See if we can center the selection on screen, and have it actually fit.
1721 // tOnLeft is the amount of time we would need before the selection left edge to center it.
1722 float t0 = viewInfo.selectedRegion.t0();
1723 float t1 = viewInfo.selectedRegion.t1();
1724 float tAvailable = viewInfo.GetScreenEndTime() - viewInfo.h;
1725 float tOnLeft = (tAvailable - t0 + t1)/2.0;
1726 // Bug 1292 (Enh) is effectively a request to do this scrolling of the selection into view.
1727 // If tOnLeft is positive, then we have room for the selection, so scroll to it.
1728 if( tOnLeft >=0 )
1729 TP_ScrollWindow( t0-tOnLeft);
1730}
1731
1732// Utility function called by other zoom methods
1733void ProjectWindow::ZoomBy(double multiplier)
1734{
1735 auto pProject = FindProject();
1736 if (!pProject)
1737 return;
1738 auto &project = *pProject;
1739 auto &viewInfo = ViewInfo::Get( project );
1740 viewInfo.ZoomBy(multiplier);
1741 FixScrollbars();
1742}
1743
1745// This method 'rewinds' the track, by setting the cursor to 0 and
1746// scrolling the window to fit 0 on the left side of it
1747// (maintaining current zoom).
1748// If shift is held down, it will extend the left edge of the
1749// selection to 0 (holding right edge constant), otherwise it will
1750// move both left and right edge of selection to 0
1753{
1754 auto pProject = FindProject();
1755 if (!pProject)
1756 return;
1757 auto &project = *pProject;
1758 auto &viewInfo = ViewInfo::Get( project );
1759 viewInfo.selectedRegion.setT0(0, false);
1760 if (!shift)
1761 viewInfo.selectedRegion.setT1(0);
1762
1763 TP_ScrollWindow(0);
1764}
1765
1766
1768// This method 'fast-forwards' the track, by setting the cursor to
1769// the end of the samples on the selected track and scrolling the
1770// window to fit the end on its right side (maintaining current zoom).
1771// If shift is held down, it will extend the right edge of the
1772// selection to the end (holding left edge constant), otherwise it will
1773// move both left and right edge of selection to the end
1776{
1777 auto pProject = FindProject();
1778 if (!pProject)
1779 return;
1780 auto &project = *pProject;
1781 auto &tracks = TrackList::Get( project );
1782 auto &viewInfo = ViewInfo::Get( project );
1783 double len = tracks.GetEndTime();
1784
1785 viewInfo.selectedRegion.setT1(len, false);
1786 if (!shift)
1787 viewInfo.selectedRegion.setT0(len);
1788
1789 // Make sure the end of the track is visible
1790 ScrollIntoView(len);
1791}
1792
1793// TrackPanel callback method
1795{
1796 OnScrollLeft();
1797}
1798
1799// TrackPanel callback method
1801{
1802 OnScrollRight();
1803}
1804
1805// TrackPanel callback method
1807{
1808 FixScrollbars();
1809}
1810
1812{
1813 HandleResize();
1814}
1815
1817: mProject(project)
1818{
1819}
1820
1822{
1823 auto gAudioIO = AudioIO::Get();
1824 mRecentStreamTime = gAudioIO->GetStreamTime();
1825
1826 auto cleanup = finally([&]{
1827 // Propagate the message to other listeners bound to this
1828 this->Publish({});
1829 });
1830
1831 if(!ProjectAudioIO::Get( *mProject ).IsAudioActive())
1832 return;
1833 else if (mMode == Mode::Refresh) {
1834 // PRL: see comments in Scrubbing.cpp for why this is sometimes needed.
1835 // These unnecessary refreshes cause wheel rotation events to be delivered more uniformly
1836 // to the application, so scrub speed control is smoother.
1837 // (So I see at least with OS 10.10 and wxWidgets 3.0.2.)
1838 // Is there another way to ensure that than by refreshing?
1839 auto &trackPanel = GetProjectPanel( *mProject );
1840 trackPanel.Refresh(false);
1841 }
1842 else if (mMode != Mode::Off) {
1843 // Pan the view, so that we put the play indicator at some fixed
1844 // fraction of the window width.
1845
1846 auto &viewInfo = ViewInfo::Get( *mProject );
1847 auto &trackPanel = GetProjectPanel( *mProject );
1848 const int posX = viewInfo.TimeToPosition(mRecentStreamTime);
1849 auto width = viewInfo.GetTracksUsableWidth();
1850 int deltaX;
1851 switch (mMode)
1852 {
1853 default:
1854 wxASSERT(false);
1855 /* fallthru */
1856 case Mode::Pinned:
1857 deltaX =
1859 break;
1860 case Mode::Right:
1861 deltaX = posX - width; break;
1862 }
1863 viewInfo.h =
1864 viewInfo.OffsetTimeByPixels(viewInfo.h, deltaX, true);
1865 if (!ProjectWindow::Get( *mProject ).MayScrollBeyondZero())
1866 // Can't scroll too far left
1867 viewInfo.h = std::max(0.0, viewInfo.h);
1868 trackPanel.Refresh(false);
1869 }
1870}
1871
1872void ProjectWindow::ZoomInByFactor( double ZoomFactor )
1873{
1874 auto pProject = FindProject();
1875 if (!pProject)
1876 return;
1877 auto &project = *pProject;
1878 auto &viewInfo = ViewInfo::Get( project );
1879
1880 auto gAudioIO = AudioIO::Get();
1881 // LLL: Handling positioning differently when audio is
1882 // actively playing. Don't do this if paused.
1883 if (gAudioIO->IsStreamActive(
1884 ProjectAudioIO::Get( project ).GetAudioIOToken()) &&
1885 !gAudioIO->IsPaused()){
1886 ZoomBy(ZoomFactor);
1887 ScrollIntoView(gAudioIO->GetStreamTime());
1888 return;
1889 }
1890
1891 // DMM: Here's my attempt to get logical zooming behavior
1892 // when there's a selection that's currently at least
1893 // partially on-screen
1894
1895 const double endTime = viewInfo.GetScreenEndTime();
1896 const double duration = endTime - viewInfo.h;
1897
1898 bool selectionIsOnscreen =
1899 (viewInfo.selectedRegion.t0() < endTime) &&
1900 (viewInfo.selectedRegion.t1() >= viewInfo.h);
1901
1902 bool selectionFillsScreen =
1903 (viewInfo.selectedRegion.t0() < viewInfo.h) &&
1904 (viewInfo.selectedRegion.t1() > endTime);
1905
1906 if (selectionIsOnscreen && !selectionFillsScreen) {
1907 // Start with the center of the selection
1908 double selCenter = (viewInfo.selectedRegion.t0() +
1909 viewInfo.selectedRegion.t1()) / 2;
1910
1911 // If the selection center is off-screen, pick the
1912 // center of the part that is on-screen.
1913 if (selCenter < viewInfo.h)
1914 selCenter = viewInfo.h +
1915 (viewInfo.selectedRegion.t1() - viewInfo.h) / 2;
1916 if (selCenter > endTime)
1917 selCenter = endTime -
1918 (endTime - viewInfo.selectedRegion.t0()) / 2;
1919
1920 // Zoom in
1921 ZoomBy(ZoomFactor);
1922 const double newDuration =
1923 viewInfo.GetScreenEndTime() - viewInfo.h;
1924
1925 // Recenter on selCenter
1926 TP_ScrollWindow(selCenter - newDuration / 2);
1927 return;
1928 }
1929
1930
1931 double origLeft = viewInfo.h;
1932 double origWidth = duration;
1933 ZoomBy(ZoomFactor);
1934
1935 const double newDuration =
1936 viewInfo.GetScreenEndTime() - viewInfo.h;
1937 double newh = origLeft + (origWidth - newDuration) / 2;
1938
1939 // MM: Commented this out because it was confusing users
1940 /*
1941 // make sure that the *right-hand* end of the selection is
1942 // no further *left* than 1/3 of the way across the screen
1943 if (viewInfo.selectedRegion.t1() < newh + viewInfo.screen / 3)
1944 newh = viewInfo.selectedRegion.t1() - viewInfo.screen / 3;
1945
1946 // make sure that the *left-hand* end of the selection is
1947 // no further *right* than 2/3 of the way across the screen
1948 if (viewInfo.selectedRegion.t0() > newh + viewInfo.screen * 2 / 3)
1949 newh = viewInfo.selectedRegion.t0() - viewInfo.screen * 2 / 3;
1950 */
1951
1952 TP_ScrollWindow(newh);
1953}
1954
1955void ProjectWindow::ZoomOutByFactor( double ZoomFactor )
1956{
1957 auto pProject = FindProject();
1958 if (!pProject)
1959 return;
1960 auto &project = *pProject;
1961 auto &viewInfo = ViewInfo::Get( project );
1962
1963 //Zoom() may change these, so record original values:
1964 const double origLeft = viewInfo.h;
1965 const double origWidth = viewInfo.GetScreenEndTime() - origLeft;
1966
1967 ZoomBy(ZoomFactor);
1968 const double newWidth = viewInfo.GetScreenEndTime() - viewInfo.h;
1969
1970 const double newh = origLeft + (origWidth - newWidth) / 2;
1971 // newh = (newh > 0) ? newh : 0;
1972 TP_ScrollWindow(newh);
1973}
1974
1976{
1977 auto pProject = FindProject();
1978 if (!pProject)
1979 return 1.0;
1980 auto &project = *pProject;
1981 auto &tracks = TrackList::Get( project );
1982 auto &viewInfo = ViewInfo::Get( project );
1983
1984 const double end = tracks.GetEndTime();
1985 const double start = viewInfo.bScrollBeyondZero
1986 ? std::min( tracks.GetStartTime(), 0.0)
1987 : 0;
1988 const double len = end - start;
1989
1990 if (len <= 0.0)
1991 return viewInfo.GetZoom();
1992
1993 auto w = viewInfo.GetTracksUsableWidth();
1994 w -= 10;
1995 return w/len;
1996}
1997
1999{
2000 auto pProject = FindProject();
2001 if (!pProject)
2002 return;
2003 auto &project = *pProject;
2004 auto &viewInfo = ViewInfo::Get( project );
2005 auto &tracks = TrackList::Get( project );
2006 auto &window = *this;
2007
2008 const double start = viewInfo.bScrollBeyondZero
2009 ? std::min(tracks.GetStartTime(), 0.0)
2010 : 0;
2011
2012 window.Zoom( window.GetZoomOfToFit() );
2013 window.TP_ScrollWindow(start);
2014}
2015
2017[]( wxWindow &window ){
2018 auto pProjectWindow = dynamic_cast< ProjectWindow* >( &window );
2019 return pProjectWindow ? pProjectWindow->GetTopPanel() : nullptr;
2020} };
void SetActiveProject(AudacityProject *project)
Handle changing of active project and keep global project pointer.
EVT_MENU(OnSetPlayRegionToSelectionID, AdornedRulerPanel::OnSetPlayRegionToSelection) EVT_COMMAND(OnTogglePinnedStateID
END_EVENT_TABLE()
int min(int a, int b)
const TranslatableString name
Definition: Distortion.cpp:76
#define _(s)
Definition: Internat.h:73
EventMonitor monitor
EVT_COMMAND(wxID_ANY, EVT_FREQUENCYTEXTCTRL_UPDATED, LabelDialog::OnFreqUpdate) LabelDialog
Definition: LabelDialog.cpp:89
#define safenew
Definition: MemoryX.h:10
ProjectFileIOMessage
Subscribe to ProjectFileIO to receive messages; always in idle time.
Definition: ProjectFileIO.h:49
@ ProjectTitleChange
A normal occurrence.
StatusBarField
Definition: ProjectStatus.h:24
@ mainStatusBarField
Definition: ProjectStatus.h:26
@ nStatusBarFields
Definition: ProjectStatus.h:29
@ rateStatusBarField
Definition: ProjectStatus.h:27
IntSetting ProjectWindowY
bool CornersOnScreen(wxRect &r)
IntSetting ProjectWindowX
void GetDefaultWindowRect(wxRect *defRect)
IntSetting ProjectWindowWidth
BoolSetting ProjectWindowIconized
bool IsWindowAccessible(wxRect *requestedRect)
BoolSetting ProjectWindowMaximized
@ VSBarID
@ HSBarID
@ NextID
@ FirstID
const int sbarHjump
IntSetting ProjectWindowNormalHeight
IntSetting ProjectWindowHeight
IntSetting ProjectWindowNormalX
void GetNextWindowPlacement(wxRect *nextRect, bool *pMaximized, bool *pIconized)
static ToolManager::TopPanelHook::Scope scope
int ScreenContaining(wxRect &r)
IntSetting ProjectWindowNormalWidth
IntSetting ProjectWindowNormalY
AUDACITY_DLL_API wxWindow & GetProjectPanel(AudacityProject &project)
Get the main sub-window of the project frame that displays track data.
AUDACITY_DLL_API wxFrame & GetProjectFrame(AudacityProject &project)
Get the top-level window associated with the project (as a wxFrame only, when you do not need to use ...
AUDACITY_DLL_API AttachedWindows & GetAttachedWindows(AudacityProject &project)
accessors for certain important windows associated with each project
THEME_API Theme theTheme
Definition: Theme.cpp:82
int id
const_reverse_iterator rend() const
Definition: Project.cpp:37
const_iterator end() const
Definition: Project.cpp:27
Container::value_type value_type
Definition: Project.h:57
const_iterator begin() const
Definition: Project.cpp:22
const_reverse_iterator rbegin() const
Definition: Project.cpp:32
bool empty() const
Definition: Project.h:47
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 AudioIO * Get()
Definition: AudioIO.cpp:147
This specialization of Setting for bool adds a Toggle method to negate the saved value.
Definition: Prefs.h:339
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:333
Subclass & Get(const RegisteredFactory &key)
Get reference to an attachment, creating on demand if not present, down-cast it to Subclass.
Definition: ClientData.h:309
CommandContext provides additional information to an 'Apply()' command. It provides the project,...
AudacityProject & project
static CommandManager & Get(AudacityProject &project)
typename GlobalVariable< TopPanelHook, const std::function< wxWindow * >, nullptr, Options... >::Scope Scope
Specialization of Setting for int.
Definition: Prefs.h:349
static MenuManager & Get(AudacityProject &project)
Definition: Menus.cpp:69
void UpdateMenus(bool checkActive=true)
Definition: Menus.cpp:581
CommandFlag GetUpdateFlags(bool checkActive=false) const
Definition: Menus.cpp:539
Subscription Subscribe(Callback callback)
Connect a callback to the Publisher; later-connected are called earlier.
Definition: Observer.h:199
CallbackReturn Publish(const ProjectWindowDestroyedMessage &message)
Send a message to connected callbacks.
Definition: Observer.h:207
bool IsAudioActive() const
int GetAudioIOToken() const
static ProjectAudioIO & Get(AudacityProject &project)
const wxString & GetProjectTitle() const
Definition: ProjectFileIO.h:79
static ProjectFileIO & Get(AudacityProject &project)
static ProjectSnap & Get(AudacityProject &project)
Definition: ProjectSnap.cpp:27
static const StatusWidthFunctions & GetStatusWidthFunctions()
PlaybackScroller(AudacityProject *project)
A top-level window associated with a project.
std::shared_ptr< AudacityProject > FindProject()
A top-level window associated with a project, and handling scrollbars and zooming.
Definition: ProjectWindow.h:41
void OnToolBarUpdate(wxCommandEvent &event)
void ZoomAfterImport(Track *pTrack)
wxScrollBar * mHsbar
void Rewind(bool shift)
void ZoomOutByFactor(double ZoomFactor)
wxSplitterWindow * mContainerWindow
void ZoomBy(double multiplier)
static ProjectWindow & Get(AudacityProject &project)
wxPanel * mTopPanel
void SetHorizontalThumb(double scrollto)
void TP_ScrollLeft() override
void UpdatePrefs() override
void OnActivate(wxActivateEvent &event)
void ApplyUpdatedTheme()
double GetZoomOfToFit() const
void OnScrollLeftButton(wxScrollEvent &event)
void TP_HandleResize() override
void OnUpdateUI(wxUpdateUIEvent &event)
void Zoom(double level)
bool IsActive() override
wxPanel * GetTopPanel() noexcept
Top panel contains project-related controls and tools.
void RedrawProject(const bool bForceWaveTracks=false)
static ProjectWindow * Find(AudacityProject *pProject)
Observer::Subscription mThemeChangeSubscription
wxWindow * GetTrackListWindow() noexcept
Track list window is the parent container for TrackPanel.
double ScrollingLowerBoundTime() const
void OnMove(wxMoveEvent &event)
wxSplitterWindow * GetContainerWindow() noexcept
Container is a parent window for both effects panel and track list windows.
double PixelWidthBeforeTime(double scrollto) const
void SetNormalizedWindowState(wxRect pSizeAndLocation)
bool MayScrollBeyondZero() const
void OnMouseEvent(wxMouseEvent &event)
bool mbInitializingScrollbar
void ScrollIntoView(double pos)
~ProjectWindow() override
bool IsBeingDeleted() const
Definition: ProjectWindow.h:63
void OnScrollRightButton(wxScrollEvent &event)
void OnScroll(wxScrollEvent &event)
void OnUndoPushedModified()
std::unique_ptr< PlaybackScroller > mPlaybackScroller
void OnSize(wxSizeEvent &event)
void UpdateStatusWidths()
void OnProjectTitleChange(ProjectFileIOMessage)
void FinishAutoScroll()
static void OnResetWindow(const CommandContext &context)
void OnThemeChange(struct ThemeChangeMessage)
Observer::Subscription mTitleChangeSubcription
void SkipEnd(bool shift)
bool TP_ScrollUpDown(int delta) override
Observer::Subscription mUndoSubscription
void ZoomInByFactor(double ZoomFactor)
void TP_ScrollRight() override
void TP_ScrollWindow(double scrollto) override
wxScrollBar * mVsbar
void OnMenu(wxCommandEvent &event)
void TP_RedrawScrollbars() override
bool IsIconized() const override
Observer::Subscription mSnappingChangedSubscription
void OnShow(wxShowEvent &event)
wxWindow * mTrackListWindow
ProjectWindow(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size, AudacityProject &project)
void OnIconize(wxIconizeEvent &event)
void MacShowUndockedToolbars(bool show)
void HandleScrollWheel(int steps)
bool IsScrollScrubbing() const
Definition: Scrubbing.h:93
bool IsScrubbing() const
static Scrubber & Get(AudacityProject &project)
Definition: Scrubbing.cpp:187
bool ReadWithDefault(T *pVar, const T &defaultValue) const
overload of ReadWithDefault returning a boolean that is true if the value was previously defined *‍/
Definition: Prefs.h:206
bool Read(T *pVar) const
overload of Read returning a boolean that is true if the value was previously defined *‍/
Definition: Prefs.h:200
wxColour & Colour(int iIndex)
class ToolFrame
Definition: ToolManager.h:190
static ToolManager & Get(AudacityProject &project)
Track * Get()
Abstract base class for an object holding data associated with points on a time axis.
Definition: Track.h:162
void EnsureVisible(bool modifyState=false)
Definition: Track.cpp:98
bool Any() const
Definition: Track.cpp:299
bool empty() const
Definition: Track.cpp:917
double GetEndTime() const
Definition: Track.cpp:960
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:385
static int GetTotalHeight(const TrackList &list)
Definition: TrackView.cpp:47
static double GetPinnedHeadPositionPreference()
static UndoManager & Get(AudacityProject &project)
Definition: UndoManager.cpp:71
static ViewInfo & Get(AudacityProject &project)
Definition: ViewInfo.cpp:235
A Track that contains audio waveform data.
Definition: WaveTrack.h:51
An alternative to using wxWindowAccessible, which in wxWidgets 3.1.1 contained GetParent() which was ...
ScrollBar(wxWindow *parent, wxWindowID id, long style)
void CallAfter(Action action)
Schedule an action to be done later, and in the main thread.
Definition: BasicUI.cpp:208
std::unique_ptr< WindowPlacement > FindFocus()
Find the window that is accepting keyboard input, if any.
Definition: BasicUI.h:343
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 OnCloseWindow(wxCloseEvent &e)
AttachedWindows::RegisteredFactory sProjectWindowKey
std::optional< PreferredSystemAppearance > appearance
Definition: Theme.h:112
Type of message published by UndoManager.
Definition: UndoManager.h:55
enum UndoRedoMessage::Type type