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