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 "TrackFocus.h"
14#include "CommandContext.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
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 std::vector<wxWindow*> seq;
34 // Skip docks that are empty (Bug 1564).
35 if(!ToolManager::Get(project).GetTopDock()->GetChildren().IsEmpty())
36 seq.push_back(ProjectWindow::Get( project ).GetTopPanel());
37 seq.push_back(&TrackPanel::Get( project ));
38 seq.push_back(&RealtimeEffectPanel::Get(project));
39 if(!ToolManager::Get( project ).GetBotDock()->GetChildren().IsEmpty())
40 seq.push_back(ToolManager::Get( project ).GetBotDock());
41
42 auto FindAncestor = [&]() {
43 wxWindow *pWindow = wxWindow::FindFocus();
44 while (pWindow)
45 {
46 auto it = std::find(seq.cbegin(), seq.cend(), pWindow);
47 if(it != seq.cend())
48 return static_cast<size_t>(std::distance(seq.cbegin(), it));
49 pWindow = pWindow->GetParent();
50 }
51 return seq.size();
52 };
53
54 const auto idx = FindAncestor();
55 if (idx == seq.size())
56 return;
57
58 auto idx2 = idx;
59 const auto increment = (forward ? 1 : static_cast<int>(seq.size()) - 1);
60
61 while( idx != (idx2 = (idx2 + increment) % seq.size()) ) {
62 wxWindow *toFocus = seq[idx2];
63 if(!toFocus->IsShown())
64 continue;
65
66 toFocus->SetFocus();
67 if ( FindAncestor() == idx2 )
68 // The focus took!
69 break;
70 }
71}
72
75 AudacityProject &project, bool shift, bool circularTrackNavigation)
76{
77 auto &projectHistory = ProjectHistory::Get(project);
78 auto &trackFocus = TrackFocus::Get(project);
80 auto &selectionState = SelectionState::Get(project);
81 auto &viewport = Viewport::Get(project);
82
83 const auto t = trackFocus.Get();
84 if (!t) {
85 // if there isn't one, focus on last
86 const auto last = *tracks.rbegin();
87 trackFocus.Set(last);
88 if (last) {
89 viewport.ShowTrack(*last);
90 projectHistory.ModifyState(false);
91 }
92 return;
93 }
94 assert(t->IsLeader());
95
96 if (shift) {
97 auto p = * -- tracks.Find(t); // Get previous track
98 if (!p) {
99 // On first track
100 // JKC: wxBell() is probably for accessibility, so a blind
101 // user knows they were at the top track.
102 wxBell();
103 if (circularTrackNavigation)
104 p = *tracks.rbegin();
105 else {
106 viewport.ShowTrack(*t);
107 return;
108 }
109 }
110 // If here, then there is a nonempty list and a previous track
111 // (maybe circularly)
112 assert(p && p->IsLeader());
113 auto tSelected = t->GetSelected();
114 auto pSelected = p->GetSelected();
115 if (tSelected && pSelected) {
116 selectionState.SelectTrack(*t, false, false);
117 trackFocus.Set(p); // move focus to next track up
118 if (p) {
119 viewport.ShowTrack(*p);
120 projectHistory.ModifyState(false);
121 }
122 return;
123 }
124 if (tSelected && !pSelected) {
125 selectionState.SelectTrack(*p, true, false);
126 trackFocus.Set(p); // move focus to next track up
127 if (p) {
128 viewport.ShowTrack(*p);
129 projectHistory.ModifyState(false);
130 }
131 return;
132 }
133 if (!tSelected && pSelected) {
134 selectionState.SelectTrack(*p, false, false);
135 trackFocus.Set(p); // move focus to next track up
136 if (p) {
137 viewport.ShowTrack(*p);
138 projectHistory.ModifyState(false);
139 }
140 return;
141 }
142 if (!tSelected && !pSelected) {
143 selectionState.SelectTrack(*t, true, false);
144 trackFocus.Set(p); // move focus to next track up
145 if (p) {
146 viewport.ShowTrack(*p);
147 projectHistory.ModifyState(false);
148 }
149 return;
150 }
151 }
152 else {
153 auto p = * -- tracks.Find(t); // Get previous track
154 if (!p) {
155 // On first track so stay there?
156 wxBell();
157 if (circularTrackNavigation) {
158 auto range = tracks.Any();
159 p = * range.rbegin(); // null if range is empty
160 trackFocus.Set(p); // Wrap to the last track
161 if (p) {
162 viewport.ShowTrack(*p);
163 projectHistory.ModifyState(false);
164 }
165 return;
166 }
167 else {
168 viewport.ShowTrack(*t);
169 return;
170 }
171 }
172 else {
173 trackFocus.Set(p); // move focus to next track up
174 viewport.ShowTrack(*p);
175 projectHistory.ModifyState(false);
176 return;
177 }
178 }
179}
180
185 AudacityProject &project, bool shift, bool circularTrackNavigation )
186{
187 auto &projectHistory = ProjectHistory::Get(project);
188 auto &trackFocus = TrackFocus::Get(project);
190 auto &selectionState = SelectionState::Get(project);
191 auto &viewport = Viewport::Get(project);
192
193 const auto t = trackFocus.Get();
194 if (!t) {
195 // if there isn't one, focus on first
196 const auto first = *tracks.begin();
197 trackFocus.Set(first);
198 if (first) {
199 viewport.ShowTrack(*first);
200 projectHistory.ModifyState(false);
201 }
202 return;
203 }
204 assert(t->IsLeader());
205
206 if (shift) {
207 auto n = * ++ tracks.Find(t); // Get next track
208 if (!n) {
209 // On last track so stay there
210 wxBell();
211 if (circularTrackNavigation)
212 n = *tracks.begin();
213 else {
214 viewport.ShowTrack(*t);
215 return;
216 }
217 }
218 // If here, then there is a nonempty list and a next track
219 // (maybe circularly)
220 assert(n && n->IsLeader());
221 auto tSelected = t->GetSelected();
222 auto nSelected = n->GetSelected();
223 if (tSelected && nSelected) {
224 selectionState.SelectTrack(*t, false, false);
225 trackFocus.Set(n); // move focus to next track down
226 if (n) {
227 viewport.ShowTrack(*n);
228 projectHistory.ModifyState(false);
229 }
230 return;
231 }
232 if (tSelected && !nSelected) {
233 selectionState.SelectTrack(*n, true, false);
234 trackFocus.Set(n); // move focus to next track down
235 if (n) {
236 viewport.ShowTrack(*n);
237 projectHistory.ModifyState(false);
238 }
239 return;
240 }
241 if (!tSelected && nSelected) {
242 selectionState.SelectTrack(*n, false, false);
243 trackFocus.Set(n); // move focus to next track down
244 if (n) {
245 viewport.ShowTrack(*n);
246 projectHistory.ModifyState(false);
247 }
248 return;
249 }
250 if (!tSelected && !nSelected) {
251 selectionState.SelectTrack(*t, true, false);
252 trackFocus.Set(n); // move focus to next track down
253 if (n) {
254 viewport.ShowTrack(*n);
255 projectHistory.ModifyState(false);
256 }
257 return;
258 }
259 }
260 else {
261 auto n = * ++ tracks.Find(t); // Get next track
262 if (!n) {
263 // On last track so stay there
264 wxBell();
265 if (circularTrackNavigation) {
266 n = *tracks.begin();
267 trackFocus.Set(n); // Wrap to the first track
268 if (n) {
269 viewport.ShowTrack(*n);
270 projectHistory.ModifyState(false);
271 }
272 return;
273 }
274 else {
275 viewport.ShowTrack(*t);
276 return;
277 }
278 }
279 else {
280 trackFocus.Set(n); // move focus to next track down
281 viewport.ShowTrack(*n);
282 projectHistory.ModifyState(false);
283 return;
284 }
285 }
286}
287
288}
289
292
293// exported helper functions
294// none
295
296// Menu handler functions
297
299 : CommandHandlerObject // MUST be the first base class!
302{
303
304void OnPrevWindow(const CommandContext &context)
305{
306 auto &project = context.project;
307 auto &window = GetProjectFrame( project );
308 auto isEnabled = window.IsEnabled();
309
310 wxWindow *w = wxGetTopLevelParent(wxWindow::FindFocus());
311 const auto & list = window.GetChildren();
312 auto iter = list.rbegin(), end = list.rend();
313
314 // If the project window has the current focus, start the search with the
315 // last child
316 if (w == &window)
317 {
318 }
319 // Otherwise start the search with the current window's previous sibling
320 else
321 {
322 while (iter != end && *iter != w)
323 ++iter;
324 if (iter != end)
325 ++iter;
326 }
327
328 // Search for the previous toplevel window
329 for (; iter != end; ++iter)
330 {
331 // If it's a toplevel and is visible (we have come hidden windows), then
332 // we're done
333 w = *iter;
334 if (w->IsTopLevel() && w->IsShown() && isEnabled)
335 {
336 break;
337 }
338 }
339
340 // Ran out of siblings, so make the current project active
341 if ((iter == end) && isEnabled)
342 {
343 w = &window;
344 }
345
346 // And make sure it's on top (only for floating windows...project window will
347 // not raise)
348 // (Really only works on Windows)
349 w->Raise();
350
351
352#if defined(__WXMAC__) || defined(__WXGTK__)
353 // bug 868
354 // Simulate a TAB key press before continuing, else the cycle of
355 // navigation among top level windows stops because the keystrokes don't
356 // go to the CommandManager.
357 if (dynamic_cast<wxDialog*>(w)) {
358 w->SetFocus();
359 }
360#endif
361}
362
363void OnNextWindow(const CommandContext &context)
364{
365 auto &project = context.project;
366 auto &window = GetProjectFrame( project );
367 auto isEnabled = window.IsEnabled();
368
369 wxWindow *w = wxGetTopLevelParent(wxWindow::FindFocus());
370 const auto & list = window.GetChildren();
371 auto iter = list.begin(), end = list.end();
372
373 // If the project window has the current focus, start the search with the
374 // first child
375 if (w == &window)
376 {
377 }
378 // Otherwise start the search with the current window's next sibling
379 else
380 {
381 // Find the window in this projects children. If the window with the
382 // focus isn't a child of this project (like when a dialog is created
383 // without specifying a parent), then we'll get back NULL here.
384 while (iter != end && *iter != w)
385 ++iter;
386 if (iter != end)
387 ++iter;
388 }
389
390 // Search for the next toplevel window
391 for (; iter != end; ++iter)
392 {
393 // If it's a toplevel, visible (we have hidden windows) and is enabled,
394 // then we're done. The IsEnabled() prevents us from moving away from
395 // a modal dialog because all other toplevel windows will be disabled.
396 w = *iter;
397 if (w->IsTopLevel() && w->IsShown() && w->IsEnabled())
398 {
399 break;
400 }
401 }
402
403 // Ran out of siblings, so make the current project active
404 if ((iter == end) && isEnabled)
405 {
406 w = &window;
407 }
408
409 // And make sure it's on top (only for floating windows...project window will
410 // not raise)
411 // (Really only works on Windows)
412 w->Raise();
413
414
415#if defined(__WXMAC__) || defined(__WXGTK__)
416 // bug 868
417 // Simulate a TAB key press before continuing, else the cycle of
418 // navigation among top level windows stops because the keystrokes don't
419 // go to the CommandManager.
420 if (dynamic_cast<wxDialog*>(w)) {
421 w->SetFocus();
422 }
423#endif
424}
425
426void OnPrevFrame(const CommandContext &context)
427{
428 auto &project = context.project;
429 NextOrPrevFrame(project, false);
430}
431
432void OnNextFrame(const CommandContext &context)
433{
434 auto &project = context.project;
436}
437
438// Handler state:
440
441void OnCursorUp(const CommandContext &context)
442{
443 auto &project = context.project;
445}
446
447void OnCursorDown(const CommandContext &context)
448{
449 auto &project = context.project;
451}
452
453void OnFirstTrack(const CommandContext &context)
454{
455 auto &project = context.project;
456 auto &projectHistory = ProjectHistory::Get(project);
457 auto &trackFocus = TrackFocus::Get( project );
458 auto &tracks = TrackList::Get( project );
459 auto &viewport = Viewport::Get(project);
460
461 auto t = trackFocus.Get();
462 if (!t)
463 return;
464
465 auto f = *tracks.begin();
466 if (t != f)
467 trackFocus.Set(f);
468 if (f) {
469 viewport.ShowTrack(*f);
470 if (t != f)
471 projectHistory.ModifyState(false);
472 }
473}
474
475void OnLastTrack(const CommandContext &context)
476{
477 auto &project = context.project;
478 auto &projectHistory = ProjectHistory::Get(project);
479 auto &trackFocus = TrackFocus::Get( project );
480 auto &tracks = TrackList::Get( project );
481 auto &viewport = Viewport::Get(project);
482
483 Track *t = trackFocus.Get();
484 assert(t->IsLeader());
485 if (!t)
486 return;
487
488 auto l = *tracks.rbegin();
489 if (t != l)
490 trackFocus.Set(l);
491 if (l) {
492 viewport.ShowTrack(*l);
493 if (t != l)
494 projectHistory.ModifyState(false);
495 }
496}
497
498void OnShiftUp(const CommandContext &context)
499{
500 auto &project = context.project;
502}
503
504void OnShiftDown(const CommandContext &context)
505{
506 auto &project = context.project;
508}
509
510void OnToggle(const CommandContext &context)
511{
512 auto &project = context.project;
513 auto &projectHistory = ProjectHistory::Get(project);
514 auto &trackFocus = TrackFocus::Get(project);
515 auto &selectionState = SelectionState::Get(project);
516 auto &viewport = Viewport::Get(project);
517
518 Track *t;
519
520 t = trackFocus.Get(); // Get currently focused track
521 if (!t)
522 return;
523 assert(t->IsLeader()); // TrackFocus promises this
524 selectionState.SelectTrack(*t, !t->GetSelected(), true);
525 viewport.ShowTrack(*t);
526 projectHistory.ModifyState(false);
527
528 trackFocus.UpdateAccessibility();
529
530 return;
531}
532
533void UpdatePrefs() override
534{
536 gPrefs->ReadBool(wxT("/GUI/CircularTrackNavigation"), false);
537}
539{
540 UpdatePrefs();
541}
542Handler( const Handler & ) = delete;
543Handler &operator=( const Handler & ) = delete;
544
545}; // struct Handler
546
547} // namespace
548
549// Handler is stateful. Needs a factory registered with
550// AudacityProject.
552 [](AudacityProject&) {
553 return std::make_unique< NavigationActions::Handler >(); } };
554
556 return project.AttachedObjects::Get< NavigationActions::Handler >( key );
557};
558
559// Menu definitions
560
561#define FN(X) (& NavigationActions::Handler :: X)
562
563namespace {
564using namespace MenuRegistry;
566{
567 // Ceci n'est pas un menu
568 static auto items = std::shared_ptr{
570 Items( wxT("Navigation"),
571 Command( wxT("PrevWindow"), XXO("Move Backward Through Active Windows"),
572 FN(OnPrevWindow), AlwaysEnabledFlag,
573 Options{ wxT("Alt+Shift+F6") }.IsGlobal() ),
574 Command( wxT("NextWindow"), XXO("Move Forward Through Active Windows"),
575 FN(OnNextWindow), AlwaysEnabledFlag,
576 Options{ wxT("Alt+F6") }.IsGlobal() )
577 ) ) };
578 return items;
579}
580
582 wxT("Optional/Extra/Part2")
583};
584
586{
587 static const auto FocusedTracksFlags =
589 static auto menu = std::shared_ptr{
591 Menu( wxT("Focus"), XXO("Foc&us"),
592 Command( wxT("PrevFrame"),
593 XXO("Move &Backward from Toolbars to Tracks"), FN(OnPrevFrame),
594 AlwaysEnabledFlag, wxT("Ctrl+Shift+F6") ),
595 Command( wxT("NextFrame"),
596 XXO("Move F&orward from Toolbars to Tracks"), FN(OnNextFrame),
597 AlwaysEnabledFlag, wxT("Ctrl+F6") ),
598 Command( wxT("PrevTrack"), XXO("Move Focus to &Previous Track"),
599 FN(OnCursorUp), FocusedTracksFlags, wxT("Up") ),
600 Command( wxT("NextTrack"), XXO("Move Focus to &Next Track"),
601 FN(OnCursorDown), FocusedTracksFlags, wxT("Down") ),
602 Command( wxT("FirstTrack"), XXO("Move Focus to &First Track"),
603 FN(OnFirstTrack), FocusedTracksFlags, wxT("Ctrl+Home") ),
604 Command( wxT("LastTrack"), XXO("Move Focus to &Last Track"),
605 FN(OnLastTrack), FocusedTracksFlags, wxT("Ctrl+End") ),
606 Command( wxT("ShiftUp"), XXO("Move Focus to P&revious and Select"),
607 FN(OnShiftUp), FocusedTracksFlags, wxT("Shift+Up") ),
608 Command( wxT("ShiftDown"), XXO("Move Focus to N&ext and Select"),
609 FN(OnShiftDown), FocusedTracksFlags, wxT("Shift+Down") ),
610 Command( wxT("Toggle"), XXO("&Toggle Focused Track"), FN(OnToggle),
611 FocusedTracksFlags, wxT("Return") ),
612 Command( wxT("ToggleAlt"), XXO("Toggle Focuse&d Track"), FN(OnToggle),
613 FocusedTracksFlags, wxT("NUMPAD_ENTER") )
614 ) ) };
615 return menu;
616}
617
619 wxT("Optional/Extra/Part2")
620};
621
622}
623
624#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)
audacity::BasicSettings * gPrefs
Definition: Prefs.cpp:68
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 ...
const auto tracks
const auto project
declares abstract base class Track, TrackList, and iterators over TrackList
static TempAllowFocus TemporarilyAllowFocus()
Definition: AButton.cpp:658
static TempAllowFocus TemporarilyAllowFocus()
Definition: ASlider.cpp:1892
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:274
Subclass & Get(const RegisteredFactory &key)
Get reference to an attachment, creating on demand if not present, down-cast it to Subclass.
Definition: ClientData.h:317
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:652
static ProjectHistory & Get(AudacityProject &project)
static ProjectWindow & Get(AudacityProject &project)
static RealtimeEffectPanel & Get(AudacityProject &project)
Generates classes whose instances register items at construction.
Definition: Registry.h:388
static SelectionState & Get(AudacityProject &project)
static ToolManager & Get(AudacityProject &project)
Track * Get()
Definition: TrackFocus.cpp:156
Abstract base class for an object holding data associated with points on a time axis.
Definition: Track.h:122
bool GetSelected() const
Selectedness is always the same for all channels of a group.
Definition: Track.cpp:70
bool IsLeader() const override
Definition: Track.cpp:291
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:347
static TrackPanel & Get(AudacityProject &project)
Definition: TrackPanel.cpp:233
static Viewport & Get(AudacityProject &project)
Definition: Viewport.cpp:32
bool ReadBool(const wxString &key, bool defaultValue) const
std::unique_ptr< WindowPlacement > FindFocus()
Find the window that is accepting keyboard input, if any.
Definition: BasicUI.h:373
constexpr auto Items
Definition: MenuRegistry.h:427
constexpr auto Command
Definition: MenuRegistry.h:456
constexpr auto Menu
Items will appear in a main toolbar menu or in a sub-menu.
Definition: MenuRegistry.h:445
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::unique_ptr< detail::IndirectItem< Item > > Indirect(const std::shared_ptr< Item > &ptr)
A convenience function.
Definition: Registry.h:175
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)
Handler(const Handler &)=delete
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 & operator=(const Handler &)=delete
void OnLastTrack(const CommandContext &context)
void OnCursorDown(const CommandContext &context)
void OnFirstTrack(const CommandContext &context)
void OnShiftUp(const CommandContext &context)