1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-06-22 23:30:07 +02:00

Navigation Menus

This commit is contained in:
Paul Licameli 2018-10-23 16:34:33 -04:00
parent c7521b211d
commit 4a8a30f1a9
3 changed files with 604 additions and 563 deletions

View File

@ -59,6 +59,7 @@ menu items.
#include <wx/utils.h>
#include "TrackPanel.h"
#include "widgets/Ruler.h"
#include "effects/EffectManager.h"
@ -113,7 +114,6 @@ menu items.
#include "prefs/TracksPrefs.h"
#include "widgets/Meter.h"
#include "widgets/ErrorDialog.h"
#include "./commands/AudacityCommand.h"
@ -155,8 +155,6 @@ MenuCreator::~MenuCreator()
void MenuCommandHandler::UpdatePrefs()
{
gPrefs->Read(wxT("/GUI/CircularTrackNavigation"), &mCircularTrackNavigation,
false);
}
void MenuManager::UpdatePrefs()
@ -367,13 +365,14 @@ MenuTable::BaseItemPtr WindowMenu( AudacityProject& );
MenuTable::BaseItemPtr ExtraWindowItems( AudacityProject & );
MenuTable::BaseItemPtr ExtraGlobalCommands( AudacityProject & );
MenuTable::BaseItemPtr ExtraFocusMenu( AudacityProject & );
namespace {
MenuTable::BaseItemPtr ExtraMenu( AudacityProject& );
MenuTable::BaseItemPtr ExtraMixerMenu( AudacityProject & );
MenuTable::BaseItemPtr ExtraDeviceMenu( AudacityProject & );
MenuTable::BaseItemPtr ExtraGlobalCommands( AudacityProject & );
MenuTable::BaseItemPtr ExtraFocusMenu( AudacityProject & );
MenuTable::BaseItemPtr ExtraMiscItems( AudacityProject & );
MenuTable::BaseItemPtr HelpMenu( AudacityProject& );
@ -467,52 +466,6 @@ MenuTable::BaseItemPtr ExtraDeviceMenu( AudacityProject & )
);
}
MenuTable::BaseItemPtr ExtraGlobalCommands( AudacityProject & )
{
// Ceci n'est pas un menu
using namespace MenuTable;
using Options = CommandManager::Options;
return Items(
Command( wxT("PrevWindow"), XXO("Move Backward Through Active Windows"),
FN(OnPrevWindow), AlwaysEnabledFlag,
Options{ wxT("Alt+Shift+F6") }.IsGlobal() ),
Command( wxT("NextWindow"), XXO("Move Forward Through Active Windows"),
FN(OnNextWindow), AlwaysEnabledFlag,
Options{ wxT("Alt+F6") }.IsGlobal() )
);
}
MenuTable::BaseItemPtr ExtraFocusMenu( AudacityProject & )
{
using namespace MenuTable;
constexpr auto FocusedTracksFlags = TracksExistFlag | TrackPanelHasFocus;
return Menu( _("F&ocus"),
Command( wxT("PrevFrame"),
XXO("Move &Backward from Toolbars to Tracks"), FN(OnPrevFrame),
AlwaysEnabledFlag, wxT("Ctrl+Shift+F6") ),
Command( wxT("NextFrame"),
XXO("Move F&orward from Toolbars to Tracks"), FN(OnNextFrame),
AlwaysEnabledFlag, wxT("Ctrl+F6") ),
Command( wxT("PrevTrack"), XXO("Move Focus to &Previous Track"),
FN(OnCursorUp), FocusedTracksFlags, wxT("Up") ),
Command( wxT("NextTrack"), XXO("Move Focus to &Next Track"),
FN(OnCursorDown), FocusedTracksFlags, wxT("Down") ),
Command( wxT("FirstTrack"), XXO("Move Focus to &First Track"),
FN(OnFirstTrack), FocusedTracksFlags, wxT("Ctrl+Home") ),
Command( wxT("LastTrack"), XXO("Move Focus to &Last Track"),
FN(OnLastTrack), FocusedTracksFlags, wxT("Ctrl+End") ),
Command( wxT("ShiftUp"), XXO("Move Focus to P&revious and Select"),
FN(OnShiftUp), FocusedTracksFlags, wxT("Shift+Up") ),
Command( wxT("ShiftDown"), XXO("Move Focus to N&ext and Select"),
FN(OnShiftDown), FocusedTracksFlags, wxT("Shift+Down") ),
Command( wxT("Toggle"), XXO("&Toggle Focused Track"), FN(OnToggle),
FocusedTracksFlags, wxT("Return") ),
Command( wxT("ToggleAlt"), XXO("Toggle Focuse&d Track"), FN(OnToggle),
FocusedTracksFlags, wxT("NUMPAD_ENTER") )
);
}
MenuTable::BaseItemPtr ExtraMiscItems( AudacityProject &project )
{
using namespace MenuTable;
@ -1150,493 +1103,6 @@ void AudacityProject::SortTracks(int flags)
/// selecting and unselecting depending if you are on the start of a
/// block or not.
/// \todo Merge related methods, OnPrevTrack and OnNextTrack.
void MenuCommandHandler::DoPrevTrack( AudacityProject &project, bool shift )
{
auto trackPanel = project.GetTrackPanel();
auto tracks = project.GetTracks();
auto &selectionState = project.GetSelectionState();
auto mixerBoard = project.GetMixerBoard();
Track* t = trackPanel->GetFocusedTrack();
if( t == NULL ) // if there isn't one, focus on last
{
t = *tracks->Any().rbegin();
trackPanel->SetFocusedTrack( t );
trackPanel->EnsureVisible( t );
project.ModifyState(false);
return;
}
Track* p = NULL;
bool tSelected = false;
bool pSelected = false;
if( shift )
{
p = * -- tracks->FindLeader( t ); // Get previous track
if( p == NULL ) // On first track
{
// JKC: wxBell() is probably for accessibility, so a blind
// user knows they were at the top track.
wxBell();
if( mCircularTrackNavigation )
p = *tracks->Any().rbegin();
else
{
trackPanel->EnsureVisible( t );
return;
}
}
tSelected = t->GetSelected();
if (p)
pSelected = p->GetSelected();
if( tSelected && pSelected )
{
selectionState.SelectTrack
( *t, false, false, mixerBoard );
trackPanel->SetFocusedTrack( p ); // move focus to next track up
trackPanel->EnsureVisible( p );
project.ModifyState(false);
return;
}
if( tSelected && !pSelected )
{
selectionState.SelectTrack
( *p, true, false, mixerBoard );
trackPanel->SetFocusedTrack( p ); // move focus to next track up
trackPanel->EnsureVisible( p );
project.ModifyState(false);
return;
}
if( !tSelected && pSelected )
{
selectionState.SelectTrack
( *p, false, false, mixerBoard );
trackPanel->SetFocusedTrack( p ); // move focus to next track up
trackPanel->EnsureVisible( p );
project.ModifyState(false);
return;
}
if( !tSelected && !pSelected )
{
selectionState.SelectTrack
( *t, true, false, mixerBoard );
trackPanel->SetFocusedTrack( p ); // move focus to next track up
trackPanel->EnsureVisible( p );
project.ModifyState(false);
return;
}
}
else
{
p = * -- tracks->FindLeader( t ); // Get previous track
if( p == NULL ) // On first track so stay there?
{
wxBell();
if( mCircularTrackNavigation )
{
auto range = tracks->Leaders();
p = * range.rbegin(); // null if range is empty
trackPanel->SetFocusedTrack( p ); // Wrap to the last track
trackPanel->EnsureVisible( p );
project.ModifyState(false);
return;
}
else
{
trackPanel->EnsureVisible( t );
return;
}
}
else
{
trackPanel->SetFocusedTrack( p ); // move focus to next track up
trackPanel->EnsureVisible( p );
project.ModifyState(false);
return;
}
}
}
/// The following method moves to the next track,
/// selecting and unselecting depending if you are on the start of a
/// block or not.
void MenuCommandHandler::DoNextTrack( AudacityProject &project, bool shift )
{
auto trackPanel = project.GetTrackPanel();
auto tracks = project.GetTracks();
auto &selectionState = project.GetSelectionState();
auto mixerBoard = project.GetMixerBoard();
auto t = trackPanel->GetFocusedTrack(); // Get currently focused track
if( t == NULL ) // if there isn't one, focus on first
{
t = *tracks->Any().begin();
trackPanel->SetFocusedTrack( t );
trackPanel->EnsureVisible( t );
project.ModifyState(false);
return;
}
if( shift )
{
auto n = * ++ tracks->FindLeader( t ); // Get next track
if( n == NULL ) // On last track so stay there
{
wxBell();
if( mCircularTrackNavigation )
n = *tracks->Any().begin();
else
{
trackPanel->EnsureVisible( t );
return;
}
}
auto tSelected = t->GetSelected();
auto nSelected = n->GetSelected();
if( tSelected && nSelected )
{
selectionState.SelectTrack
( *t, false, false, mixerBoard );
trackPanel->SetFocusedTrack( n ); // move focus to next track down
trackPanel->EnsureVisible( n );
project.ModifyState(false);
return;
}
if( tSelected && !nSelected )
{
selectionState.SelectTrack
( *n, true, false, mixerBoard );
trackPanel->SetFocusedTrack( n ); // move focus to next track down
trackPanel->EnsureVisible( n );
project.ModifyState(false);
return;
}
if( !tSelected && nSelected )
{
selectionState.SelectTrack
( *n, false, false, mixerBoard );
trackPanel->SetFocusedTrack( n ); // move focus to next track down
trackPanel->EnsureVisible( n );
project.ModifyState(false);
return;
}
if( !tSelected && !nSelected )
{
selectionState.SelectTrack
( *t, true, false, mixerBoard );
trackPanel->SetFocusedTrack( n ); // move focus to next track down
trackPanel->EnsureVisible( n );
project.ModifyState(false);
return;
}
}
else
{
auto n = * ++ tracks->FindLeader( t ); // Get next track
if( n == NULL ) // On last track so stay there
{
wxBell();
if( mCircularTrackNavigation )
{
n = *tracks->Any().begin();
trackPanel->SetFocusedTrack( n ); // Wrap to the first track
trackPanel->EnsureVisible( n );
project.ModifyState(false);
return;
}
else
{
trackPanel->EnsureVisible( t );
return;
}
}
else
{
trackPanel->SetFocusedTrack( n ); // move focus to next track down
trackPanel->EnsureVisible( n );
project.ModifyState(false);
return;
}
}
}
void MenuCommandHandler::OnCursorUp(const CommandContext &context)
{
auto &project = context.project;
DoPrevTrack( project, false );
}
void MenuCommandHandler::OnCursorDown(const CommandContext &context)
{
auto &project = context.project;
DoNextTrack( project, false );
}
void MenuCommandHandler::OnFirstTrack(const CommandContext &context)
{
auto &project = context.project;
auto trackPanel = project.GetTrackPanel();
auto tracks = project.GetTracks();
Track *t = trackPanel->GetFocusedTrack();
if (!t)
return;
auto f = *tracks->Any().begin();
if (t != f)
{
trackPanel->SetFocusedTrack(f);
project.ModifyState(false);
}
trackPanel->EnsureVisible(f);
}
void MenuCommandHandler::OnLastTrack(const CommandContext &context)
{
auto &project = context.project;
auto trackPanel = project.GetTrackPanel();
auto tracks = project.GetTracks();
Track *t = trackPanel->GetFocusedTrack();
if (!t)
return;
auto l = *tracks->Any().rbegin();
if (t != l)
{
trackPanel->SetFocusedTrack(l);
project.ModifyState(false);
}
trackPanel->EnsureVisible(l);
}
void MenuCommandHandler::OnShiftUp(const CommandContext &context)
{
auto &project = context.project;
DoPrevTrack( project, true );
}
void MenuCommandHandler::OnShiftDown(const CommandContext &context)
{
auto &project = context.project;
DoNextTrack( project, true );
}
#include "TrackPanelAx.h"
void MenuCommandHandler::OnToggle(const CommandContext &context)
{
auto &project = context.project;
auto trackPanel = project.GetTrackPanel();
auto &selectionState = project.GetSelectionState();
auto mixerBoard = project.GetMixerBoard();
Track *t;
t = trackPanel->GetFocusedTrack(); // Get currently focused track
if (!t)
return;
selectionState.SelectTrack
( *t, !t->GetSelected(), true, mixerBoard );
trackPanel->EnsureVisible( t );
project.ModifyState(false);
trackPanel->GetAx().Updated();
return;
}
void MenuCommandHandler::NextOrPrevFrame(AudacityProject &project, bool forward)
{
// Focus won't take in a dock unless at least one descendant window
// accepts focus. Tell controls to take focus for the duration of this
// function, only. Outside of this, they won't steal the focus when
// clicked.
auto temp1 = AButton::TemporarilyAllowFocus();
auto temp2 = ASlider::TemporarilyAllowFocus();
auto temp3 = MeterPanel::TemporarilyAllowFocus();
auto toolManager = project.GetToolManager();
auto botDock = toolManager->GetBotDock();
// Define the set of windows we rotate among.
static const unsigned rotationSize = 3u;
wxWindow *const begin [rotationSize] = {
project.GetTopPanel(),
project.GetTrackPanel(),
botDock,
};
const auto end = begin + rotationSize;
// helper functions
auto IndexOf = [&](wxWindow *pWindow) {
return std::find(begin, end, pWindow) - begin;
};
auto FindAncestor = [&]() {
wxWindow *pWindow = wxWindow::FindFocus();
unsigned index = rotationSize;
while ( pWindow &&
(rotationSize == (index = IndexOf(pWindow) ) ) )
pWindow = pWindow->GetParent();
return index;
};
const auto idx = FindAncestor();
if (idx == rotationSize)
return;
auto idx2 = idx;
auto increment = (forward ? 1 : rotationSize - 1);
while( idx != (idx2 = (idx2 + increment) % rotationSize) ) {
wxWindow *toFocus = begin[idx2];
bool bIsAnEmptyDock=false;
if( idx2 != 1 )
bIsAnEmptyDock = ((idx2==0) ? toolManager->GetTopDock() : botDock)->
GetChildren().GetCount() < 1;
// Skip docks that are empty (Bug 1564).
if( !bIsAnEmptyDock ){
toFocus->SetFocus();
if ( FindAncestor() == idx2 )
// The focus took!
break;
}
}
}
void MenuCommandHandler::OnNextFrame(const CommandContext &context)
{
auto &project = context.project;
NextOrPrevFrame(project, true);
}
void MenuCommandHandler::OnPrevFrame(const CommandContext &context)
{
auto &project = context.project;
NextOrPrevFrame(project, false);
}
void MenuCommandHandler::OnNextWindow(const CommandContext &context)
{
auto &project = context.project;
auto isEnabled = project.IsEnabled();
wxWindow *w = wxGetTopLevelParent(wxWindow::FindFocus());
const auto & list = project.GetChildren();
auto iter = list.begin(), end = list.end();
// If the project window has the current focus, start the search with the first child
if (w == &project)
{
}
// Otherwise start the search with the current window's next sibling
else
{
// Find the window in this projects children. If the window with the
// focus isn't a child of this project (like when a dialog is created
// without specifying a parent), then we'll get back NULL here.
while (iter != end && *iter != w)
++iter;
if (iter != end)
++iter;
}
// Search for the next toplevel window
for (; iter != end; ++iter)
{
// If it's a toplevel, visible (we have hidden windows) and is enabled,
// then we're done. The IsEnabled() prevents us from moving away from
// a modal dialog because all other toplevel windows will be disabled.
w = *iter;
if (w->IsTopLevel() && w->IsShown() && w->IsEnabled())
{
break;
}
}
// Ran out of siblings, so make the current project active
if ((iter == end) && isEnabled)
{
w = &project;
}
// And make sure it's on top (only for floating windows...project window will not raise)
// (Really only works on Windows)
w->Raise();
#if defined(__WXMAC__) || defined(__WXGTK__)
// bug 868
// Simulate a TAB key press before continuing, else the cycle of
// navigation among top level windows stops because the keystrokes don't
// go to the CommandManager.
if (dynamic_cast<wxDialog*>(w)) {
w->SetFocus();
}
#endif
}
void MenuCommandHandler::OnPrevWindow(const CommandContext &context)
{
auto &project = context.project;
auto isEnabled = project.IsEnabled();
wxWindow *w = wxGetTopLevelParent(wxWindow::FindFocus());
const auto & list = project.GetChildren();
auto iter = list.rbegin(), end = list.rend();
// If the project window has the current focus, start the search with the last child
if (w == &project)
{
}
// Otherwise start the search with the current window's previous sibling
else
{
while (iter != end && *iter != w)
++iter;
if (iter != end)
++iter;
}
// Search for the previous toplevel window
for (; iter != end; ++iter)
{
// If it's a toplevel and is visible (we have come hidden windows), then we're done
w = *iter;
if (w->IsTopLevel() && w->IsShown() && isEnabled)
{
break;
}
}
// Ran out of siblings, so make the current project active
if ((iter == end) && isEnabled)
{
w = &project;
}
// And make sure it's on top (only for floating windows...project window will not raise)
// (Really only works on Windows)
w->Raise();
#if defined(__WXMAC__) || defined(__WXGTK__)
// bug 868
// Simulate a TAB key press before continuing, else the cycle of
// navigation among top level windows stops because the keystrokes don't
// go to the CommandManager.
if (dynamic_cast<wxDialog*>(w)) {
w->SetFocus();
}
#endif
}
void MenuCommandHandler::OnInputDevice(const CommandContext &context)
{
auto &project = context.project;

View File

@ -64,20 +64,6 @@ void OnOutputGainDec(const CommandContext &context );
void OnInputGainInc(const CommandContext &context );
void OnInputGainDec(const CommandContext &context );
// Moving track focus commands
void DoPrevTrack( AudacityProject &project, bool shift );
void DoNextTrack( AudacityProject &project, bool shift );
void OnCursorUp(const CommandContext &context );
void OnCursorDown(const CommandContext &context );
void OnFirstTrack(const CommandContext &context );
void OnLastTrack(const CommandContext &context );
// Selection-Editing Commands
void OnShiftUp(const CommandContext &context );
void OnShiftDown(const CommandContext &context );
void OnToggle(const CommandContext &context );
void OnFullScreen(const CommandContext &context );
@ -126,18 +112,7 @@ void OnAudioDeviceInfo(const CommandContext &context );
void OnMidiDeviceInfo(const CommandContext &context );
#endif
// Keyboard navigation
void NextOrPrevFrame(AudacityProject &project, bool next);
void OnPrevFrame(const CommandContext &context );
void OnNextFrame(const CommandContext &context );
void OnPrevWindow(const CommandContext &context );
void OnNextWindow(const CommandContext &context );
public:
bool mCircularTrackNavigation{};
void UpdatePrefs() override;
};

View File

@ -0,0 +1,600 @@
#include "../Audacity.h"
#include "../Menus.h"
#include "../Prefs.h"
#include "../Project.h"
#include "../TrackPanel.h"
#include "../TrackPanelAx.h"
#include "../commands/CommandContext.h"
#include "../commands/CommandManager.h"
#include "../toolbars/ToolManager.h"
#include "../widgets/AButton.h"
#include "../widgets/ASlider.h"
#include "../widgets/Meter.h"
// private helper classes and functions
namespace {
void NextOrPrevFrame(AudacityProject &project, bool forward)
{
// Focus won't take in a dock unless at least one descendant window
// accepts focus. Tell controls to take focus for the duration of this
// function, only. Outside of this, they won't steal the focus when
// clicked.
auto temp1 = AButton::TemporarilyAllowFocus();
auto temp2 = ASlider::TemporarilyAllowFocus();
auto temp3 = MeterPanel::TemporarilyAllowFocus();
auto toolManager = project.GetToolManager();
auto botDock = toolManager->GetBotDock();
// Define the set of windows we rotate among.
static const unsigned rotationSize = 3u;
wxWindow *const begin [rotationSize] = {
project.GetTopPanel(),
project.GetTrackPanel(),
botDock,
};
const auto end = begin + rotationSize;
// helper functions
auto IndexOf = [&](wxWindow *pWindow) {
return std::find(begin, end, pWindow) - begin;
};
auto FindAncestor = [&]() {
wxWindow *pWindow = wxWindow::FindFocus();
unsigned index = rotationSize;
while ( pWindow &&
(rotationSize == (index = IndexOf(pWindow) ) ) )
pWindow = pWindow->GetParent();
return index;
};
const auto idx = FindAncestor();
if (idx == rotationSize)
return;
auto idx2 = idx;
auto increment = (forward ? 1 : rotationSize - 1);
while( idx != (idx2 = (idx2 + increment) % rotationSize) ) {
wxWindow *toFocus = begin[idx2];
bool bIsAnEmptyDock=false;
if( idx2 != 1 )
bIsAnEmptyDock = ((idx2==0) ? toolManager->GetTopDock() : botDock)->
GetChildren().GetCount() < 1;
// Skip docks that are empty (Bug 1564).
if( !bIsAnEmptyDock ){
toFocus->SetFocus();
if ( FindAncestor() == idx2 )
// The focus took!
break;
}
}
}
/// \todo Merge related methods, OnPrevTrack and OnNextTrack.
void DoPrevTrack(
AudacityProject &project, bool shift, bool circularTrackNavigation )
{
auto trackPanel = project.GetTrackPanel();
auto tracks = project.GetTracks();
auto &selectionState = project.GetSelectionState();
auto mixerBoard = project.GetMixerBoard();
Track* t = trackPanel->GetFocusedTrack();
if( t == NULL ) // if there isn't one, focus on last
{
t = *tracks->Any().rbegin();
trackPanel->SetFocusedTrack( t );
trackPanel->EnsureVisible( t );
project.ModifyState(false);
return;
}
Track* p = NULL;
bool tSelected = false;
bool pSelected = false;
if( shift )
{
p = * -- tracks->FindLeader( t ); // Get previous track
if( p == NULL ) // On first track
{
// JKC: wxBell() is probably for accessibility, so a blind
// user knows they were at the top track.
wxBell();
if( circularTrackNavigation )
p = *tracks->Any().rbegin();
else
{
trackPanel->EnsureVisible( t );
return;
}
}
tSelected = t->GetSelected();
if (p)
pSelected = p->GetSelected();
if( tSelected && pSelected )
{
selectionState.SelectTrack
( *t, false, false, mixerBoard );
trackPanel->SetFocusedTrack( p ); // move focus to next track up
trackPanel->EnsureVisible( p );
project.ModifyState(false);
return;
}
if( tSelected && !pSelected )
{
selectionState.SelectTrack
( *p, true, false, mixerBoard );
trackPanel->SetFocusedTrack( p ); // move focus to next track up
trackPanel->EnsureVisible( p );
project.ModifyState(false);
return;
}
if( !tSelected && pSelected )
{
selectionState.SelectTrack
( *p, false, false, mixerBoard );
trackPanel->SetFocusedTrack( p ); // move focus to next track up
trackPanel->EnsureVisible( p );
project.ModifyState(false);
return;
}
if( !tSelected && !pSelected )
{
selectionState.SelectTrack
( *t, true, false, mixerBoard );
trackPanel->SetFocusedTrack( p ); // move focus to next track up
trackPanel->EnsureVisible( p );
project.ModifyState(false);
return;
}
}
else
{
p = * -- tracks->FindLeader( t ); // Get previous track
if( p == NULL ) // On first track so stay there?
{
wxBell();
if( circularTrackNavigation )
{
auto range = tracks->Leaders();
p = * range.rbegin(); // null if range is empty
trackPanel->SetFocusedTrack( p ); // Wrap to the last track
trackPanel->EnsureVisible( p );
project.ModifyState(false);
return;
}
else
{
trackPanel->EnsureVisible( t );
return;
}
}
else
{
trackPanel->SetFocusedTrack( p ); // move focus to next track up
trackPanel->EnsureVisible( p );
project.ModifyState(false);
return;
}
}
}
/// The following method moves to the next track,
/// selecting and unselecting depending if you are on the start of a
/// block or not.
void DoNextTrack(
AudacityProject &project, bool shift, bool circularTrackNavigation )
{
auto trackPanel = project.GetTrackPanel();
auto tracks = project.GetTracks();
auto &selectionState = project.GetSelectionState();
auto mixerBoard = project.GetMixerBoard();
auto t = trackPanel->GetFocusedTrack(); // Get currently focused track
if( t == NULL ) // if there isn't one, focus on first
{
t = *tracks->Any().begin();
trackPanel->SetFocusedTrack( t );
trackPanel->EnsureVisible( t );
project.ModifyState(false);
return;
}
if( shift )
{
auto n = * ++ tracks->FindLeader( t ); // Get next track
if( n == NULL ) // On last track so stay there
{
wxBell();
if( circularTrackNavigation )
n = *tracks->Any().begin();
else
{
trackPanel->EnsureVisible( t );
return;
}
}
auto tSelected = t->GetSelected();
auto nSelected = n->GetSelected();
if( tSelected && nSelected )
{
selectionState.SelectTrack
( *t, false, false, mixerBoard );
trackPanel->SetFocusedTrack( n ); // move focus to next track down
trackPanel->EnsureVisible( n );
project.ModifyState(false);
return;
}
if( tSelected && !nSelected )
{
selectionState.SelectTrack
( *n, true, false, mixerBoard );
trackPanel->SetFocusedTrack( n ); // move focus to next track down
trackPanel->EnsureVisible( n );
project.ModifyState(false);
return;
}
if( !tSelected && nSelected )
{
selectionState.SelectTrack
( *n, false, false, mixerBoard );
trackPanel->SetFocusedTrack( n ); // move focus to next track down
trackPanel->EnsureVisible( n );
project.ModifyState(false);
return;
}
if( !tSelected && !nSelected )
{
selectionState.SelectTrack
( *t, true, false, mixerBoard );
trackPanel->SetFocusedTrack( n ); // move focus to next track down
trackPanel->EnsureVisible( n );
project.ModifyState(false);
return;
}
}
else
{
auto n = * ++ tracks->FindLeader( t ); // Get next track
if( n == NULL ) // On last track so stay there
{
wxBell();
if( circularTrackNavigation )
{
n = *tracks->Any().begin();
trackPanel->SetFocusedTrack( n ); // Wrap to the first track
trackPanel->EnsureVisible( n );
project.ModifyState(false);
return;
}
else
{
trackPanel->EnsureVisible( t );
return;
}
}
else
{
trackPanel->SetFocusedTrack( n ); // move focus to next track down
trackPanel->EnsureVisible( n );
project.ModifyState(false);
return;
}
}
}
}
namespace NavigationActions {
// exported helper functions
// none
// Menu handler functions
struct Handler
: CommandHandlerObject // MUST be the first base class!
, PrefsListener
{
void OnPrevWindow(const CommandContext &context)
{
auto &project = context.project;
auto isEnabled = project.IsEnabled();
wxWindow *w = wxGetTopLevelParent(wxWindow::FindFocus());
const auto & list = project.GetChildren();
auto iter = list.rbegin(), end = list.rend();
// If the project window has the current focus, start the search with the
// last child
if (w == &project)
{
}
// Otherwise start the search with the current window's previous sibling
else
{
while (iter != end && *iter != w)
++iter;
if (iter != end)
++iter;
}
// Search for the previous toplevel window
for (; iter != end; ++iter)
{
// If it's a toplevel and is visible (we have come hidden windows), then
// we're done
w = *iter;
if (w->IsTopLevel() && w->IsShown() && isEnabled)
{
break;
}
}
// Ran out of siblings, so make the current project active
if ((iter == end) && isEnabled)
{
w = &project;
}
// And make sure it's on top (only for floating windows...project window will
// not raise)
// (Really only works on Windows)
w->Raise();
#if defined(__WXMAC__) || defined(__WXGTK__)
// bug 868
// Simulate a TAB key press before continuing, else the cycle of
// navigation among top level windows stops because the keystrokes don't
// go to the CommandManager.
if (dynamic_cast<wxDialog*>(w)) {
w->SetFocus();
}
#endif
}
void OnNextWindow(const CommandContext &context)
{
auto &project = context.project;
auto isEnabled = project.IsEnabled();
wxWindow *w = wxGetTopLevelParent(wxWindow::FindFocus());
const auto & list = project.GetChildren();
auto iter = list.begin(), end = list.end();
// If the project window has the current focus, start the search with the
// first child
if (w == &project)
{
}
// Otherwise start the search with the current window's next sibling
else
{
// Find the window in this projects children. If the window with the
// focus isn't a child of this project (like when a dialog is created
// without specifying a parent), then we'll get back NULL here.
while (iter != end && *iter != w)
++iter;
if (iter != end)
++iter;
}
// Search for the next toplevel window
for (; iter != end; ++iter)
{
// If it's a toplevel, visible (we have hidden windows) and is enabled,
// then we're done. The IsEnabled() prevents us from moving away from
// a modal dialog because all other toplevel windows will be disabled.
w = *iter;
if (w->IsTopLevel() && w->IsShown() && w->IsEnabled())
{
break;
}
}
// Ran out of siblings, so make the current project active
if ((iter == end) && isEnabled)
{
w = &project;
}
// And make sure it's on top (only for floating windows...project window will
// not raise)
// (Really only works on Windows)
w->Raise();
#if defined(__WXMAC__) || defined(__WXGTK__)
// bug 868
// Simulate a TAB key press before continuing, else the cycle of
// navigation among top level windows stops because the keystrokes don't
// go to the CommandManager.
if (dynamic_cast<wxDialog*>(w)) {
w->SetFocus();
}
#endif
}
void OnPrevFrame(const CommandContext &context)
{
auto &project = context.project;
NextOrPrevFrame(project, false);
}
void OnNextFrame(const CommandContext &context)
{
auto &project = context.project;
NextOrPrevFrame(project, true);
}
// Handler state:
bool mCircularTrackNavigation{};
void OnCursorUp(const CommandContext &context)
{
auto &project = context.project;
DoPrevTrack( project, false, mCircularTrackNavigation );
}
void OnCursorDown(const CommandContext &context)
{
auto &project = context.project;
DoNextTrack( project, false, mCircularTrackNavigation );
}
void OnFirstTrack(const CommandContext &context)
{
auto &project = context.project;
auto trackPanel = project.GetTrackPanel();
auto tracks = project.GetTracks();
Track *t = trackPanel->GetFocusedTrack();
if (!t)
return;
auto f = *tracks->Any().begin();
if (t != f)
{
trackPanel->SetFocusedTrack(f);
project.ModifyState(false);
}
trackPanel->EnsureVisible(f);
}
void OnLastTrack(const CommandContext &context)
{
auto &project = context.project;
auto trackPanel = project.GetTrackPanel();
auto tracks = project.GetTracks();
Track *t = trackPanel->GetFocusedTrack();
if (!t)
return;
auto l = *tracks->Any().rbegin();
if (t != l)
{
trackPanel->SetFocusedTrack(l);
project.ModifyState(false);
}
trackPanel->EnsureVisible(l);
}
void OnShiftUp(const CommandContext &context)
{
auto &project = context.project;
DoPrevTrack( project, true, mCircularTrackNavigation );
}
void OnShiftDown(const CommandContext &context)
{
auto &project = context.project;
DoNextTrack( project, true, mCircularTrackNavigation );
}
void OnToggle(const CommandContext &context)
{
auto &project = context.project;
auto trackPanel = project.GetTrackPanel();
auto &selectionState = project.GetSelectionState();
auto mixerBoard = project.GetMixerBoard();
Track *t;
t = trackPanel->GetFocusedTrack(); // Get currently focused track
if (!t)
return;
selectionState.SelectTrack
( *t, !t->GetSelected(), true, mixerBoard );
trackPanel->EnsureVisible( t );
project.ModifyState(false);
trackPanel->GetAx().Updated();
return;
}
void UpdatePrefs() override
{
mCircularTrackNavigation =
gPrefs->ReadBool(wxT("/GUI/CircularTrackNavigation"), false);
}
}; // struct Handler
} // namespace
// Handler is stateful. Needs a factory registered with
// AudacityProject.
static const AudacityProject::RegisteredAttachedObjectFactory factory{ []{
return std::make_unique< NavigationActions::Handler >();
} };
static CommandHandlerObject &findCommandHandler(AudacityProject &project) {
return static_cast<NavigationActions::Handler&>(
project.GetAttachedObject(factory));
};
// Menu definitions
#define FN(X) findCommandHandler, \
static_cast<CommandFunctorPointer>(& NavigationActions::Handler :: X)
#define XXO(X) _(X), wxString{X}.Contains("...")
MenuTable::BaseItemPtr ExtraGlobalCommands( AudacityProject & )
{
// Ceci n'est pas un menu
using namespace MenuTable;
using Options = CommandManager::Options;
return Items(
Command( wxT("PrevWindow"), XXO("Move Backward Through Active Windows"),
FN(OnPrevWindow), AlwaysEnabledFlag,
Options{ wxT("Alt+Shift+F6") }.IsGlobal() ),
Command( wxT("NextWindow"), XXO("Move Forward Through Active Windows"),
FN(OnNextWindow), AlwaysEnabledFlag,
Options{ wxT("Alt+F6") }.IsGlobal() )
);
}
MenuTable::BaseItemPtr ExtraFocusMenu( AudacityProject & )
{
using namespace MenuTable;
constexpr auto FocusedTracksFlags = TracksExistFlag | TrackPanelHasFocus;
return Menu( _("F&ocus"),
Command( wxT("PrevFrame"),
XXO("Move &Backward from Toolbars to Tracks"), FN(OnPrevFrame),
AlwaysEnabledFlag, wxT("Ctrl+Shift+F6") ),
Command( wxT("NextFrame"),
XXO("Move F&orward from Toolbars to Tracks"), FN(OnNextFrame),
AlwaysEnabledFlag, wxT("Ctrl+F6") ),
Command( wxT("PrevTrack"), XXO("Move Focus to &Previous Track"),
FN(OnCursorUp), FocusedTracksFlags, wxT("Up") ),
Command( wxT("NextTrack"), XXO("Move Focus to &Next Track"),
FN(OnCursorDown), FocusedTracksFlags, wxT("Down") ),
Command( wxT("FirstTrack"), XXO("Move Focus to &First Track"),
FN(OnFirstTrack), FocusedTracksFlags, wxT("Ctrl+Home") ),
Command( wxT("LastTrack"), XXO("Move Focus to &Last Track"),
FN(OnLastTrack), FocusedTracksFlags, wxT("Ctrl+End") ),
Command( wxT("ShiftUp"), XXO("Move Focus to P&revious and Select"),
FN(OnShiftUp), FocusedTracksFlags, wxT("Shift+Up") ),
Command( wxT("ShiftDown"), XXO("Move Focus to N&ext and Select"),
FN(OnShiftDown), FocusedTracksFlags, wxT("Shift+Down") ),
Command( wxT("Toggle"), XXO("&Toggle Focused Track"), FN(OnToggle),
FocusedTracksFlags, wxT("Return") ),
Command( wxT("ToggleAlt"), XXO("Toggle Focuse&d Track"), FN(OnToggle),
FocusedTracksFlags, wxT("NUMPAD_ENTER") )
);
}
#undef XXO
#undef FN