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