1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-08-02 08:59:28 +02:00
audacity/src/menus/NavigationMenus.cpp
Paul Licameli 4d09705a73 Change XO to XXO in many more places, with no effects at all...
... because the two macros have the same expansion, and are both checked for
in the --keyword arguments passed to msgfmt by locale/update_po_files.sh.

This commit makes ONLY such changes, and comments in Internat.h.  It is big
but quite harmless.

The intention is to introduce a type distinction in a later release, by defining
XXO differently.  XXO is used where & characters in strings (for hotkeys of menu
items or control prompts) are permitted, XO where not.
2020-05-22 13:07:50 -04:00

624 lines
17 KiB
C++

#include "../Audacity.h"
#include "../CommonCommandFlags.h"
#include "../Prefs.h"
#include "../Project.h"
#include "../ProjectHistory.h"
#include "../ProjectWindow.h"
#include "../Track.h"
#include "../SelectionState.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 = ToolManager::Get( project );
auto botDock = toolManager.GetBotDock();
// Define the set of windows we rotate among.
static const unsigned rotationSize = 3u;
wxWindow *const begin [rotationSize] = {
ProjectWindow::Get( project ).GetTopPanel(),
&TrackPanel::Get( project ),
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 &projectHistory = ProjectHistory::Get( project );
auto &trackFocus = TrackFocus::Get( project );
auto &tracks = TrackList::Get( project );
auto &selectionState = SelectionState::Get( project );
auto t = trackFocus.Get();
if( t == NULL ) // if there isn't one, focus on last
{
t = *tracks.Any().rbegin();
trackFocus.Set( t );
if (t)
t->EnsureVisible( true );
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
{
t->EnsureVisible();
return;
}
}
tSelected = t->GetSelected();
if (p)
pSelected = p->GetSelected();
if( tSelected && pSelected )
{
selectionState.SelectTrack
( *t, false, false );
trackFocus.Set( p ); // move focus to next track up
if (p)
p->EnsureVisible( true );
return;
}
if( tSelected && !pSelected )
{
selectionState.SelectTrack
( *p, true, false );
trackFocus.Set( p ); // move focus to next track up
if (p)
p->EnsureVisible( true );
return;
}
if( !tSelected && pSelected )
{
selectionState.SelectTrack
( *p, false, false );
trackFocus.Set( p ); // move focus to next track up
if (p)
p->EnsureVisible( true );
return;
}
if( !tSelected && !pSelected )
{
selectionState.SelectTrack
( *t, true, false );
trackFocus.Set( p ); // move focus to next track up
if (p)
p->EnsureVisible( true );
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
trackFocus.Set( p ); // Wrap to the last track
if (p)
p->EnsureVisible( true );
return;
}
else
{
t->EnsureVisible();
return;
}
}
else
{
trackFocus.Set( p ); // move focus to next track up
p->EnsureVisible( true );
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 &projectHistory = ProjectHistory::Get( project );
auto &trackFocus = TrackFocus::Get( project );
auto &tracks = TrackList::Get( project );
auto &selectionState = SelectionState::Get( project );
auto t = trackFocus.Get(); // Get currently focused track
if( t == NULL ) // if there isn't one, focus on first
{
t = *tracks.Any().begin();
trackFocus.Set( t );
if (t)
t->EnsureVisible( true );
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
{
t->EnsureVisible();
return;
}
}
auto tSelected = t->GetSelected();
auto nSelected = n->GetSelected();
if( tSelected && nSelected )
{
selectionState.SelectTrack
( *t, false, false );
trackFocus.Set( n ); // move focus to next track down
if (n)
n->EnsureVisible( true );
return;
}
if( tSelected && !nSelected )
{
selectionState.SelectTrack
( *n, true, false );
trackFocus.Set( n ); // move focus to next track down
if (n)
n->EnsureVisible( true );
return;
}
if( !tSelected && nSelected )
{
selectionState.SelectTrack
( *n, false, false );
trackFocus.Set( n ); // move focus to next track down
if (n)
n->EnsureVisible( true );
return;
}
if( !tSelected && !nSelected )
{
selectionState.SelectTrack
( *t, true, false );
trackFocus.Set( n ); // move focus to next track down
if (n)
n->EnsureVisible( true );
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();
trackFocus.Set( n ); // Wrap to the first track
if (n)
n->EnsureVisible( true );
return;
}
else
{
t->EnsureVisible();
return;
}
}
else
{
trackFocus.Set( n ); // move focus to next track down
n->EnsureVisible( true );
return;
}
}
}
}
/// Namespace for functions for project navigation menu (part of Extra menu)
namespace NavigationActions {
// exported helper functions
// none
// Menu handler functions
struct Handler
: CommandHandlerObject // MUST be the first base class!
, ClientData::Base
, PrefsListener
{
void OnPrevWindow(const CommandContext &context)
{
auto &project = context.project;
auto &window = GetProjectFrame( project );
auto isEnabled = window.IsEnabled();
wxWindow *w = wxGetTopLevelParent(wxWindow::FindFocus());
const auto & list = window.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 == &window)
{
}
// 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 = &window;
}
// 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 &window = GetProjectFrame( project );
auto isEnabled = window.IsEnabled();
wxWindow *w = wxGetTopLevelParent(wxWindow::FindFocus());
const auto & list = window.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 == &window)
{
}
// 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 = &window;
}
// 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 &trackFocus = TrackFocus::Get( project );
auto &tracks = TrackList::Get( project );
auto t = trackFocus.Get();
if (!t)
return;
auto f = *tracks.Any().begin();
if (t != f)
trackFocus.Set(f);
if (f)
f->EnsureVisible( t != f );
}
void OnLastTrack(const CommandContext &context)
{
auto &project = context.project;
auto &trackFocus = TrackFocus::Get( project );
auto &tracks = TrackList::Get( project );
Track *t = trackFocus.Get();
if (!t)
return;
auto l = *tracks.Any().rbegin();
if (t != l)
trackFocus.Set(l);
if (l)
l->EnsureVisible( t != 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 &trackFocus = TrackFocus::Get( project );
auto &selectionState = SelectionState::Get( project );
Track *t;
t = trackFocus.Get(); // Get currently focused track
if (!t)
return;
selectionState.SelectTrack
( *t, !t->GetSelected(), true );
t->EnsureVisible( true );
trackFocus.UpdateAccessibility();
return;
}
void UpdatePrefs() override
{
mCircularTrackNavigation =
gPrefs->ReadBool(wxT("/GUI/CircularTrackNavigation"), false);
}
Handler()
{
UpdatePrefs();
}
Handler( const Handler & ) PROHIBITED;
Handler &operator=( const Handler & ) PROHIBITED;
}; // struct Handler
} // namespace
// Handler is stateful. Needs a factory registered with
// AudacityProject.
static const AudacityProject::AttachedObjects::RegisteredFactory key{
[](AudacityProject&) {
return std::make_unique< NavigationActions::Handler >(); } };
static CommandHandlerObject &findCommandHandler(AudacityProject &project) {
return project.AttachedObjects::Get< NavigationActions::Handler >( key );
};
// Menu definitions
#define FN(X) (& NavigationActions::Handler :: X)
namespace {
using namespace MenuTable;
BaseItemSharedPtr ExtraGlobalCommands()
{
// Ceci n'est pas un menu
using Options = CommandManager::Options;
static BaseItemSharedPtr items{
( FinderScope{ findCommandHandler },
Items( wxT("Navigation"),
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() )
) ) };
return items;
}
AttachedItem sAttachment2{
wxT("Optional/Extra/Part2"),
Shared( ExtraGlobalCommands() )
};
BaseItemSharedPtr ExtraFocusMenu()
{
static const auto FocusedTracksFlags = TracksExistFlag() | TrackPanelHasFocus();
static BaseItemSharedPtr menu{
( FinderScope{ findCommandHandler },
Menu( wxT("Focus"), XXO("Foc&us"),
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") )
) ) };
return menu;
}
AttachedItem sAttachment3{
wxT("Optional/Extra/Part2"),
Shared( ExtraFocusMenu() )
};
}
#undef FN