Audacity 3.2.0
NavigationMenus.cpp
Go to the documentation of this file.
1
2
3#include "../CommonCommandFlags.h"
4#include "Prefs.h"
5#include "Project.h"
6#include "ProjectHistory.h"
7#include "../ProjectWindow.h"
8#include "../ProjectWindows.h"
9#include "../RealtimeEffectPanel.h"
10#include "Track.h"
11#include "SelectionState.h"
12#include "../TrackPanel.h"
13#include "../TrackPanelAx.h"
14#include "../commands/CommandContext.h"
15#include "../commands/CommandManager.h"
16#include "../toolbars/ToolManager.h"
17#include "../widgets/AButton.h"
18#include "../widgets/ASlider.h"
19#include "../widgets/MeterPanel.h"
20
21// private helper classes and functions
22namespace {
23
24void NextOrPrevFrame(AudacityProject &project, bool forward)
25{
26 // Focus won't take in a dock unless at least one descendant window
27 // accepts focus. Tell controls to take focus for the duration of this
28 // function, only. Outside of this, they won't steal the focus when
29 // clicked.
30 auto temp1 = AButton::TemporarilyAllowFocus();
31 auto temp2 = ASlider::TemporarilyAllowFocus();
33
34 std::vector<wxWindow*> seq;
35 // Skip docks that are empty (Bug 1564).
36 if(!ToolManager::Get(project).GetTopDock()->GetChildren().IsEmpty())
37 seq.push_back(ProjectWindow::Get( project ).GetTopPanel());
38 seq.push_back(&TrackPanel::Get( project ));
39 seq.push_back(&RealtimeEffectPanel::Get(project));
40 if(!ToolManager::Get( project ).GetBotDock()->GetChildren().IsEmpty())
41 seq.push_back(ToolManager::Get( project ).GetBotDock());
42
43 auto FindAncestor = [&]() {
44 wxWindow *pWindow = wxWindow::FindFocus();
45 while (pWindow)
46 {
47 auto it = std::find(seq.cbegin(), seq.cend(), pWindow);
48 if(it != seq.cend())
49 return static_cast<size_t>(std::distance(seq.cbegin(), it));
50 pWindow = pWindow->GetParent();
51 }
52 return seq.size();
53 };
54
55 const auto idx = FindAncestor();
56 if (idx == seq.size())
57 return;
58
59 auto idx2 = idx;
60 const auto increment = (forward ? 1 : static_cast<int>(seq.size()) - 1);
61
62 while( idx != (idx2 = (idx2 + increment) % seq.size()) ) {
63 wxWindow *toFocus = seq[idx2];
64 if(!toFocus->IsShown())
65 continue;
66
67 toFocus->SetFocus();
68 if ( FindAncestor() == idx2 )
69 // The focus took!
70 break;
71 }
72}
73
76 AudacityProject &project, bool shift, bool circularTrackNavigation )
77{
78 auto &projectHistory = ProjectHistory::Get( project );
79 auto &trackFocus = TrackFocus::Get( project );
80 auto &tracks = TrackList::Get( project );
81 auto &selectionState = SelectionState::Get( project );
82
83 auto t = trackFocus.Get();
84 if( t == NULL ) // if there isn't one, focus on last
85 {
86 t = *tracks.Any().rbegin();
87 trackFocus.Set( t );
88 if (t)
89 t->EnsureVisible( true );
90 return;
91 }
92
93 Track* p = NULL;
94 bool tSelected = false;
95 bool pSelected = false;
96 if( shift )
97 {
98 p = * -- tracks.FindLeader( t ); // Get previous track
99 if( p == NULL ) // On first track
100 {
101 // JKC: wxBell() is probably for accessibility, so a blind
102 // user knows they were at the top track.
103 wxBell();
104 if( circularTrackNavigation )
105 p = *tracks.Any().rbegin();
106 else
107 {
108 t->EnsureVisible();
109 return;
110 }
111 }
112 tSelected = t->GetSelected();
113 if (p)
114 pSelected = p->GetSelected();
115 if( tSelected && pSelected )
116 {
117 selectionState.SelectTrack
118 ( *t, false, false );
119 trackFocus.Set( p ); // move focus to next track up
120 if (p)
121 p->EnsureVisible( true );
122 return;
123 }
124 if( tSelected && !pSelected )
125 {
126 selectionState.SelectTrack
127 ( *p, true, false );
128 trackFocus.Set( p ); // move focus to next track up
129 if (p)
130 p->EnsureVisible( true );
131 return;
132 }
133 if( !tSelected && pSelected )
134 {
135 selectionState.SelectTrack
136 ( *p, false, false );
137 trackFocus.Set( p ); // move focus to next track up
138 if (p)
139 p->EnsureVisible( true );
140 return;
141 }
142 if( !tSelected && !pSelected )
143 {
144 selectionState.SelectTrack
145 ( *t, true, false );
146 trackFocus.Set( p ); // move focus to next track up
147 if (p)
148 p->EnsureVisible( true );
149 return;
150 }
151 }
152 else
153 {
154 p = * -- tracks.FindLeader( t ); // Get previous track
155 if( p == NULL ) // On first track so stay there?
156 {
157 wxBell();
158 if( circularTrackNavigation )
159 {
160 auto range = tracks.Leaders();
161 p = * range.rbegin(); // null if range is empty
162 trackFocus.Set( p ); // Wrap to the last track
163 if (p)
164 p->EnsureVisible( true );
165 return;
166 }
167 else
168 {
169 t->EnsureVisible();
170 return;
171 }
172 }
173 else
174 {
175 trackFocus.Set( p ); // move focus to next track up
176 p->EnsureVisible( true );
177 return;
178 }
179 }
180}
181
186 AudacityProject &project, bool shift, bool circularTrackNavigation )
187{
188 auto &projectHistory = ProjectHistory::Get( project );
189 auto &trackFocus = TrackFocus::Get( project );
190 auto &tracks = TrackList::Get( project );
191 auto &selectionState = SelectionState::Get( project );
192
193 auto t = trackFocus.Get(); // Get currently focused track
194 if( t == NULL ) // if there isn't one, focus on first
195 {
196 t = *tracks.Any().begin();
197 trackFocus.Set( t );
198 if (t)
199 t->EnsureVisible( true );
200 return;
201 }
202
203 if( shift )
204 {
205 auto n = * ++ tracks.FindLeader( t ); // Get next track
206 if( n == NULL ) // On last track so stay there
207 {
208 wxBell();
209 if( circularTrackNavigation )
210 n = *tracks.Any().begin();
211 else
212 {
213 t->EnsureVisible();
214 return;
215 }
216 }
217 auto tSelected = t->GetSelected();
218 auto nSelected = n->GetSelected();
219 if( tSelected && nSelected )
220 {
221 selectionState.SelectTrack
222 ( *t, false, false );
223 trackFocus.Set( n ); // move focus to next track down
224 if (n)
225 n->EnsureVisible( true );
226 return;
227 }
228 if( tSelected && !nSelected )
229 {
230 selectionState.SelectTrack
231 ( *n, true, false );
232 trackFocus.Set( n ); // move focus to next track down
233 if (n)
234 n->EnsureVisible( true );
235 return;
236 }
237 if( !tSelected && nSelected )
238 {
239 selectionState.SelectTrack
240 ( *n, false, false );
241 trackFocus.Set( n ); // move focus to next track down
242 if (n)
243 n->EnsureVisible( true );
244 return;
245 }
246 if( !tSelected && !nSelected )
247 {
248 selectionState.SelectTrack
249 ( *t, true, false );
250 trackFocus.Set( n ); // move focus to next track down
251 if (n)
252 n->EnsureVisible( true );
253 return;
254 }
255 }
256 else
257 {
258 auto n = * ++ tracks.FindLeader( t ); // Get next track
259 if( n == NULL ) // On last track so stay there
260 {
261 wxBell();
262 if( circularTrackNavigation )
263 {
264 n = *tracks.Any().begin();
265 trackFocus.Set( n ); // Wrap to the first track
266 if (n)
267 n->EnsureVisible( true );
268 return;
269 }
270 else
271 {
272 t->EnsureVisible();
273 return;
274 }
275 }
276 else
277 {
278 trackFocus.Set( n ); // move focus to next track down
279 n->EnsureVisible( true );
280 return;
281 }
282 }
283}
284
285}
286
289
290// exported helper functions
291// none
292
293// Menu handler functions
294
296 : CommandHandlerObject // MUST be the first base class!
299{
300
301void OnPrevWindow(const CommandContext &context)
302{
303 auto &project = context.project;
304 auto &window = GetProjectFrame( project );
305 auto isEnabled = window.IsEnabled();
306
307 wxWindow *w = wxGetTopLevelParent(wxWindow::FindFocus());
308 const auto & list = window.GetChildren();
309 auto iter = list.rbegin(), end = list.rend();
310
311 // If the project window has the current focus, start the search with the
312 // last child
313 if (w == &window)
314 {
315 }
316 // Otherwise start the search with the current window's previous sibling
317 else
318 {
319 while (iter != end && *iter != w)
320 ++iter;
321 if (iter != end)
322 ++iter;
323 }
324
325 // Search for the previous toplevel window
326 for (; iter != end; ++iter)
327 {
328 // If it's a toplevel and is visible (we have come hidden windows), then
329 // we're done
330 w = *iter;
331 if (w->IsTopLevel() && w->IsShown() && isEnabled)
332 {
333 break;
334 }
335 }
336
337 // Ran out of siblings, so make the current project active
338 if ((iter == end) && isEnabled)
339 {
340 w = &window;
341 }
342
343 // And make sure it's on top (only for floating windows...project window will
344 // not raise)
345 // (Really only works on Windows)
346 w->Raise();
347
348
349#if defined(__WXMAC__) || defined(__WXGTK__)
350 // bug 868
351 // Simulate a TAB key press before continuing, else the cycle of
352 // navigation among top level windows stops because the keystrokes don't
353 // go to the CommandManager.
354 if (dynamic_cast<wxDialog*>(w)) {
355 w->SetFocus();
356 }
357#endif
358}
359
360void OnNextWindow(const CommandContext &context)
361{
362 auto &project = context.project;
363 auto &window = GetProjectFrame( project );
364 auto isEnabled = window.IsEnabled();
365
366 wxWindow *w = wxGetTopLevelParent(wxWindow::FindFocus());
367 const auto & list = window.GetChildren();
368 auto iter = list.begin(), end = list.end();
369
370 // If the project window has the current focus, start the search with the
371 // first child
372 if (w == &window)
373 {
374 }
375 // Otherwise start the search with the current window's next sibling
376 else
377 {
378 // Find the window in this projects children. If the window with the
379 // focus isn't a child of this project (like when a dialog is created
380 // without specifying a parent), then we'll get back NULL here.
381 while (iter != end && *iter != w)
382 ++iter;
383 if (iter != end)
384 ++iter;
385 }
386
387 // Search for the next toplevel window
388 for (; iter != end; ++iter)
389 {
390 // If it's a toplevel, visible (we have hidden windows) and is enabled,
391 // then we're done. The IsEnabled() prevents us from moving away from
392 // a modal dialog because all other toplevel windows will be disabled.
393 w = *iter;
394 if (w->IsTopLevel() && w->IsShown() && w->IsEnabled())
395 {
396 break;
397 }
398 }
399
400 // Ran out of siblings, so make the current project active
401 if ((iter == end) && isEnabled)
402 {
403 w = &window;
404 }
405
406 // And make sure it's on top (only for floating windows...project window will
407 // not raise)
408 // (Really only works on Windows)
409 w->Raise();
410
411
412#if defined(__WXMAC__) || defined(__WXGTK__)
413 // bug 868
414 // Simulate a TAB key press before continuing, else the cycle of
415 // navigation among top level windows stops because the keystrokes don't
416 // go to the CommandManager.
417 if (dynamic_cast<wxDialog*>(w)) {
418 w->SetFocus();
419 }
420#endif
421}
422
423void OnPrevFrame(const CommandContext &context)
424{
425 auto &project = context.project;
426 NextOrPrevFrame(project, false);
427}
428
429void OnNextFrame(const CommandContext &context)
430{
431 auto &project = context.project;
432 NextOrPrevFrame(project, true);
433}
434
435// Handler state:
437
438void OnCursorUp(const CommandContext &context)
439{
440 auto &project = context.project;
441 DoPrevTrack( project, false, mCircularTrackNavigation );
442}
443
444void OnCursorDown(const CommandContext &context)
445{
446 auto &project = context.project;
447 DoNextTrack( project, false, mCircularTrackNavigation );
448}
449
450void OnFirstTrack(const CommandContext &context)
451{
452 auto &project = context.project;
453 auto &trackFocus = TrackFocus::Get( project );
454 auto &tracks = TrackList::Get( project );
455
456 auto t = trackFocus.Get();
457 if (!t)
458 return;
459
460 auto f = *tracks.Any().begin();
461 if (t != f)
462 trackFocus.Set(f);
463 if (f)
464 f->EnsureVisible( t != f );
465}
466
467void OnLastTrack(const CommandContext &context)
468{
469 auto &project = context.project;
470 auto &trackFocus = TrackFocus::Get( project );
471 auto &tracks = TrackList::Get( project );
472
473 Track *t = trackFocus.Get();
474 if (!t)
475 return;
476
477 auto l = *tracks.Any().rbegin();
478 if (t != l)
479 trackFocus.Set(l);
480 if (l)
481 l->EnsureVisible( t != l );
482}
483
484void OnShiftUp(const CommandContext &context)
485{
486 auto &project = context.project;
487 DoPrevTrack( project, true, mCircularTrackNavigation );
488}
489
490void OnShiftDown(const CommandContext &context)
491{
492 auto &project = context.project;
493 DoNextTrack( project, true, mCircularTrackNavigation );
494}
495
496void OnToggle(const CommandContext &context)
497{
498 auto &project = context.project;
499 auto &trackFocus = TrackFocus::Get( project );
500 auto &selectionState = SelectionState::Get( project );
501
502 Track *t;
503
504 t = trackFocus.Get(); // Get currently focused track
505 if (!t)
506 return;
507
508 selectionState.SelectTrack
509 ( *t, !t->GetSelected(), true );
510 t->EnsureVisible( true );
511
512 trackFocus.UpdateAccessibility();
513
514 return;
515}
516
517void UpdatePrefs() override
518{
520 gPrefs->ReadBool(wxT("/GUI/CircularTrackNavigation"), false);
521}
523{
524 UpdatePrefs();
525}
526Handler( const Handler & ) PROHIBITED;
527Handler &operator=( const Handler & ) PROHIBITED;
528
529}; // struct Handler
530
531} // namespace
532
533// Handler is stateful. Needs a factory registered with
534// AudacityProject.
536 [](AudacityProject&) {
537 return std::make_unique< NavigationActions::Handler >(); } };
538
540 return project.AttachedObjects::Get< NavigationActions::Handler >( key );
541};
542
543// Menu definitions
544
545#define FN(X) (& NavigationActions::Handler :: X)
546
547namespace {
548using namespace MenuTable;
550{
551 // Ceci n'est pas un menu
553
554 static BaseItemSharedPtr items{
556 Items( wxT("Navigation"),
557 Command( wxT("PrevWindow"), XXO("Move Backward Through Active Windows"),
558 FN(OnPrevWindow), AlwaysEnabledFlag,
559 Options{ wxT("Alt+Shift+F6") }.IsGlobal() ),
560 Command( wxT("NextWindow"), XXO("Move Forward Through Active Windows"),
561 FN(OnNextWindow), AlwaysEnabledFlag,
562 Options{ wxT("Alt+F6") }.IsGlobal() )
563 ) ) };
564 return items;
565}
566
568 wxT("Optional/Extra/Part2"),
570};
571
573{
574 static const auto FocusedTracksFlags = TracksExistFlag() | TrackPanelHasFocus();
575
576 static BaseItemSharedPtr menu{
578 Menu( wxT("Focus"), XXO("Foc&us"),
579 Command( wxT("PrevFrame"),
580 XXO("Move &Backward from Toolbars to Tracks"), FN(OnPrevFrame),
581 AlwaysEnabledFlag, wxT("Ctrl+Shift+F6") ),
582 Command( wxT("NextFrame"),
583 XXO("Move F&orward from Toolbars to Tracks"), FN(OnNextFrame),
584 AlwaysEnabledFlag, wxT("Ctrl+F6") ),
585 Command( wxT("PrevTrack"), XXO("Move Focus to &Previous Track"),
586 FN(OnCursorUp), FocusedTracksFlags, wxT("Up") ),
587 Command( wxT("NextTrack"), XXO("Move Focus to &Next Track"),
588 FN(OnCursorDown), FocusedTracksFlags, wxT("Down") ),
589 Command( wxT("FirstTrack"), XXO("Move Focus to &First Track"),
590 FN(OnFirstTrack), FocusedTracksFlags, wxT("Ctrl+Home") ),
591 Command( wxT("LastTrack"), XXO("Move Focus to &Last Track"),
592 FN(OnLastTrack), FocusedTracksFlags, wxT("Ctrl+End") ),
593 Command( wxT("ShiftUp"), XXO("Move Focus to P&revious and Select"),
594 FN(OnShiftUp), FocusedTracksFlags, wxT("Shift+Up") ),
595 Command( wxT("ShiftDown"), XXO("Move Focus to N&ext and Select"),
596 FN(OnShiftDown), FocusedTracksFlags, wxT("Shift+Down") ),
597 Command( wxT("Toggle"), XXO("&Toggle Focused Track"), FN(OnToggle),
598 FocusedTracksFlags, wxT("Return") ),
599 Command( wxT("ToggleAlt"), XXO("Toggle Focuse&d Track"), FN(OnToggle),
600 FocusedTracksFlags, wxT("NUMPAD_ENTER") )
601 ) ) };
602 return menu;
603}
604
606 wxT("Optional/Extra/Part2"),
608};
609
610}
611
612#undef FN
wxT("CloseDown"))
AttachedItem sAttachment3
AttachedItem sAttachment2
constexpr CommandFlag AlwaysEnabledFlag
Definition: CommandFlag.h:34
wxEvtHandler CommandHandlerObject
const ReservedCommandFlag & TracksExistFlag()
const ReservedCommandFlag & TrackPanelHasFocus()
XXO("&Cut/Copy/Paste Toolbar")
static const AudacityProject::AttachedObjects::RegisteredFactory key
#define FN(X)
static CommandHandlerObject & findCommandHandler(AudacityProject &project)
FileConfig * gPrefs
Definition: Prefs.cpp:70
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 ...
declares abstract base class Track, TrackList, and iterators over TrackList
static TempAllowFocus TemporarilyAllowFocus()
Definition: AButton.cpp:658
static TempAllowFocus TemporarilyAllowFocus()
Definition: ASlider.cpp:1894
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:90
Client code makes static instance from a factory of attachments; passes it to Get or Find as a retrie...
Definition: ClientData.h:266
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 TempAllowFocus TemporarilyAllowFocus()
A listener notified of changes in preferences.
Definition: Prefs.h:556
static ProjectHistory & Get(AudacityProject &project)
static ProjectWindow & Get(AudacityProject &project)
static RealtimeEffectPanel & Get(AudacityProject &project)
static SelectionState & Get(AudacityProject &project)
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:226
bool GetSelected() const
Definition: Track.h:470
void EnsureVisible(bool modifyState=false)
Definition: Track.cpp:98
bool Any() const
Definition: Track.cpp:400
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:487
static TrackPanel & Get(AudacityProject &project)
Definition: TrackPanel.cpp:231
std::unique_ptr< WindowPlacement > FindFocus()
Find the window that is accepting keyboard input, if any.
Definition: BasicUI.h:343
std::unique_ptr< MenuItem > Menu(const Identifier &internalName, const TranslatableString &title, Args &&... args)
std::unique_ptr< MenuItems > Items(const Identifier &internalName, Args &&... args)
std::unique_ptr< CommandItem > Command(const CommandID &name, const TranslatableString &label_in, void(Handler::*pmf)(const CommandContext &), CommandFlag flags, const CommandManager::Options &options={}, CommandHandlerFinder finder=FinderScope::DefaultFinder())
Namespace for functions for project navigation menu (part of Extra menu)
auto end(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:159
std::shared_ptr< BaseItem > BaseItemSharedPtr
Definition: Registry.h:72
void DoPrevTrack(AudacityProject &project, bool shift, bool circularTrackNavigation)
void DoNextTrack(AudacityProject &project, bool shift, bool circularTrackNavigation)
void NextOrPrevFrame(AudacityProject &project, bool forward)
A convenient default parameter for class template Site.
Definition: ClientData.h:28
void OnNextWindow(const CommandContext &context)
void OnNextFrame(const CommandContext &context)
void OnToggle(const CommandContext &context)
void OnPrevFrame(const CommandContext &context)
void OnCursorUp(const CommandContext &context)
void OnShiftDown(const CommandContext &context)
void OnPrevWindow(const CommandContext &context)
Handler(const Handler &) PROHIBITED
Handler & operator=(const Handler &) PROHIBITED
void OnLastTrack(const CommandContext &context)
void OnCursorDown(const CommandContext &context)
void OnFirstTrack(const CommandContext &context)
void OnShiftUp(const CommandContext &context)