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
21 namespace {
22 
NextOrPrevFrame(AudacityProject & project,bool forward)23 void 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();
31    auto temp3 = MeterPanel::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] = {
41       ProjectWindow::Get( project ).GetTopPanel(),
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 
86 /// \todo Merge related methods, OnPrevTrack and OnNextTrack.
DoPrevTrack(AudacityProject & project,bool shift,bool circularTrackNavigation)87 void DoPrevTrack(
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 
194 /// The following method moves to the next track,
195 /// selecting and unselecting depending if you are on the start of a
196 /// block or not.
DoNextTrack(AudacityProject & project,bool shift,bool circularTrackNavigation)197 void DoNextTrack(
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 
299 /// Namespace for functions for project navigation menu (part of Extra menu)
300 namespace NavigationActions {
301 
302 // exported helper functions
303 // none
304 
305 // Menu handler functions
306 
307 struct Handler
308    : CommandHandlerObject // MUST be the first base class!
309    , ClientData::Base
310    , PrefsListener
311 {
312 
OnPrevWindowNavigationActions::Handler313 void 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 
OnNextWindowNavigationActions::Handler372 void 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 
OnPrevFrameNavigationActions::Handler435 void OnPrevFrame(const CommandContext &context)
436 {
437    auto &project = context.project;
438    NextOrPrevFrame(project, false);
439 }
440 
OnNextFrameNavigationActions::Handler441 void OnNextFrame(const CommandContext &context)
442 {
443    auto &project = context.project;
444    NextOrPrevFrame(project, true);
445 }
446 
447 // Handler state:
448 bool mCircularTrackNavigation{};
449 
OnCursorUpNavigationActions::Handler450 void OnCursorUp(const CommandContext &context)
451 {
452    auto &project = context.project;
453    DoPrevTrack( project, false, mCircularTrackNavigation );
454 }
455 
OnCursorDownNavigationActions::Handler456 void OnCursorDown(const CommandContext &context)
457 {
458    auto &project = context.project;
459    DoNextTrack( project, false, mCircularTrackNavigation );
460 }
461 
OnFirstTrackNavigationActions::Handler462 void 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 
OnLastTrackNavigationActions::Handler479 void 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 
OnShiftUpNavigationActions::Handler496 void OnShiftUp(const CommandContext &context)
497 {
498    auto &project = context.project;
499    DoPrevTrack( project, true, mCircularTrackNavigation );
500 }
501 
OnShiftDownNavigationActions::Handler502 void OnShiftDown(const CommandContext &context)
503 {
504    auto &project = context.project;
505    DoNextTrack( project, true, mCircularTrackNavigation );
506 }
507 
OnToggleNavigationActions::Handler508 void 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 
UpdatePrefsNavigationActions::Handler529 void UpdatePrefs() override
530 {
531    mCircularTrackNavigation =
532       gPrefs->ReadBool(wxT("/GUI/CircularTrackNavigation"), false);
533 }
HandlerNavigationActions::Handler534 Handler()
535 {
536    UpdatePrefs();
537 }
538 Handler( const Handler & ) PROHIBITED;
539 Handler &operator=( const Handler & ) PROHIBITED;
540 
541 }; // struct Handler
542 
543 } // namespace
544 
545 // Handler is stateful.  Needs a factory registered with
546 // AudacityProject.
547 static const AudacityProject::AttachedObjects::RegisteredFactory key{
__anondfd3e3490402() 548    [](AudacityProject&) {
549       return std::make_unique< NavigationActions::Handler >(); } };
550 
findCommandHandler(AudacityProject & project)551 static CommandHandlerObject &findCommandHandler(AudacityProject &project) {
552    return project.AttachedObjects::Get< NavigationActions::Handler >( key );
553 };
554 
555 // Menu definitions
556 
557 #define FN(X) (& NavigationActions::Handler :: X)
558 
559 namespace {
560 using namespace MenuTable;
ExtraGlobalCommands()561 BaseItemSharedPtr ExtraGlobalCommands()
562 {
563    // Ceci n'est pas un menu
564    using Options = CommandManager::Options;
565 
566    static BaseItemSharedPtr items{
567    ( FinderScope{ findCommandHandler },
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 
579 AttachedItem sAttachment2{
580    wxT("Optional/Extra/Part2"),
581    Shared( ExtraGlobalCommands() )
582 };
583 
ExtraFocusMenu()584 BaseItemSharedPtr ExtraFocusMenu()
585 {
586    static const auto FocusedTracksFlags = TracksExistFlag() | TrackPanelHasFocus();
587 
588    static BaseItemSharedPtr menu{
589    ( FinderScope{ findCommandHandler },
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 
617 AttachedItem sAttachment3{
618    wxT("Optional/Extra/Part2"),
619    Shared( ExtraFocusMenu() )
620 };
621 
622 }
623 
624 #undef FN
625