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
95 if (shift) {
96 auto p = * -- tracks.Find(t); // Get previous track
97 if (!p) {
98 // On first track
99 // JKC: wxBell() is probably for accessibility, so a blind
100 // user knows they were at the top track.
101 wxBell();
102 if (circularTrackNavigation)
103 p = *tracks.rbegin();
104 else {
105 viewport.ShowTrack(*t);
106 return;
107 }
108 }
109 // If here, then there is a nonempty list and a previous track
110 // (maybe circularly)
111 assert(p);
112 auto tSelected = t->GetSelected();
113 auto pSelected = p->GetSelected();
114 if (tSelected && pSelected) {
115 selectionState.SelectTrack(*t, false, false);
116 trackFocus.Set(p); // move focus to next track up
117 if (p) {
118 viewport.ShowTrack(*p);
119 projectHistory.ModifyState(false);
120 }
121 return;
122 }
123 if (tSelected && !pSelected) {
124 selectionState.SelectTrack(*p, true, false);
125 trackFocus.Set(p); // move focus to next track up
126 if (p) {
127 viewport.ShowTrack(*p);
128 projectHistory.ModifyState(false);
129 }
130 return;
131 }
132 if (!tSelected && pSelected) {
133 selectionState.SelectTrack(*p, false, false);
134 trackFocus.Set(p); // move focus to next track up
135 if (p) {
136 viewport.ShowTrack(*p);
137 projectHistory.ModifyState(false);
138 }
139 return;
140 }
141 if (!tSelected && !pSelected) {
142 selectionState.SelectTrack(*t, true, false);
143 trackFocus.Set(p); // move focus to next track up
144 if (p) {
145 viewport.ShowTrack(*p);
146 projectHistory.ModifyState(false);
147 }
148 return;
149 }
150 }
151 else {
152 auto p = * -- tracks.Find(t); // Get previous track
153 if (!p) {
154 // On first track so stay there?
155 wxBell();
156 if (circularTrackNavigation) {
157 auto range = tracks.Any();
158 p = * range.rbegin(); // null if range is empty
159 trackFocus.Set(p); // Wrap to the last track
160 if (p) {
161 viewport.ShowTrack(*p);
162 projectHistory.ModifyState(false);
163 }
164 return;
165 }
166 else {
167 viewport.ShowTrack(*t);
168 return;
169 }
170 }
171 else {
172 trackFocus.Set(p); // move focus to next track up
173 viewport.ShowTrack(*p);
174 projectHistory.ModifyState(false);
175 return;
176 }
177 }
178}
179
184 AudacityProject &project, bool shift, bool circularTrackNavigation )
185{
186 auto &projectHistory = ProjectHistory::Get(project);
187 auto &trackFocus = TrackFocus::Get(project);
189 auto &selectionState = SelectionState::Get(project);
190 auto &viewport = Viewport::Get(project);
191
192 const auto t = trackFocus.Get();
193 if (!t) {
194 // if there isn't one, focus on first
195 const auto first = *tracks.begin();
196 trackFocus.Set(first);
197 if (first) {
198 viewport.ShowTrack(*first);
199 projectHistory.ModifyState(false);
200 }
201 return;
202 }
203
204 if (shift) {
205 auto n = * ++ tracks.Find(t); // Get next track
206 if (!n) {
207 // On last track so stay there
208 wxBell();
209 if (circularTrackNavigation)
210 n = *tracks.begin();
211 else {
212 viewport.ShowTrack(*t);
213 return;
214 }
215 }
216 // If here, then there is a nonempty list and a next track
217 // (maybe circularly)
218 assert(n);
219 auto tSelected = t->GetSelected();
220 auto nSelected = n->GetSelected();
221 if (tSelected && nSelected) {
222 selectionState.SelectTrack(*t, false, false);
223 trackFocus.Set(n); // move focus to next track down
224 if (n) {
225 viewport.ShowTrack(*n);
226 projectHistory.ModifyState(false);
227 }
228 return;
229 }
230 if (tSelected && !nSelected) {
231 selectionState.SelectTrack(*n, true, false);
232 trackFocus.Set(n); // move focus to next track down
233 if (n) {
234 viewport.ShowTrack(*n);
235 projectHistory.ModifyState(false);
236 }
237 return;
238 }
239 if (!tSelected && nSelected) {
240 selectionState.SelectTrack(*n, false, false);
241 trackFocus.Set(n); // move focus to next track down
242 if (n) {
243 viewport.ShowTrack(*n);
244 projectHistory.ModifyState(false);
245 }
246 return;
247 }
248 if (!tSelected && !nSelected) {
249 selectionState.SelectTrack(*t, true, false);
250 trackFocus.Set(n); // move focus to next track down
251 if (n) {
252 viewport.ShowTrack(*n);
253 projectHistory.ModifyState(false);
254 }
255 return;
256 }
257 }
258 else {
259 auto n = * ++ tracks.Find(t); // Get next track
260 if (!n) {
261 // On last track so stay there
262 wxBell();
263 if (circularTrackNavigation) {
264 n = *tracks.begin();
265 trackFocus.Set(n); // Wrap to the first track
266 if (n) {
267 viewport.ShowTrack(*n);
268 projectHistory.ModifyState(false);
269 }
270 return;
271 }
272 else {
273 viewport.ShowTrack(*t);
274 return;
275 }
276 }
277 else {
278 trackFocus.Set(n); // move focus to next track down
279 viewport.ShowTrack(*n);
280 projectHistory.ModifyState(false);
281 return;
282 }
283 }
284}
285
286}
287
290
291// exported helper functions
292// none
293
294// Menu handler functions
295
297 : CommandHandlerObject // MUST be the first base class!
300{
301
302void OnPrevWindow(const CommandContext &context)
303{
304 auto &project = context.project;
305 auto &window = GetProjectFrame( project );
306 auto isEnabled = window.IsEnabled();
307
308 wxWindow *w = wxGetTopLevelParent(wxWindow::FindFocus());
309 const auto & list = window.GetChildren();
310 auto iter = list.rbegin(), end = list.rend();
311
312 // If the project window has the current focus, start the search with the
313 // last child
314 if (w == &window)
315 {
316 }
317 // Otherwise start the search with the current window's previous sibling
318 else
319 {
320 while (iter != end && *iter != w)
321 ++iter;
322 if (iter != end)
323 ++iter;
324 }
325
326 // Search for the previous toplevel window
327 for (; iter != end; ++iter)
328 {
329 // If it's a toplevel and is visible (we have come hidden windows), then
330 // we're done
331 w = *iter;
332 if (w->IsTopLevel() && w->IsShown() && isEnabled)
333 {
334 break;
335 }
336 }
337
338 // Ran out of siblings, so make the current project active
339 if ((iter == end) && isEnabled)
340 {
341 w = &window;
342 }
343
344 // And make sure it's on top (only for floating windows...project window will
345 // not raise)
346 // (Really only works on Windows)
347 w->Raise();
348
349
350#if defined(__WXMAC__) || defined(__WXGTK__)
351 // bug 868
352 // Simulate a TAB key press before continuing, else the cycle of
353 // navigation among top level windows stops because the keystrokes don't
354 // go to the CommandManager.
355 if (dynamic_cast<wxDialog*>(w)) {
356 w->SetFocus();
357 }
358#endif
359}
360
361void OnNextWindow(const CommandContext &context)
362{
363 auto &project = context.project;
364 auto &window = GetProjectFrame( project );
365 auto isEnabled = window.IsEnabled();
366
367 wxWindow *w = wxGetTopLevelParent(wxWindow::FindFocus());
368 const auto & list = window.GetChildren();
369 auto iter = list.begin(), end = list.end();
370
371 // If the project window has the current focus, start the search with the
372 // first child
373 if (w == &window)
374 {
375 }
376 // Otherwise start the search with the current window's next sibling
377 else
378 {
379 // Find the window in this projects children. If the window with the
380 // focus isn't a child of this project (like when a dialog is created
381 // without specifying a parent), then we'll get back NULL here.
382 while (iter != end && *iter != w)
383 ++iter;
384 if (iter != end)
385 ++iter;
386 }
387
388 // Search for the next toplevel window
389 for (; iter != end; ++iter)
390 {
391 // If it's a toplevel, visible (we have hidden windows) and is enabled,
392 // then we're done. The IsEnabled() prevents us from moving away from
393 // a modal dialog because all other toplevel windows will be disabled.
394 w = *iter;
395 if (w->IsTopLevel() && w->IsShown() && w->IsEnabled())
396 {
397 break;
398 }
399 }
400
401 // Ran out of siblings, so make the current project active
402 if ((iter == end) && isEnabled)
403 {
404 w = &window;
405 }
406
407 // And make sure it's on top (only for floating windows...project window will
408 // not raise)
409 // (Really only works on Windows)
410 w->Raise();
411
412
413#if defined(__WXMAC__) || defined(__WXGTK__)
414 // bug 868
415 // Simulate a TAB key press before continuing, else the cycle of
416 // navigation among top level windows stops because the keystrokes don't
417 // go to the CommandManager.
418 if (dynamic_cast<wxDialog*>(w)) {
419 w->SetFocus();
420 }
421#endif
422}
423
424void OnPrevFrame(const CommandContext &context)
425{
426 auto &project = context.project;
427 NextOrPrevFrame(project, false);
428}
429
430void OnNextFrame(const CommandContext &context)
431{
432 auto &project = context.project;
434}
435
436// Handler state:
438
439void OnCursorUp(const CommandContext &context)
440{
441 auto &project = context.project;
443}
444
445void OnCursorDown(const CommandContext &context)
446{
447 auto &project = context.project;
449}
450
451void OnFirstTrack(const CommandContext &context)
452{
453 auto &project = context.project;
454 auto &projectHistory = ProjectHistory::Get(project);
455 auto &trackFocus = TrackFocus::Get( project );
456 auto &tracks = TrackList::Get( project );
457 auto &viewport = Viewport::Get(project);
458
459 auto t = trackFocus.Get();
460 if (!t)
461 return;
462
463 auto f = *tracks.begin();
464 if (t != f)
465 trackFocus.Set(f);
466 if (f) {
467 viewport.ShowTrack(*f);
468 if (t != f)
469 projectHistory.ModifyState(false);
470 }
471}
472
473void OnLastTrack(const CommandContext &context)
474{
475 auto &project = context.project;
476 auto &projectHistory = ProjectHistory::Get(project);
477 auto &trackFocus = TrackFocus::Get( project );
478 auto &tracks = TrackList::Get( project );
479 auto &viewport = Viewport::Get(project);
480
481 Track *t = trackFocus.Get();
482 if (!t)
483 return;
484
485 auto l = *tracks.rbegin();
486 if (t != l)
487 trackFocus.Set(l);
488 if (l) {
489 viewport.ShowTrack(*l);
490 if (t != l)
491 projectHistory.ModifyState(false);
492 }
493}
494
495void OnShiftUp(const CommandContext &context)
496{
497 auto &project = context.project;
499}
500
501void OnShiftDown(const CommandContext &context)
502{
503 auto &project = context.project;
505}
506
507void OnToggle(const CommandContext &context)
508{
509 auto &project = context.project;
510 auto &projectHistory = ProjectHistory::Get(project);
511 auto &trackFocus = TrackFocus::Get(project);
512 auto &selectionState = SelectionState::Get(project);
513 auto &viewport = Viewport::Get(project);
514
515 Track *t;
516
517 t = trackFocus.Get(); // Get currently focused track
518 if (!t)
519 return;
520 selectionState.SelectTrack(*t, !t->GetSelected(), true);
521 viewport.ShowTrack(*t);
522 projectHistory.ModifyState(false);
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 & ) = delete;
539Handler &operator=( const Handler & ) = delete;
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 MenuRegistry;
562{
563 // Ceci n'est pas un menu
564 static auto items = std::shared_ptr{
566 Items( wxT("Navigation"),
567 Command( wxT("PrevWindow"), XXO("Move Backward Through Active Windows"),
568 FN(OnPrevWindow), AlwaysEnabledFlag,
569 Options{ wxT("Alt+Shift+F6") }.IsGlobal() ),
570 Command( wxT("NextWindow"), XXO("Move Forward Through Active Windows"),
571 FN(OnNextWindow), AlwaysEnabledFlag,
572 Options{ wxT("Alt+F6") }.IsGlobal() )
573 ) ) };
574 return items;
575}
576
578 wxT("Optional/Extra/Part2")
579};
580
582{
583 static const auto FocusedTracksFlags =
585 static auto menu = std::shared_ptr{
587 Menu( wxT("Focus"), XXO("Foc&us"),
588 Command( wxT("PrevFrame"),
589 XXO("Move &Backward from Toolbars to Tracks"), FN(OnPrevFrame),
590 AlwaysEnabledFlag, wxT("Ctrl+Shift+F6") ),
591 Command( wxT("NextFrame"),
592 XXO("Move F&orward from Toolbars to Tracks"), FN(OnNextFrame),
593 AlwaysEnabledFlag, wxT("Ctrl+F6") ),
594 Command( wxT("PrevTrack"), XXO("Move Focus to &Previous Track"),
595 FN(OnCursorUp), FocusedTracksFlags, wxT("Up") ),
596 Command( wxT("NextTrack"), XXO("Move Focus to &Next Track"),
597 FN(OnCursorDown), FocusedTracksFlags, wxT("Down") ),
598 Command( wxT("FirstTrack"), XXO("Move Focus to &First Track"),
599 FN(OnFirstTrack), FocusedTracksFlags, wxT("Ctrl+Home") ),
600 Command( wxT("LastTrack"), XXO("Move Focus to &Last Track"),
601 FN(OnLastTrack), FocusedTracksFlags, wxT("Ctrl+End") ),
602 Command( wxT("ShiftUp"), XXO("Move Focus to P&revious and Select"),
603 FN(OnShiftUp), FocusedTracksFlags, wxT("Shift+Up") ),
604 Command( wxT("ShiftDown"), XXO("Move Focus to N&ext and Select"),
605 FN(OnShiftDown), FocusedTracksFlags, wxT("Shift+Down") ),
606 Command( wxT("Toggle"), XXO("&Toggle Focused Track"), FN(OnToggle),
607 FocusedTracksFlags, wxT("Return") ),
608 Command( wxT("ToggleAlt"), XXO("Toggle Focuse&d Track"), FN(OnToggle),
609 FocusedTracksFlags, wxT("NUMPAD_ENTER") )
610 ) ) };
611 return menu;
612}
613
615 wxT("Optional/Extra/Part2")
616};
617
618}
619
620#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:1884
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:275
Subclass & Get(const RegisteredFactory &key)
Get reference to an attachment, creating on demand if not present, down-cast it to Subclass.
Definition: ClientData.h:318
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:110
bool GetSelected() const
Selectedness is always the same for all channels of a group.
Definition: Track.cpp:78
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:314
static TrackPanel & Get(AudacityProject &project)
Definition: TrackPanel.cpp:234
static Viewport & Get(AudacityProject &project)
Definition: Viewport.cpp:33
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:375
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)
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)
const char * end(const char *str) noexcept
Definition: StringUtils.h:106
A convenient default parameter for class template Site.
Definition: ClientData.h:29
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)