2010-01-23 19:44:49 +00:00
|
|
|
/**********************************************************************
|
|
|
|
|
|
|
|
Audacity: A Digital Audio Editor
|
|
|
|
|
|
|
|
CommandManager.cpp
|
|
|
|
|
|
|
|
Brian Gunlogson
|
|
|
|
Dominic Mazzoni
|
|
|
|
|
|
|
|
*******************************************************************//**
|
|
|
|
|
|
|
|
\class CommandManager
|
|
|
|
\brief CommandManager implements a system for organizing all user-callable
|
2014-06-03 20:30:19 +00:00
|
|
|
commands.
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
It creates and manages a menu bar with a command
|
|
|
|
associated with each item, and managing other commands callable
|
|
|
|
by keyboard shortcuts.
|
|
|
|
|
|
|
|
Commands are implemented by overriding an abstract functor class.
|
|
|
|
See Menus.cpp for an example use.
|
|
|
|
|
|
|
|
Menus or submenus containing lists of items can be added at once,
|
|
|
|
with a single function (functor) to be called when any of the
|
|
|
|
items is selected, with the index number of the selection as the
|
|
|
|
parameter. This is useful for dynamic menus (effects) and
|
|
|
|
submenus containing a list of choices (selection formats).
|
|
|
|
|
|
|
|
Menu items can be enabled or disabled individually, groups of
|
|
|
|
"multi-items" can be enabled or disabled all at once, or entire
|
|
|
|
sets of commands can be enabled or disabled all at once using
|
|
|
|
flags. The flags should be a bitfield stored in a 32-bit
|
|
|
|
integer but can be whatever you want. You specify both the
|
|
|
|
desired values of the flags, and the set of flags relevant to
|
|
|
|
a particular command, by using a combination of a flags parameter
|
|
|
|
and a mask parameter. Any flag set to 0 in the mask parameter is
|
|
|
|
the same as "don't care". Any command whose mask is set to zero
|
|
|
|
will not be affected by enabling/disabling by flags.
|
|
|
|
|
|
|
|
*//****************************************************************//**
|
|
|
|
|
|
|
|
\class CommandFunctor
|
2014-06-03 20:30:19 +00:00
|
|
|
\brief CommandFunctor is a very small class that works with
|
2010-01-23 19:44:49 +00:00
|
|
|
CommandManager. It holds the callback for one command.
|
|
|
|
|
|
|
|
*//****************************************************************//**
|
|
|
|
|
|
|
|
\class MenuBarListEntry
|
|
|
|
\brief MenuBarListEntry is a structure used by CommandManager.
|
|
|
|
|
|
|
|
*//****************************************************************//**
|
|
|
|
|
|
|
|
\class SubMenuListEntry
|
|
|
|
\brief SubMenuListEntry is a structure used by CommandManager.
|
|
|
|
|
|
|
|
*//****************************************************************//**
|
|
|
|
|
|
|
|
\class CommandListEntry
|
|
|
|
\brief CommandListEntry is a structure used by CommandManager.
|
|
|
|
|
|
|
|
*//****************************************************************//**
|
|
|
|
|
|
|
|
\class MenuBarList
|
|
|
|
\brief List of MenuBarListEntry.
|
|
|
|
|
|
|
|
*//****************************************************************//**
|
|
|
|
|
|
|
|
\class SubMenuList
|
|
|
|
\brief List of SubMenuListEntry.
|
|
|
|
|
|
|
|
*//****************************************************************//**
|
|
|
|
|
|
|
|
\class CommandList
|
|
|
|
\brief List of CommandListEntry.
|
|
|
|
|
|
|
|
*//******************************************************************/
|
|
|
|
|
2021-05-09 15:16:56 +00:00
|
|
|
|
2018-11-11 02:40:37 +00:00
|
|
|
|
2016-02-19 06:39:44 +00:00
|
|
|
#include "CommandManager.h"
|
2018-11-10 19:47:12 +00:00
|
|
|
|
Automation: AudacityCommand
This is a squash of 50 commits.
This merges the capabilities of BatchCommands and Effects using a new
AudacityCommand class. AudacityCommand provides one function to specify the
parameters, and then we leverage that one function in automation, whether by chains,
mod-script-pipe or (future) Nyquist.
- Now have AudacityCommand which is using the same mechanism as Effect
- Has configurable parameters
- Has data-entry GUI (built using shuttle GUI)
- Registers with PluginManager.
- Menu commands now provided in chains, and to python batch.
- Tested with Zoom Toggle.
- ShuttleParams now can set, get, set defaults, validate and specify
the parameters.
- Bugfix: Don't overwrite values with defaults first time out.
- Add DefineParams function for all built-in effects.
- Extend CommandContext to carry output channels for results.
We abuse EffectsManager. It handles both Effects and
AudacityCommands now. In time an Effect should become a special case of
AudacityCommand and we'll split and rename the EffectManager class.
- Don't use 'default' as a parameter name.
- Massive renaming for CommandDefinitionInterface
- EffectIdentInterface becomes EffectDefinitionInterface
- EffectAutomationParameters becomes CommandAutomationParameters
- PluginType is now a bit field.
This way we can search for related types at the same time.
- Most old batch commands made into AudacityCommands.
The ones that weren't are for a reason. They are used by mod-script-pipe
to carry commands and responses across from a non-GUI thread to the GUI
thread.
- Major tidy up of ScreenshotCommand
- Reworking of SelectCommand
- GetPreferenceCommand and SetPreferenceCommand
- GetTrackInfo and SetTrackInfo
- GetInfoCommand
- Help, Open, Save, Import and Export commands.
- Removed obsolete commands ExecMenu, GetProjectInfo and SetProjectInfo
which are now better handled by other commands.
- JSONify "GetInfo: Commands" output, i.e. commas in the right places.
- General work on better Doxygen.
- Lyrics -> LyricsPanel
- Meter -> MeterPanel
- Updated Linux makefile.
- Scripting commands added into Extra menu.
- Distinct names for previously duplicated find-clipping parameters.
- Fixed longstanding error with erroneous status field number which
previously caused an ASSERT in debug.
- Sensible formatting of numbers in Chains, 0.1 not 0.1000000000137
2018-01-14 18:51:41 +00:00
|
|
|
#include "CommandContext.h"
|
2019-06-02 18:14:01 +00:00
|
|
|
#include "CommandManagerWindowClasses.h"
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
#include <wx/defs.h>
|
2019-05-22 13:59:22 +00:00
|
|
|
#include <wx/evtloop.h>
|
2019-06-03 13:54:58 +00:00
|
|
|
#include <wx/frame.h>
|
2010-01-23 19:44:49 +00:00
|
|
|
#include <wx/hash.h>
|
|
|
|
#include <wx/intl.h>
|
|
|
|
#include <wx/log.h>
|
2019-03-23 14:53:24 +00:00
|
|
|
#include <wx/menu.h>
|
2010-01-23 19:44:49 +00:00
|
|
|
#include <wx/tokenzr.h>
|
|
|
|
|
2018-10-16 20:45:26 +00:00
|
|
|
#include "../Menus.h"
|
2018-04-11 14:11:06 +00:00
|
|
|
|
2019-05-22 13:59:22 +00:00
|
|
|
#include "../Project.h"
|
2019-05-20 18:27:11 +00:00
|
|
|
#include "../widgets/AudacityMessageBox.h"
|
2017-08-25 12:54:37 +00:00
|
|
|
#include "../widgets/HelpSystem.h"
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2018-04-11 14:11:06 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
// On wxGTK, there may be many many many plugins, but the menus don't automatically
|
|
|
|
// allow for scrolling, so we build sub-menus. If the menu gets longer than
|
|
|
|
// MAX_MENU_LEN, we put things in submenus that have MAX_SUBMENU_LEN items in them.
|
2014-06-03 20:30:19 +00:00
|
|
|
//
|
2010-01-23 19:44:49 +00:00
|
|
|
#ifdef __WXGTK__
|
|
|
|
#define MAX_MENU_LEN 20
|
|
|
|
#define MAX_SUBMENU_LEN 15
|
|
|
|
#else
|
|
|
|
#define MAX_MENU_LEN 1000
|
|
|
|
#define MAX_SUBMENU_LEN 1000
|
|
|
|
#endif
|
|
|
|
|
2019-01-09 20:26:32 +00:00
|
|
|
#define COMMAND XO("Command")
|
2015-07-28 19:35:09 +00:00
|
|
|
|
|
|
|
|
2020-05-03 15:21:01 +00:00
|
|
|
struct MenuBarListEntry
|
|
|
|
{
|
|
|
|
MenuBarListEntry(const wxString &name_, wxMenuBar *menubar_);
|
|
|
|
~MenuBarListEntry();
|
|
|
|
|
|
|
|
wxString name;
|
|
|
|
wxWeakRef<wxMenuBar> menubar; // This structure does not assume memory ownership!
|
|
|
|
};
|
|
|
|
|
|
|
|
struct SubMenuListEntry
|
|
|
|
{
|
|
|
|
SubMenuListEntry( const TranslatableString &name_ );
|
|
|
|
SubMenuListEntry( SubMenuListEntry&& ) = default;
|
|
|
|
~SubMenuListEntry();
|
|
|
|
|
|
|
|
TranslatableString name;
|
|
|
|
std::unique_ptr<wxMenu> menu;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct CommandListEntry
|
|
|
|
{
|
|
|
|
int id;
|
|
|
|
CommandID name;
|
|
|
|
TranslatableString longLabel;
|
|
|
|
NormalizedKeyString key;
|
|
|
|
NormalizedKeyString defaultKey;
|
|
|
|
TranslatableString label;
|
|
|
|
TranslatableString labelPrefix;
|
|
|
|
TranslatableString labelTop;
|
|
|
|
wxMenu *menu;
|
|
|
|
CommandHandlerFinder finder;
|
|
|
|
CommandFunctorPointer callback;
|
|
|
|
CommandParameter parameter;
|
|
|
|
|
|
|
|
// type of a function that determines checkmark state
|
|
|
|
using CheckFn = std::function< bool(AudacityProject&) >;
|
|
|
|
CheckFn checkmarkFn;
|
|
|
|
|
|
|
|
bool multi;
|
|
|
|
int index;
|
|
|
|
int count;
|
|
|
|
bool enabled;
|
|
|
|
bool skipKeydown;
|
|
|
|
bool wantKeyup;
|
|
|
|
bool allowDup;
|
|
|
|
bool isGlobal;
|
|
|
|
bool isOccult;
|
|
|
|
bool isEffect;
|
|
|
|
bool excludeFromMacros;
|
|
|
|
CommandFlag flags;
|
|
|
|
bool useStrictFlags{ false };
|
|
|
|
};
|
|
|
|
|
2018-12-28 16:49:35 +00:00
|
|
|
NonKeystrokeInterceptingWindow::~NonKeystrokeInterceptingWindow()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
TopLevelKeystrokeHandlingWindow::~TopLevelKeystrokeHandlingWindow()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2019-03-23 14:53:24 +00:00
|
|
|
MenuBarListEntry::MenuBarListEntry(const wxString &name_, wxMenuBar *menubar_)
|
|
|
|
: name(name_), menubar(menubar_)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
MenuBarListEntry::~MenuBarListEntry()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2020-05-03 15:21:01 +00:00
|
|
|
SubMenuListEntry::SubMenuListEntry( const TranslatableString &name_ )
|
|
|
|
: name(name_), menu( std::make_unique< wxMenu >() )
|
2019-03-23 14:53:24 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
SubMenuListEntry::~SubMenuListEntry()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2019-04-26 21:17:04 +00:00
|
|
|
///
|
|
|
|
static const AudacityProject::AttachedObjects::RegisteredFactory key{
|
|
|
|
[](AudacityProject&) {
|
|
|
|
return std::make_unique<CommandManager>();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
CommandManager &CommandManager::Get( AudacityProject &project )
|
|
|
|
{
|
|
|
|
return project.AttachedObjects::Get< CommandManager >( key );
|
|
|
|
}
|
|
|
|
|
|
|
|
const CommandManager &CommandManager::Get( const AudacityProject &project )
|
|
|
|
{
|
|
|
|
return Get( const_cast< AudacityProject & >( project ) );
|
|
|
|
}
|
|
|
|
|
2019-06-10 23:10:07 +00:00
|
|
|
static CommandManager::MenuHook &sMenuHook()
|
|
|
|
{
|
|
|
|
static CommandManager::MenuHook theHook;
|
|
|
|
return theHook;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto CommandManager::SetMenuHook( const MenuHook &hook ) -> MenuHook
|
|
|
|
{
|
|
|
|
auto &theHook = sMenuHook();
|
|
|
|
auto result = theHook;
|
|
|
|
theHook = hook;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
///
|
|
|
|
/// Standard Constructor
|
|
|
|
///
|
|
|
|
CommandManager::CommandManager():
|
2015-07-28 19:35:09 +00:00
|
|
|
mCurrentID(17000),
|
2010-01-23 19:44:49 +00:00
|
|
|
mCurrentMenuName(COMMAND),
|
2017-04-21 14:47:17 +00:00
|
|
|
bMakingOccultCommands( false )
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
mbSeparatorAllowed = false;
|
2017-06-28 22:00:02 +00:00
|
|
|
SetMaxList();
|
2021-02-03 11:02:49 +00:00
|
|
|
mLastProcessId = 0;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
///
|
|
|
|
/// Class Destructor. Includes PurgeData, which removes
|
|
|
|
/// menubars
|
|
|
|
CommandManager::~CommandManager()
|
|
|
|
{
|
|
|
|
//WARNING: This removes menubars that could still be assigned to windows!
|
|
|
|
PurgeData();
|
|
|
|
}
|
|
|
|
|
2018-02-09 02:16:13 +00:00
|
|
|
const std::vector<NormalizedKeyString> &CommandManager::ExcludedList()
|
|
|
|
{
|
|
|
|
static const auto list = [] {
|
|
|
|
// These short cuts are for the max list only....
|
|
|
|
const char *const strings[] = {
|
2019-04-06 13:34:04 +00:00
|
|
|
// "Ctrl+I",
|
2018-02-09 02:16:13 +00:00
|
|
|
"Ctrl+Alt+I",
|
|
|
|
"Ctrl+J",
|
|
|
|
"Ctrl+Alt+J",
|
|
|
|
"Ctrl+Alt+V",
|
|
|
|
"Alt+X",
|
|
|
|
"Alt+K",
|
|
|
|
"Shift+Alt+X",
|
|
|
|
"Shift+Alt+K",
|
|
|
|
"Alt+L",
|
|
|
|
"Shift+Alt+C",
|
|
|
|
"Alt+I",
|
|
|
|
"Alt+J",
|
|
|
|
"Shift+Alt+J",
|
|
|
|
"Ctrl+Shift+A",
|
2019-07-15 07:53:16 +00:00
|
|
|
//"Q",
|
2018-02-09 02:16:13 +00:00
|
|
|
//"Shift+J",
|
|
|
|
//"Shift+K",
|
|
|
|
//"Shift+Home",
|
|
|
|
//"Shift+End",
|
|
|
|
"Ctrl+[",
|
|
|
|
"Ctrl+]",
|
|
|
|
"1",
|
|
|
|
"Shift+F5",
|
|
|
|
"Shift+F6",
|
|
|
|
"Shift+F7",
|
|
|
|
"Shift+F8",
|
|
|
|
"Ctrl+Shift+F5",
|
|
|
|
"Ctrl+Shift+F7",
|
|
|
|
"Ctrl+Shift+N",
|
|
|
|
"Ctrl+Shift+M",
|
|
|
|
"Ctrl+Home",
|
|
|
|
"Ctrl+End",
|
|
|
|
"Shift+C",
|
|
|
|
"Alt+Shift+Up",
|
|
|
|
"Alt+Shift+Down",
|
|
|
|
"Shift+P",
|
|
|
|
"Alt+Shift+Left",
|
|
|
|
"Alt+Shift+Right",
|
|
|
|
"Ctrl+Shift+T",
|
|
|
|
//"Command+M",
|
|
|
|
//"Option+Command+M",
|
|
|
|
"Shift+H",
|
|
|
|
"Shift+O",
|
|
|
|
"Shift+I",
|
|
|
|
"Shift+N",
|
|
|
|
"D",
|
|
|
|
"A",
|
|
|
|
"Alt+Shift+F6",
|
|
|
|
"Alt+F6",
|
|
|
|
};
|
|
|
|
|
|
|
|
std::vector<NormalizedKeyString> result(
|
2019-02-28 12:37:10 +00:00
|
|
|
std::begin(strings), std::end(strings)
|
|
|
|
);
|
2018-02-09 02:16:13 +00:00
|
|
|
std::sort( result.begin(), result.end() );
|
|
|
|
return result;
|
|
|
|
}();
|
|
|
|
return list;
|
|
|
|
}
|
|
|
|
|
2018-08-15 10:29:12 +00:00
|
|
|
// CommandManager needs to know which defaults are standard and which are in the
|
2017-06-28 22:00:02 +00:00
|
|
|
// full (max) list.
|
|
|
|
void CommandManager::SetMaxList()
|
|
|
|
{
|
|
|
|
|
|
|
|
// This list is a DUPLICATE of the list in
|
|
|
|
// KeyConfigPrefs::OnImportDefaults(wxCommandEvent & event)
|
|
|
|
|
|
|
|
// TODO: At a later date get rid of the maxList entirely and
|
2021-01-24 09:46:08 +00:00
|
|
|
// instead use flags in the menu entries to indicate whether the default
|
2017-06-28 22:00:02 +00:00
|
|
|
// shortcut is standard or full.
|
|
|
|
|
2018-02-09 02:16:13 +00:00
|
|
|
mMaxListOnly.clear();
|
2017-06-30 20:38:20 +00:00
|
|
|
|
|
|
|
// if the full list, don't exclude any.
|
|
|
|
bool bFull = gPrefs->ReadBool(wxT("/GUI/Shortcuts/FullDefaults"),false);
|
|
|
|
if( bFull )
|
|
|
|
return;
|
|
|
|
|
2018-02-09 02:16:13 +00:00
|
|
|
mMaxListOnly = ExcludedList();
|
2017-06-28 22:00:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
void CommandManager::PurgeData()
|
|
|
|
{
|
|
|
|
// mCommandList contains pointers to CommandListEntrys
|
2016-02-19 00:09:10 +00:00
|
|
|
// mMenuBarList contains MenuBarListEntrys.
|
|
|
|
// mSubMenuList contains SubMenuListEntrys
|
2016-02-19 15:49:50 +00:00
|
|
|
mCommandList.clear();
|
2016-02-19 00:09:10 +00:00
|
|
|
mMenuBarList.clear();
|
|
|
|
mSubMenuList.clear();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
mCommandNameHash.clear();
|
|
|
|
mCommandKeyHash.clear();
|
2019-02-27 17:57:21 +00:00
|
|
|
mCommandNumericIDHash.clear();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
mCurrentMenuName = COMMAND;
|
2017-05-15 22:17:10 +00:00
|
|
|
mCurrentID = 17000;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
///
|
2016-02-13 15:43:16 +00:00
|
|
|
/// Makes a NEW menubar for placement on the top of a project
|
2010-01-23 19:44:49 +00:00
|
|
|
/// Names it according to the passed-in string argument.
|
|
|
|
///
|
2016-02-21 01:23:54 +00:00
|
|
|
/// If the menubar already exists, that's unexpected.
|
|
|
|
std::unique_ptr<wxMenuBar> CommandManager::AddMenuBar(const wxString & sMenu)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
wxMenuBar *menuBar = GetMenuBar(sMenu);
|
2016-02-21 01:23:54 +00:00
|
|
|
if (menuBar) {
|
|
|
|
wxASSERT(false);
|
|
|
|
return {};
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2016-02-21 01:23:54 +00:00
|
|
|
auto result = std::make_unique<wxMenuBar>();
|
|
|
|
mMenuBarList.emplace_back(sMenu, result.get());
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2016-02-19 00:09:10 +00:00
|
|
|
return result;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
///
|
2014-06-03 20:30:19 +00:00
|
|
|
/// Retrieves the menubar based on the name given in AddMenuBar(name)
|
2010-01-23 19:44:49 +00:00
|
|
|
///
|
2015-08-16 11:18:41 +00:00
|
|
|
wxMenuBar * CommandManager::GetMenuBar(const wxString & sMenu) const
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-02-19 00:09:10 +00:00
|
|
|
for (const auto &entry : mMenuBarList)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2019-03-10 18:12:35 +00:00
|
|
|
if(entry.name == sMenu)
|
2016-02-19 00:09:10 +00:00
|
|
|
return entry.menubar;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
///
|
|
|
|
/// Retrieve the 'current' menubar; either NULL or the
|
|
|
|
/// last on in the mMenuBarList.
|
2015-08-08 05:01:24 +00:00
|
|
|
wxMenuBar * CommandManager::CurrentMenuBar() const
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-02-19 00:09:10 +00:00
|
|
|
if(mMenuBarList.empty())
|
2010-01-23 19:44:49 +00:00
|
|
|
return NULL;
|
|
|
|
|
2016-02-19 00:09:10 +00:00
|
|
|
return mMenuBarList.back().menubar;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2018-08-15 10:29:12 +00:00
|
|
|
///
|
|
|
|
/// Typically used to switch back and forth
|
|
|
|
/// between adding to a hidden menu bar and
|
2018-10-20 16:50:22 +00:00
|
|
|
/// adding to one that is visible
|
2018-07-05 18:34:16 +00:00
|
|
|
///
|
2018-10-20 16:50:22 +00:00
|
|
|
void CommandManager::PopMenuBar()
|
2018-07-05 18:34:16 +00:00
|
|
|
{
|
2018-10-20 16:50:22 +00:00
|
|
|
auto iter = mMenuBarList.end();
|
|
|
|
if ( iter != mMenuBarList.begin() )
|
|
|
|
mMenuBarList.erase( --iter );
|
|
|
|
else
|
|
|
|
wxASSERT( false );
|
2018-07-05 18:34:16 +00:00
|
|
|
}
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
///
|
2016-02-13 15:43:16 +00:00
|
|
|
/// This starts a NEW menu
|
2010-01-23 19:44:49 +00:00
|
|
|
///
|
2019-01-09 20:26:32 +00:00
|
|
|
wxMenu *CommandManager::BeginMenu(const TranslatableString & tName)
|
2018-10-18 12:52:37 +00:00
|
|
|
{
|
|
|
|
if ( mCurrentMenu )
|
|
|
|
return BeginSubMenu( tName );
|
|
|
|
else
|
|
|
|
return BeginMainMenu( tName );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
///
|
|
|
|
/// This attaches a menu, if it's main, to the menubar
|
|
|
|
// and in all cases ends the menu
|
|
|
|
///
|
|
|
|
void CommandManager::EndMenu()
|
|
|
|
{
|
|
|
|
if ( mSubMenuList.empty() )
|
|
|
|
EndMainMenu();
|
|
|
|
else
|
|
|
|
EndSubMenu();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
///
|
|
|
|
/// This starts a NEW menu
|
|
|
|
///
|
2019-01-09 20:26:32 +00:00
|
|
|
wxMenu *CommandManager::BeginMainMenu(const TranslatableString & tName)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-10-02 13:46:48 +00:00
|
|
|
uCurrentMenu = std::make_unique<wxMenu>();
|
|
|
|
mCurrentMenu = uCurrentMenu.get();
|
2010-01-23 19:44:49 +00:00
|
|
|
mCurrentMenuName = tName;
|
2018-10-18 12:52:37 +00:00
|
|
|
return mCurrentMenu;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
///
|
2015-08-16 11:18:41 +00:00
|
|
|
/// This attaches a menu to the menubar and ends the menu
|
|
|
|
///
|
2018-10-18 12:52:37 +00:00
|
|
|
void CommandManager::EndMainMenu()
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-02-21 01:23:54 +00:00
|
|
|
// Add the menu to the menubar after all menu items have been
|
2015-08-16 11:18:41 +00:00
|
|
|
// added to the menu to allow OSX to rearrange special menu
|
|
|
|
// items like Preferences, About, and Quit.
|
2016-10-02 13:46:48 +00:00
|
|
|
wxASSERT(uCurrentMenu);
|
2019-01-09 20:26:32 +00:00
|
|
|
CurrentMenuBar()->Append(
|
|
|
|
uCurrentMenu.release(), mCurrentMenuName.Translation());
|
2016-10-02 13:46:48 +00:00
|
|
|
mCurrentMenu = nullptr;
|
2010-01-23 19:44:49 +00:00
|
|
|
mCurrentMenuName = COMMAND;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
///
|
2016-02-13 15:43:16 +00:00
|
|
|
/// This starts a NEW submenu, and names it according to
|
2010-01-23 19:44:49 +00:00
|
|
|
/// the function's argument.
|
2019-01-09 20:26:32 +00:00
|
|
|
wxMenu* CommandManager::BeginSubMenu(const TranslatableString & tName)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2020-05-03 15:21:01 +00:00
|
|
|
mSubMenuList.emplace_back( tName );
|
2010-01-23 19:44:49 +00:00
|
|
|
mbSeparatorAllowed = false;
|
2020-05-03 15:21:01 +00:00
|
|
|
return mSubMenuList.back().menu.get();
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
///
|
|
|
|
/// This function is called after the final item of a SUBmenu is added.
|
|
|
|
/// Submenu items are added just like regular menu items; they just happen
|
|
|
|
/// after BeginSubMenu() is called but before EndSubMenu() is called.
|
|
|
|
void CommandManager::EndSubMenu()
|
|
|
|
{
|
|
|
|
//Save the submenu's information
|
2020-05-03 15:21:01 +00:00
|
|
|
SubMenuListEntry tmpSubMenu{ std::move( mSubMenuList.back() ) };
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2016-02-13 15:43:16 +00:00
|
|
|
//Pop off the NEW submenu so CurrentMenu returns the parent of the submenu
|
2016-02-19 00:09:10 +00:00
|
|
|
mSubMenuList.pop_back();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
//Add the submenu to the current menu
|
2019-01-09 20:26:32 +00:00
|
|
|
auto name = tmpSubMenu.name.Translation();
|
|
|
|
CurrentMenu()->Append(0, name, tmpSubMenu.menu.release(),
|
|
|
|
name /* help string */ );
|
2010-01-23 19:44:49 +00:00
|
|
|
mbSeparatorAllowed = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
///
|
|
|
|
/// This returns the 'Current' Submenu, which is the one at the
|
|
|
|
/// end of the mSubMenuList (or NULL, if it doesn't exist).
|
2015-08-08 05:01:24 +00:00
|
|
|
wxMenu * CommandManager::CurrentSubMenu() const
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-02-19 00:09:10 +00:00
|
|
|
if(mSubMenuList.empty())
|
2010-01-23 19:44:49 +00:00
|
|
|
return NULL;
|
|
|
|
|
2020-05-03 15:21:01 +00:00
|
|
|
return mSubMenuList.back().menu.get();
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
///
|
|
|
|
/// This returns the current menu that we're appending to - note that
|
|
|
|
/// it could be a submenu if BeginSubMenu was called and we haven't
|
|
|
|
/// reached EndSubMenu yet.
|
2015-08-08 05:01:24 +00:00
|
|
|
wxMenu * CommandManager::CurrentMenu() const
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
if(!mCurrentMenu)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
wxMenu * tmpCurrentSubMenu = CurrentSubMenu();
|
|
|
|
|
|
|
|
if(!tmpCurrentSubMenu)
|
|
|
|
{
|
2016-10-02 13:46:48 +00:00
|
|
|
return mCurrentMenu;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return tmpCurrentSubMenu;
|
|
|
|
}
|
|
|
|
|
2020-02-01 16:14:22 +00:00
|
|
|
void CommandManager::UpdateCheckmarks( AudacityProject &project )
|
|
|
|
{
|
|
|
|
for ( const auto &entry : mCommandList ) {
|
|
|
|
if ( entry->menu && entry->checkmarkFn && !entry->isOccult) {
|
|
|
|
entry->menu->Check( entry->id, entry->checkmarkFn( project ) );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-08 05:01:24 +00:00
|
|
|
|
|
|
|
|
2019-01-09 19:14:40 +00:00
|
|
|
void CommandManager::AddItem(AudacityProject &project,
|
|
|
|
const CommandID &name,
|
2019-01-06 00:16:13 +00:00
|
|
|
const TranslatableString &label_in,
|
2017-08-19 12:45:35 +00:00
|
|
|
CommandHandlerFinder finder,
|
|
|
|
CommandFunctorPointer callback,
|
2016-05-06 00:02:13 +00:00
|
|
|
CommandFlag flags,
|
2018-10-17 16:45:30 +00:00
|
|
|
const Options &options)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2018-10-20 15:17:03 +00:00
|
|
|
if (options.global) {
|
2021-02-03 11:40:21 +00:00
|
|
|
//wxASSERT( flags == AlwaysEnabledFlag );
|
2018-10-20 15:17:03 +00:00
|
|
|
AddGlobalCommand(
|
2019-01-06 00:16:13 +00:00
|
|
|
name, label_in, finder, callback, options );
|
2018-10-20 15:17:03 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-10-17 20:35:44 +00:00
|
|
|
wxASSERT( flags != NoFlagsSpecified );
|
|
|
|
|
2017-08-19 12:45:35 +00:00
|
|
|
CommandListEntry *entry =
|
2018-08-15 10:29:12 +00:00
|
|
|
NewIdentifier(name,
|
|
|
|
label_in,
|
2019-01-07 18:08:32 +00:00
|
|
|
CurrentMenu(), finder, callback,
|
|
|
|
{}, 0, 0,
|
|
|
|
options);
|
2019-06-09 03:48:42 +00:00
|
|
|
entry->useStrictFlags = options.useStrictFlags;
|
2015-08-08 05:01:24 +00:00
|
|
|
int ID = entry->id;
|
2019-12-15 19:36:22 +00:00
|
|
|
wxString label = FormatLabelWithDisabledAccel(entry);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2019-06-13 20:56:05 +00:00
|
|
|
SetCommandFlags(name, flags);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2015-08-08 05:01:24 +00:00
|
|
|
|
2019-01-09 19:14:40 +00:00
|
|
|
auto &checker = options.checker;
|
|
|
|
if (checker) {
|
Fix for bug #736
This MUST be tested by everyone on as many platforms as you
have access to...reason:
This removes a very old piece of code (May 10, 2003) in the
command manager that worked around a problem in GTK where
accelerators could not be changed again after the initial
menu setup.
While there is another way to fix this bug, remove this old
code is actually a better way AS LONG AS it doesn't break
anything else. So far, it's looking like it is no longer
needed.
But, I'll be trying as many combinations of changing shortcuts,
entering/exiting preferences, swapping shortcuts, etc, as
I can on Linux, Windows and OSX.
But, the more ppl trying to break keyboard shortcuts the
better.
In case it helps, here's the comments that Dominic had in the
code:
// This is a very weird hack. Under GTK, menu labels are totally
// linked to accelerators the first time you create a menu item
// with that label and can't be changed. This causes all sorts of
// problems. As a workaround, we create each menu item with a
// made-up name (just an ID number string) but with the accelerator
// we want, then immediately change the label to the correct string.
// -DMM
2014-12-07 01:06:37 +00:00
|
|
|
CurrentMenu()->AppendCheckItem(ID, label);
|
2019-01-09 19:14:40 +00:00
|
|
|
CurrentMenu()->Check(ID, checker( project ));
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
else {
|
Fix for bug #736
This MUST be tested by everyone on as many platforms as you
have access to...reason:
This removes a very old piece of code (May 10, 2003) in the
command manager that worked around a problem in GTK where
accelerators could not be changed again after the initial
menu setup.
While there is another way to fix this bug, remove this old
code is actually a better way AS LONG AS it doesn't break
anything else. So far, it's looking like it is no longer
needed.
But, I'll be trying as many combinations of changing shortcuts,
entering/exiting preferences, swapping shortcuts, etc, as
I can on Linux, Windows and OSX.
But, the more ppl trying to break keyboard shortcuts the
better.
In case it helps, here's the comments that Dominic had in the
code:
// This is a very weird hack. Under GTK, menu labels are totally
// linked to accelerators the first time you create a menu item
// with that label and can't be changed. This causes all sorts of
// problems. As a workaround, we create each menu item with a
// made-up name (just an ID number string) but with the accelerator
// we want, then immediately change the label to the correct string.
// -DMM
2014-12-07 01:06:37 +00:00
|
|
|
CurrentMenu()->Append(ID, label);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
mbSeparatorAllowed = true;
|
|
|
|
}
|
|
|
|
|
2019-01-09 19:14:40 +00:00
|
|
|
auto CommandManager::Options::MakeCheckFn(
|
2020-05-03 15:21:01 +00:00
|
|
|
const wxString key, bool defaultValue ) -> CheckFn
|
2019-01-09 19:14:40 +00:00
|
|
|
{
|
|
|
|
return [=](AudacityProject&){ return gPrefs->ReadBool( key, defaultValue ); };
|
|
|
|
}
|
|
|
|
|
2019-04-03 15:27:15 +00:00
|
|
|
auto CommandManager::Options::MakeCheckFn(
|
|
|
|
const BoolSetting &setting ) -> CheckFn
|
|
|
|
{
|
|
|
|
return MakeCheckFn( setting.GetPath(), setting.GetDefault() );
|
|
|
|
}
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
///
|
|
|
|
/// Add a list of menu items to the current menu. When the user selects any
|
|
|
|
/// one of these, the given functor will be called
|
|
|
|
/// with its position in the list as the index number.
|
|
|
|
/// When you call Enable on this command name, it will enable or disable
|
|
|
|
/// all of the items at once.
|
2019-03-11 02:05:37 +00:00
|
|
|
void CommandManager::AddItemList(const CommandID & name,
|
2018-11-02 15:31:44 +00:00
|
|
|
const ComponentInterfaceSymbol items[],
|
2018-03-15 23:39:25 +00:00
|
|
|
size_t nItems,
|
2017-08-19 12:45:35 +00:00
|
|
|
CommandHandlerFinder finder,
|
2018-03-01 14:05:57 +00:00
|
|
|
CommandFunctorPointer callback,
|
2018-10-17 21:02:27 +00:00
|
|
|
CommandFlag flags,
|
2018-03-01 14:05:57 +00:00
|
|
|
bool bIsEffect)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2018-03-15 23:39:25 +00:00
|
|
|
for (size_t i = 0, cnt = nItems; i < cnt; i++) {
|
2019-01-07 18:18:48 +00:00
|
|
|
CommandListEntry *entry =
|
|
|
|
NewIdentifier(name,
|
2019-01-06 00:16:13 +00:00
|
|
|
items[i].Msgid(),
|
2019-01-07 18:18:48 +00:00
|
|
|
CurrentMenu(),
|
|
|
|
finder,
|
|
|
|
callback,
|
|
|
|
items[i].Internal(),
|
|
|
|
i,
|
|
|
|
cnt,
|
2019-01-07 18:08:32 +00:00
|
|
|
Options{}
|
|
|
|
.IsEffect(bIsEffect));
|
2019-06-13 20:56:05 +00:00
|
|
|
entry->flags = flags;
|
2019-12-15 19:36:22 +00:00
|
|
|
CurrentMenu()->Append(entry->id, FormatLabelForMenu(entry));
|
2015-07-28 19:35:09 +00:00
|
|
|
mbSeparatorAllowed = true;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-11 02:05:37 +00:00
|
|
|
void CommandManager::AddGlobalCommand(const CommandID &name,
|
2019-01-06 00:16:13 +00:00
|
|
|
const TranslatableString &label_in,
|
2017-08-19 12:45:35 +00:00
|
|
|
CommandHandlerFinder finder,
|
|
|
|
CommandFunctorPointer callback,
|
2019-01-07 18:08:32 +00:00
|
|
|
const Options &options)
|
2014-11-26 18:17:38 +00:00
|
|
|
{
|
2017-08-19 12:45:35 +00:00
|
|
|
CommandListEntry *entry =
|
2019-01-06 00:16:13 +00:00
|
|
|
NewIdentifier(name, label_in, NULL, finder, callback,
|
2019-01-07 18:08:32 +00:00
|
|
|
{}, 0, 0, options);
|
2014-11-26 18:17:38 +00:00
|
|
|
|
|
|
|
entry->enabled = false;
|
2015-08-10 18:15:15 +00:00
|
|
|
entry->isGlobal = true;
|
2016-05-06 00:02:13 +00:00
|
|
|
entry->flags = AlwaysEnabledFlag;
|
2014-11-26 18:17:38 +00:00
|
|
|
}
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
void CommandManager::AddSeparator()
|
|
|
|
{
|
|
|
|
if( mbSeparatorAllowed )
|
|
|
|
CurrentMenu()->AppendSeparator();
|
|
|
|
mbSeparatorAllowed = false; // boolean to prevent too many separators.
|
|
|
|
}
|
|
|
|
|
|
|
|
int CommandManager::NextIdentifier(int ID)
|
|
|
|
{
|
|
|
|
ID++;
|
|
|
|
|
|
|
|
//Skip the reserved identifiers used by wxWidgets
|
|
|
|
if((ID >= wxID_LOWEST) && (ID <= wxID_HIGHEST))
|
|
|
|
ID = wxID_HIGHEST+1;
|
|
|
|
|
|
|
|
return ID;
|
|
|
|
}
|
|
|
|
|
2016-02-13 15:43:16 +00:00
|
|
|
///Given all of the information for a command, comes up with a NEW unique
|
2010-01-23 19:44:49 +00:00
|
|
|
///ID, adds it to a list, and returns the ID.
|
|
|
|
///WARNING: Does this conflict with the identifiers set for controls/windows?
|
|
|
|
///If it does, a workaround may be to keep controls below wxID_LOWEST
|
|
|
|
///and keep menus above wxID_HIGHEST
|
2019-03-11 02:05:37 +00:00
|
|
|
CommandListEntry *CommandManager::NewIdentifier(const CommandID & nameIn,
|
2019-12-15 18:51:21 +00:00
|
|
|
const TranslatableString & label,
|
2016-02-19 15:49:50 +00:00
|
|
|
wxMenu *menu,
|
2017-08-19 12:45:35 +00:00
|
|
|
CommandHandlerFinder finder,
|
|
|
|
CommandFunctorPointer callback,
|
2019-03-11 02:05:37 +00:00
|
|
|
const CommandID &nameSuffix,
|
2016-02-19 15:49:50 +00:00
|
|
|
int index,
|
2017-08-19 10:40:27 +00:00
|
|
|
int count,
|
2019-01-07 18:08:32 +00:00
|
|
|
const Options &options)
|
2015-08-08 05:01:24 +00:00
|
|
|
{
|
2019-01-06 00:16:13 +00:00
|
|
|
bool excludeFromMacros =
|
|
|
|
(options.allowInMacros == 0) ||
|
2019-12-15 18:51:21 +00:00
|
|
|
((options.allowInMacros == -1) && label.MSGID().GET().Contains("..."));
|
2019-01-07 18:08:32 +00:00
|
|
|
|
|
|
|
const wxString & accel = options.accel;
|
|
|
|
bool bIsEffect = options.bIsEffect;
|
2019-12-23 11:08:19 +00:00
|
|
|
CommandID parameter = options.parameter == "" ? nameIn : options.parameter;
|
2019-01-07 18:08:32 +00:00
|
|
|
|
2019-01-06 00:16:13 +00:00
|
|
|
// if empty, new identifier's long label will be same as label, below:
|
2019-12-15 18:51:21 +00:00
|
|
|
const auto &longLabel = options.longName;
|
2019-01-06 00:16:13 +00:00
|
|
|
|
2018-03-15 23:39:25 +00:00
|
|
|
const bool multi = !nameSuffix.empty();
|
2019-03-11 02:05:37 +00:00
|
|
|
auto name = nameIn;
|
2018-02-17 16:08:38 +00:00
|
|
|
|
2017-03-19 19:28:14 +00:00
|
|
|
// If we have the identifier already, reuse it.
|
|
|
|
CommandListEntry *prev = mCommandNameHash[name];
|
|
|
|
if (!prev);
|
|
|
|
else if( prev->label != label );
|
|
|
|
else if( multi );
|
|
|
|
else
|
|
|
|
return prev;
|
|
|
|
|
2016-02-19 15:49:50 +00:00
|
|
|
{
|
2018-04-16 17:31:17 +00:00
|
|
|
auto entry = std::make_unique<CommandListEntry>();
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2019-01-09 20:26:32 +00:00
|
|
|
TranslatableString labelPrefix;
|
2020-05-03 15:21:01 +00:00
|
|
|
if (!mSubMenuList.empty())
|
|
|
|
labelPrefix = mSubMenuList.back().name.Stripped();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2018-02-17 16:08:38 +00:00
|
|
|
// For key bindings for commands with a list, such as align,
|
|
|
|
// the name in prefs is the category name plus the effect name.
|
|
|
|
// This feature is not used for built-in effects.
|
2019-03-12 23:01:46 +00:00
|
|
|
if (multi)
|
2019-02-27 18:14:25 +00:00
|
|
|
name = CommandID{ { name, nameSuffix }, wxT('_') };
|
2018-02-17 16:08:38 +00:00
|
|
|
|
2016-02-19 15:49:50 +00:00
|
|
|
// wxMac 2.5 and higher will do special things with the
|
|
|
|
// Preferences, Exit (Quit), and About menu items,
|
|
|
|
// if we give them the right IDs.
|
|
|
|
// Otherwise we just pick increasing ID numbers for each NEW
|
|
|
|
// command. Note that the name string we are comparing
|
|
|
|
// ("About", "Preferences") is the internal command name
|
|
|
|
// (untranslated), not the label that actually appears in the
|
|
|
|
// menu (which might be translated).
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2016-02-19 15:49:50 +00:00
|
|
|
mCurrentID = NextIdentifier(mCurrentID);
|
|
|
|
entry->id = mCurrentID;
|
2017-08-19 10:40:27 +00:00
|
|
|
entry->parameter = parameter;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
#if defined(__WXMAC__)
|
2021-02-03 11:40:21 +00:00
|
|
|
// See bug #2642 for some history as to why these items
|
|
|
|
// on Mac have their IDs set explicitly and not others.
|
2016-02-19 15:49:50 +00:00
|
|
|
if (name == wxT("Preferences"))
|
|
|
|
entry->id = wxID_PREFERENCES;
|
|
|
|
else if (name == wxT("Exit"))
|
|
|
|
entry->id = wxID_EXIT;
|
|
|
|
else if (name == wxT("About"))
|
|
|
|
entry->id = wxID_ABOUT;
|
2010-01-23 19:44:49 +00:00
|
|
|
#endif
|
|
|
|
|
2016-02-19 15:49:50 +00:00
|
|
|
entry->name = name;
|
|
|
|
entry->label = label;
|
2019-01-07 18:08:32 +00:00
|
|
|
|
|
|
|
// long label is the same as label unless options specified otherwise:
|
2019-02-12 00:10:48 +00:00
|
|
|
entry->longLabel = longLabel.empty() ? label : longLabel;
|
2019-01-07 18:08:32 +00:00
|
|
|
|
2019-12-12 18:37:33 +00:00
|
|
|
entry->excludeFromMacros = excludeFromMacros;
|
2018-02-09 02:16:13 +00:00
|
|
|
entry->key = NormalizedKeyString{ accel.BeforeFirst(wxT('\t')) };
|
2016-02-19 15:49:50 +00:00
|
|
|
entry->defaultKey = entry->key;
|
|
|
|
entry->labelPrefix = labelPrefix;
|
2020-05-05 20:16:56 +00:00
|
|
|
entry->labelTop = mCurrentMenuName.Stripped();
|
2016-02-19 15:49:50 +00:00
|
|
|
entry->menu = menu;
|
2017-08-19 12:45:35 +00:00
|
|
|
entry->finder = finder;
|
2016-02-19 15:49:50 +00:00
|
|
|
entry->callback = callback;
|
2018-03-01 14:05:57 +00:00
|
|
|
entry->isEffect = bIsEffect;
|
2016-02-19 15:49:50 +00:00
|
|
|
entry->multi = multi;
|
|
|
|
entry->index = index;
|
|
|
|
entry->count = count;
|
2019-06-13 20:56:05 +00:00
|
|
|
entry->flags = AlwaysEnabledFlag;
|
2016-02-19 15:49:50 +00:00
|
|
|
entry->enabled = true;
|
2020-05-05 04:02:21 +00:00
|
|
|
entry->skipKeydown = options.skipKeyDown;
|
|
|
|
entry->wantKeyup = options.wantKeyUp || entry->skipKeydown;
|
|
|
|
entry->allowDup = options.allowDup;
|
2016-02-19 15:49:50 +00:00
|
|
|
entry->isGlobal = false;
|
2017-04-21 14:47:17 +00:00
|
|
|
entry->isOccult = bMakingOccultCommands;
|
2020-02-01 16:14:22 +00:00
|
|
|
entry->checkmarkFn = options.checker;
|
2016-02-19 15:49:50 +00:00
|
|
|
|
2017-06-28 22:00:02 +00:00
|
|
|
// Exclude accelerators that are in the MaxList.
|
|
|
|
// Note that the default is unaffected, intentionally so.
|
|
|
|
// There are effectively two levels of default, the full (max) list
|
|
|
|
// and the normal reduced list.
|
2018-02-09 02:16:13 +00:00
|
|
|
if( std::binary_search( mMaxListOnly.begin(), mMaxListOnly.end(),
|
|
|
|
entry->key ) )
|
|
|
|
entry->key = {};
|
2017-06-28 22:00:02 +00:00
|
|
|
|
2018-02-09 02:16:13 +00:00
|
|
|
// Key from preferences overrides the default key given
|
2016-02-19 15:49:50 +00:00
|
|
|
gPrefs->SetPath(wxT("/NewKeys"));
|
2019-02-27 18:14:25 +00:00
|
|
|
// using GET to interpret CommandID as a config path component
|
|
|
|
const auto &path = entry->name.GET();
|
|
|
|
if (gPrefs->HasEntry(path)) {
|
2018-02-09 02:16:13 +00:00
|
|
|
entry->key =
|
2019-02-27 18:14:25 +00:00
|
|
|
NormalizedKeyString{ gPrefs->ReadObject(path, entry->key) };
|
2016-02-19 15:49:50 +00:00
|
|
|
}
|
|
|
|
gPrefs->SetPath(wxT("/"));
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2016-02-19 15:49:50 +00:00
|
|
|
mCommandList.push_back(std::move(entry));
|
|
|
|
// Don't use the variable entry eny more!
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2016-02-19 15:49:50 +00:00
|
|
|
// New variable
|
|
|
|
CommandListEntry *entry = &*mCommandList.back();
|
2019-02-27 17:57:21 +00:00
|
|
|
mCommandNumericIDHash[entry->id] = entry;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2020-04-25 07:36:27 +00:00
|
|
|
#if defined(_DEBUG)
|
2017-03-19 19:28:14 +00:00
|
|
|
prev = mCommandNameHash[entry->name];
|
2013-08-25 22:21:32 +00:00
|
|
|
if (prev) {
|
|
|
|
// Under Linux it looks as if we may ask for a newID for the same command
|
|
|
|
// more than once. So it's only an error if two different commands
|
|
|
|
// have the exact same name.
|
2015-08-08 05:01:24 +00:00
|
|
|
if( prev->label != entry->label )
|
2013-08-25 22:21:32 +00:00
|
|
|
{
|
|
|
|
wxLogDebug(wxT("Command '%s' defined by '%s' and '%s'"),
|
2019-02-27 18:14:25 +00:00
|
|
|
// using GET in a log message for devs' eyes only
|
|
|
|
entry->name.GET(),
|
2019-12-15 18:51:21 +00:00
|
|
|
prev->label.Debug(),
|
|
|
|
entry->label.Debug());
|
2015-08-08 05:01:24 +00:00
|
|
|
wxFAIL_MSG(wxString::Format(wxT("Command '%s' defined by '%s' and '%s'"),
|
2019-02-27 18:14:25 +00:00
|
|
|
// using GET in an assertion violation message for devs'
|
|
|
|
// eyes only
|
|
|
|
entry->name.GET(),
|
2019-12-15 18:51:21 +00:00
|
|
|
prev->label.Debug(),
|
|
|
|
entry->label.Debug()));
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
2013-08-25 22:21:32 +00:00
|
|
|
#endif
|
2016-01-27 21:10:46 +00:00
|
|
|
mCommandNameHash[entry->name] = entry;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2018-02-09 02:16:13 +00:00
|
|
|
if (!entry->key.empty()) {
|
2015-08-08 05:01:24 +00:00
|
|
|
mCommandKeyHash[entry->key] = entry;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2015-08-08 05:01:24 +00:00
|
|
|
return entry;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2019-12-15 19:36:22 +00:00
|
|
|
wxString CommandManager::FormatLabelForMenu(const CommandListEntry *entry) const
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2019-12-15 18:51:21 +00:00
|
|
|
auto label = entry->label.Translation();
|
2018-02-09 02:16:13 +00:00
|
|
|
if (!entry->key.empty())
|
2015-08-08 05:01:24 +00:00
|
|
|
{
|
2019-02-28 12:37:10 +00:00
|
|
|
// using GET to compose menu item name for wxWidgets
|
|
|
|
label += wxT("\t") + entry->key.GET();
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2015-08-08 05:01:24 +00:00
|
|
|
return label;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2017-05-01 18:00:59 +00:00
|
|
|
// A label that may have its accelerator disabled.
|
|
|
|
// The problem is that as soon as we show accelerators in the menu, the menu might
|
|
|
|
// catch them in normal wxWidgets processing, rather than passing the key presses on
|
|
|
|
// to the controls that had the focus. We would like all the menu accelerators to be
|
2018-08-15 10:29:12 +00:00
|
|
|
// disabled, in fact.
|
2019-12-15 19:36:22 +00:00
|
|
|
wxString CommandManager::FormatLabelWithDisabledAccel(const CommandListEntry *entry) const
|
2017-05-01 18:00:59 +00:00
|
|
|
{
|
2019-12-15 18:51:21 +00:00
|
|
|
auto label = entry->label.Translation();
|
2017-05-01 18:00:59 +00:00
|
|
|
#if 1
|
2019-03-15 18:41:21 +00:00
|
|
|
wxString Accel;
|
2017-05-01 18:00:59 +00:00
|
|
|
do{
|
2018-02-09 02:16:13 +00:00
|
|
|
if (!entry->key.empty())
|
2017-05-01 18:00:59 +00:00
|
|
|
{
|
2017-05-07 09:59:50 +00:00
|
|
|
// Dummy accelerator that looks Ok in menus but is non functional.
|
|
|
|
// Note the space before the key.
|
2017-05-09 07:22:36 +00:00
|
|
|
#ifdef __WXMSW__
|
2019-02-28 12:37:10 +00:00
|
|
|
// using GET to compose menu item name for wxWidgets
|
|
|
|
auto key = entry->key.GET();
|
2018-02-26 13:48:00 +00:00
|
|
|
Accel = wxString("\t ") + key;
|
|
|
|
if( key.StartsWith("Left" )) break;
|
|
|
|
if( key.StartsWith("Right")) break;
|
|
|
|
if( key.StartsWith("Up" )) break;
|
|
|
|
if( key.StartsWith("Down")) break;
|
|
|
|
if( key.StartsWith("Return")) break;
|
|
|
|
if( key.StartsWith("Tab")) break;
|
|
|
|
if( key.StartsWith("Shift+Tab")) break;
|
|
|
|
if( key.StartsWith("0")) break;
|
|
|
|
if( key.StartsWith("1")) break;
|
|
|
|
if( key.StartsWith("2")) break;
|
|
|
|
if( key.StartsWith("3")) break;
|
|
|
|
if( key.StartsWith("4")) break;
|
|
|
|
if( key.StartsWith("5")) break;
|
|
|
|
if( key.StartsWith("6")) break;
|
|
|
|
if( key.StartsWith("7")) break;
|
|
|
|
if( key.StartsWith("8")) break;
|
|
|
|
if( key.StartsWith("9")) break;
|
2017-05-07 10:17:18 +00:00
|
|
|
// Uncomment the below so as not to add the illegal accelerators.
|
|
|
|
// Accel = "";
|
2017-05-01 18:00:59 +00:00
|
|
|
//if( entry->key.StartsWith("Space" )) break;
|
2017-05-07 10:17:18 +00:00
|
|
|
// These ones appear to be illegal already and mess up accelerator processing.
|
2018-02-26 13:48:00 +00:00
|
|
|
if( key.StartsWith("NUMPAD_ENTER" )) break;
|
|
|
|
if( key.StartsWith("Backspace" )) break;
|
|
|
|
if( key.StartsWith("Delete" )) break;
|
2017-05-09 07:22:36 +00:00
|
|
|
#endif
|
2017-05-01 18:00:59 +00:00
|
|
|
//wxLogDebug("Added Accel:[%s][%s]", entry->label, entry->key );
|
2017-05-07 09:59:50 +00:00
|
|
|
// Normal accelerator.
|
2019-02-28 12:37:10 +00:00
|
|
|
// using GET to compose menu item name for wxWidgets
|
|
|
|
Accel = wxString("\t") + entry->key.GET();
|
2017-05-01 18:00:59 +00:00
|
|
|
}
|
|
|
|
} while (false );
|
2017-05-07 09:59:50 +00:00
|
|
|
label += Accel;
|
2017-05-01 18:00:59 +00:00
|
|
|
#endif
|
|
|
|
return label;
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
///Enables or disables a menu item based on its name (not the
|
|
|
|
///label in the menu bar, but the name of the command.)
|
|
|
|
///If you give it the name of a multi-item (one that was
|
|
|
|
///added using AddItemList(), it will enable or disable all
|
|
|
|
///of them at once
|
|
|
|
void CommandManager::Enable(CommandListEntry *entry, bool enabled)
|
|
|
|
{
|
|
|
|
if (!entry->menu) {
|
|
|
|
entry->enabled = enabled;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// LL: Refresh from real state as we can get out of sync on the
|
|
|
|
// Mac due to its reluctance to enable menus when in a modal
|
|
|
|
// state.
|
|
|
|
entry->enabled = entry->menu->IsEnabled(entry->id);
|
|
|
|
|
|
|
|
// Only enabled if needed
|
|
|
|
if (entry->enabled != enabled) {
|
|
|
|
entry->menu->Enable(entry->id, enabled);
|
|
|
|
entry->enabled = entry->menu->IsEnabled(entry->id);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (entry->multi) {
|
|
|
|
int i;
|
|
|
|
int ID = entry->id;
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
for(i=1; i<entry->count; i++) {
|
|
|
|
ID = NextIdentifier(ID);
|
|
|
|
|
|
|
|
// This menu item is not necessarily in the same menu, because
|
|
|
|
// multi-items can be spread across multiple sub menus
|
2019-02-27 17:57:21 +00:00
|
|
|
CommandListEntry *multiEntry = mCommandNumericIDHash[ID];
|
2010-01-23 19:44:49 +00:00
|
|
|
if (multiEntry) {
|
|
|
|
wxMenuItem *item = multiEntry->menu->FindItem(ID);
|
|
|
|
|
|
|
|
if (item) {
|
|
|
|
item->Enable(enabled);
|
|
|
|
} else {
|
2019-02-27 18:14:25 +00:00
|
|
|
// using GET in a log message for devs' eyes only
|
2010-01-23 19:44:49 +00:00
|
|
|
wxLogDebug(wxT("Warning: Menu entry with id %i in %s not found"),
|
2019-02-27 18:14:25 +00:00
|
|
|
ID, entry->name.GET());
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
wxLogDebug(wxT("Warning: Menu entry with id %i not in hash"), ID);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-23 02:17:41 +00:00
|
|
|
void CommandManager::Enable(const wxString &name, bool enabled)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
CommandListEntry *entry = mCommandNameHash[name];
|
|
|
|
if (!entry || !entry->menu) {
|
|
|
|
wxLogDebug(wxT("Warning: Unknown command enabled: '%s'"),
|
|
|
|
(const wxChar*)name);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
Enable(entry, enabled);
|
|
|
|
}
|
|
|
|
|
2019-06-09 03:48:42 +00:00
|
|
|
void CommandManager::EnableUsingFlags(
|
2019-06-13 20:55:01 +00:00
|
|
|
CommandFlag flags, CommandFlag strictFlags)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2019-06-09 03:48:42 +00:00
|
|
|
// strictFlags are a subset of flags. strictFlags represent the real
|
|
|
|
// conditions now, but flags are the conditions that could be made true.
|
|
|
|
// Some commands use strict flags only, refusing the chance to fix
|
|
|
|
// conditions
|
|
|
|
wxASSERT( (strictFlags & ~flags).none() );
|
|
|
|
|
2016-02-19 15:49:50 +00:00
|
|
|
for(const auto &entry : mCommandList) {
|
2010-01-23 19:44:49 +00:00
|
|
|
if (entry->multi && entry->index != 0)
|
|
|
|
continue;
|
2017-04-21 14:47:17 +00:00
|
|
|
if( entry->isOccult )
|
|
|
|
continue;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2019-06-09 03:48:42 +00:00
|
|
|
auto useFlags = entry->useStrictFlags ? strictFlags : flags;
|
|
|
|
|
2019-06-13 20:56:05 +00:00
|
|
|
if (entry->flags.any()) {
|
|
|
|
bool enable = ((useFlags & entry->flags) == entry->flags);
|
2016-02-19 15:49:50 +00:00
|
|
|
Enable(entry.get(), enable);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-11 02:05:37 +00:00
|
|
|
bool CommandManager::GetEnabled(const CommandID &name)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
CommandListEntry *entry = mCommandNameHash[name];
|
|
|
|
if (!entry || !entry->menu) {
|
2019-02-27 18:14:25 +00:00
|
|
|
// using GET in a log message for devs' eyes only
|
2010-01-23 19:44:49 +00:00
|
|
|
wxLogDebug(wxT("Warning: command doesn't exist: '%s'"),
|
2019-02-27 18:14:25 +00:00
|
|
|
name.GET());
|
2010-01-23 19:44:49 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return entry->enabled;
|
|
|
|
}
|
|
|
|
|
Bug 624: Keyboard Prefs: importing XML file can cause duplicated bindings
The fix follows the agreed behavior (see emails from around October 25) . For the sake of convenience see the agreed behavior below:
_"- first, check the xml-file. If it contains illegal shortcut duplicates, refuse importing. Shortcut duplicates are LEGAL if default settings also have those operations with the matching shortcuts. A refusal to import shortcuts must happen with the message that warns the user of a failure and explains the reason.
if the xml-file looks ok, import the shortcuts. As discussed before, because different versions of Audacity might have different sets of operations with shortcuts, it is still possible to end up with illegal shortcut duplicates for a perfectly correct xml-file. This situation must be monitored. In case of any conflicts, the shortcut from the xml-file is used, and the pre-existing shortcut is wiped clean.
When telling the user the commands which have had their shortcut removed, I think it would be useful to tell the user the name of the command, the shortcut, and and name of the command which still has that shortcut."_
I didn't find a clean way to intercept the imported content before it makes its way to the shortcut preferences, so I had to jump through some hoops right in KeyConfigPrefs::OnImport().
In general, I tried to keep changes minimal.
2020-01-09 19:47:50 +00:00
|
|
|
int CommandManager::GetNumberOfKeysRead() const
|
|
|
|
{
|
|
|
|
return mXMLKeysRead;
|
|
|
|
}
|
|
|
|
|
2019-03-11 02:05:37 +00:00
|
|
|
void CommandManager::Check(const CommandID &name, bool checked)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
CommandListEntry *entry = mCommandNameHash[name];
|
2017-04-21 16:39:35 +00:00
|
|
|
if (!entry || !entry->menu || entry->isOccult) {
|
2010-01-23 19:44:49 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
entry->menu->Check(entry->id, checked);
|
|
|
|
}
|
|
|
|
|
|
|
|
///Changes the label text of a menu item
|
2019-12-15 18:51:21 +00:00
|
|
|
void CommandManager::Modify(const wxString &name, const TranslatableString &newLabel)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
CommandListEntry *entry = mCommandNameHash[name];
|
|
|
|
if (entry && entry->menu) {
|
2015-08-10 04:49:57 +00:00
|
|
|
entry->label = newLabel;
|
2019-12-15 19:36:22 +00:00
|
|
|
entry->menu->SetLabel(entry->id, FormatLabelForMenu(entry));
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-11 02:05:37 +00:00
|
|
|
void CommandManager::SetKeyFromName(const CommandID &name,
|
2018-02-09 02:16:13 +00:00
|
|
|
const NormalizedKeyString &key)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
CommandListEntry *entry = mCommandNameHash[name];
|
|
|
|
if (entry) {
|
2018-02-09 02:16:13 +00:00
|
|
|
entry->key = key;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-09 02:16:13 +00:00
|
|
|
void CommandManager::SetKeyFromIndex(int i, const NormalizedKeyString &key)
|
2012-03-29 20:31:49 +00:00
|
|
|
{
|
2016-02-19 15:49:50 +00:00
|
|
|
const auto &entry = mCommandList[i];
|
2018-02-09 02:16:13 +00:00
|
|
|
entry->key = key;
|
2012-03-29 20:31:49 +00:00
|
|
|
}
|
|
|
|
|
2019-12-15 15:58:19 +00:00
|
|
|
TranslatableString CommandManager::DescribeCommandsAndShortcuts(
|
|
|
|
const ComponentInterfaceSymbol commands[], size_t nCommands) const
|
2017-07-15 15:59:55 +00:00
|
|
|
{
|
2017-09-12 01:11:42 +00:00
|
|
|
wxString mark;
|
|
|
|
// This depends on the language setting and may change in-session after
|
|
|
|
// change of preferences:
|
|
|
|
bool rtl = (wxLayout_RightToLeft == wxTheApp->GetLayoutDirection());
|
|
|
|
if (rtl)
|
|
|
|
mark = wxT("\u200f");
|
|
|
|
|
|
|
|
static const wxString &separatorFormat = wxT("%s / %s");
|
2019-12-15 15:58:19 +00:00
|
|
|
TranslatableString result;
|
2018-03-15 23:26:20 +00:00
|
|
|
for (size_t ii = 0; ii < nCommands; ++ii) {
|
|
|
|
const auto &pair = commands[ii];
|
2017-09-12 01:11:42 +00:00
|
|
|
// If RTL, then the control character forces right-to-left sequencing of
|
|
|
|
// "/" -separated command names, and puts any "(...)" shortcuts to the
|
|
|
|
// left, consistently with accelerators in menus (assuming matching
|
2020-04-11 07:08:33 +00:00
|
|
|
// operating system preferences for language), even if the command name
|
2017-09-12 01:11:42 +00:00
|
|
|
// was missing from the translation file and defaulted to the English.
|
2019-12-15 15:58:19 +00:00
|
|
|
|
|
|
|
// Note: not putting this and other short format strings in the
|
|
|
|
// translation catalogs
|
2019-12-19 21:20:41 +00:00
|
|
|
auto piece = Verbatim( wxT("%s%s") )
|
2019-12-19 15:23:18 +00:00
|
|
|
.Format( mark, pair.Msgid().Stripped() );
|
2018-01-08 18:57:51 +00:00
|
|
|
|
2019-03-11 02:05:37 +00:00
|
|
|
auto name = pair.Internal();
|
2018-01-08 18:57:51 +00:00
|
|
|
if (!name.empty()) {
|
|
|
|
auto keyStr = GetKeyFromName(name);
|
|
|
|
if (!keyStr.empty()){
|
2018-02-09 02:16:13 +00:00
|
|
|
auto keyString = keyStr.Display(true);
|
2018-01-08 18:57:51 +00:00
|
|
|
auto format = wxT("%s %s(%s)");
|
2017-09-12 01:11:42 +00:00
|
|
|
#ifdef __WXMAC__
|
2018-01-08 18:57:51 +00:00
|
|
|
// The unicode controls push and pop left-to-right embedding.
|
|
|
|
// This keeps the directionally weak characters, such as uparrow
|
|
|
|
// for Shift, left of the key name,
|
|
|
|
// consistently with how menu accelerators appear, even when the
|
|
|
|
// system language is RTL.
|
|
|
|
format = wxT("%s %s(\u202a%s\u202c)");
|
2017-09-12 01:11:42 +00:00
|
|
|
#endif
|
2018-01-08 18:57:51 +00:00
|
|
|
// The mark makes correctly placed parentheses for RTL, even
|
|
|
|
// in the case that the piece is untranslated.
|
2019-12-19 21:20:41 +00:00
|
|
|
piece = Verbatim( format ).Format( piece, mark, keyString );
|
2017-07-15 15:59:55 +00:00
|
|
|
}
|
|
|
|
}
|
2018-01-08 18:57:51 +00:00
|
|
|
|
2017-09-12 01:11:42 +00:00
|
|
|
if (result.empty())
|
|
|
|
result = piece;
|
|
|
|
else
|
2019-12-19 21:20:41 +00:00
|
|
|
result = Verbatim( separatorFormat ).Format( result, piece );
|
2017-07-15 15:59:55 +00:00
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2015-08-08 05:01:24 +00:00
|
|
|
///
|
|
|
|
///
|
|
|
|
///
|
2015-08-10 18:15:15 +00:00
|
|
|
bool CommandManager::FilterKeyEvent(AudacityProject *project, const wxKeyEvent & evt, bool permit)
|
2015-08-08 05:01:24 +00:00
|
|
|
{
|
2020-01-04 01:43:19 +00:00
|
|
|
if (!project)
|
|
|
|
return false;
|
|
|
|
|
2019-05-28 17:12:56 +00:00
|
|
|
auto pWindow = FindProjectFrame( project );
|
2015-08-10 18:15:15 +00:00
|
|
|
CommandListEntry *entry = mCommandKeyHash[KeyEventToKeyString(evt)];
|
|
|
|
if (entry == NULL)
|
2015-08-08 05:01:24 +00:00
|
|
|
{
|
2015-08-10 18:15:15 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-08-14 04:24:58 +00:00
|
|
|
int type = evt.GetEventType();
|
|
|
|
|
2015-08-12 23:05:50 +00:00
|
|
|
// Global commands aren't tied to any specific project
|
2015-08-14 04:24:58 +00:00
|
|
|
if (entry->isGlobal && type == wxEVT_KEY_DOWN)
|
2015-08-10 18:15:15 +00:00
|
|
|
{
|
|
|
|
// Global commands are always disabled so they do not interfere with the
|
|
|
|
// rest of the command handling. But, to use the common handler, we
|
2015-08-12 23:05:50 +00:00
|
|
|
// enable them temporarily and then disable them again after handling.
|
|
|
|
// LL: Why do they need to be disabled???
|
2015-08-10 18:15:15 +00:00
|
|
|
entry->enabled = false;
|
2016-11-24 22:17:37 +00:00
|
|
|
auto cleanup = valueRestorer( entry->enabled, true );
|
2020-01-04 01:43:19 +00:00
|
|
|
return HandleCommandEntry(*project, entry, NoFlagsSpecified, false, &evt);
|
2015-08-08 05:01:24 +00:00
|
|
|
}
|
|
|
|
|
2018-04-11 14:11:06 +00:00
|
|
|
wxWindow * pFocus = wxWindow::FindFocus();
|
|
|
|
wxWindow * pParent = wxGetTopLevelParent( pFocus );
|
2019-05-28 17:12:56 +00:00
|
|
|
bool validTarget = pParent == pWindow;
|
2018-04-11 14:11:06 +00:00
|
|
|
// Bug 1557. MixerBoard should count as 'destined for project'
|
|
|
|
// MixerBoard IS a TopLevelWindow, and its parent is the project.
|
2019-05-28 17:12:56 +00:00
|
|
|
if( pParent && pParent->GetParent() == pWindow ){
|
2018-12-28 16:49:35 +00:00
|
|
|
if( dynamic_cast< TopLevelKeystrokeHandlingWindow* >( pParent ) != NULL )
|
2018-04-11 14:11:06 +00:00
|
|
|
validTarget = true;
|
|
|
|
}
|
|
|
|
validTarget = validTarget && wxEventLoop::GetActive()->IsMain();
|
|
|
|
|
|
|
|
// Any other keypresses must be destined for this project window
|
|
|
|
if (!permit && !validTarget )
|
2015-08-08 05:01:24 +00:00
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-06-08 15:11:51 +00:00
|
|
|
auto flags = MenuManager::Get(*project).GetUpdateFlags();
|
2015-08-08 05:01:24 +00:00
|
|
|
|
|
|
|
wxKeyEvent temp = evt;
|
2015-08-12 23:05:50 +00:00
|
|
|
|
2017-05-01 18:00:59 +00:00
|
|
|
// Possibly let wxWidgets do its normal key handling IF it is one of
|
|
|
|
// the standard navigation keys.
|
|
|
|
if((type == wxEVT_KEY_DOWN) || (type == wxEVT_KEY_UP ))
|
|
|
|
{
|
|
|
|
wxWindow * pWnd = wxWindow::FindFocus();
|
2018-12-28 16:49:35 +00:00
|
|
|
bool bIntercept =
|
|
|
|
pWnd && !dynamic_cast< NonKeystrokeInterceptingWindow * >( pWnd );
|
|
|
|
|
2017-05-01 18:00:59 +00:00
|
|
|
//wxLogDebug("Focus: %p TrackPanel: %p", pWnd, pTrackPanel );
|
2018-08-15 10:29:12 +00:00
|
|
|
// We allow the keystrokes below to be handled by wxWidgets controls IF we are
|
2017-05-01 18:00:59 +00:00
|
|
|
// in some sub window rather than in the TrackPanel itself.
|
2018-08-15 10:29:12 +00:00
|
|
|
// Otherwise they will go to our command handler and if it handles them
|
2017-05-01 18:00:59 +00:00
|
|
|
// they will NOT be available to wxWidgets.
|
2017-05-14 21:14:13 +00:00
|
|
|
if( bIntercept ){
|
2017-05-01 18:00:59 +00:00
|
|
|
switch( evt.GetKeyCode() ){
|
|
|
|
case WXK_LEFT:
|
|
|
|
case WXK_RIGHT:
|
|
|
|
case WXK_UP:
|
|
|
|
case WXK_DOWN:
|
2017-08-28 21:50:19 +00:00
|
|
|
// Don't trap WXK_SPACE (Bug 1727 - SPACE not starting/stopping playback
|
|
|
|
// when cursor is in a time control)
|
|
|
|
// case WXK_SPACE:
|
2017-05-01 18:00:59 +00:00
|
|
|
case WXK_TAB:
|
|
|
|
case WXK_BACK:
|
|
|
|
case WXK_HOME:
|
|
|
|
case WXK_END:
|
|
|
|
case WXK_RETURN:
|
|
|
|
case WXK_NUMPAD_ENTER:
|
|
|
|
case WXK_DELETE:
|
2017-05-07 09:59:50 +00:00
|
|
|
case '0':
|
|
|
|
case '1':
|
|
|
|
case '2':
|
|
|
|
case '3':
|
|
|
|
case '4':
|
|
|
|
case '5':
|
|
|
|
case '6':
|
|
|
|
case '7':
|
|
|
|
case '8':
|
|
|
|
case '9':
|
2017-05-01 18:00:59 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-14 04:24:58 +00:00
|
|
|
if (type == wxEVT_KEY_DOWN)
|
2015-08-08 05:01:24 +00:00
|
|
|
{
|
2015-08-12 23:05:50 +00:00
|
|
|
if (entry->skipKeydown)
|
2015-08-10 18:15:15 +00:00
|
|
|
{
|
2015-08-12 23:05:50 +00:00
|
|
|
return true;
|
2015-08-10 18:15:15 +00:00
|
|
|
}
|
2020-01-04 01:43:19 +00:00
|
|
|
return HandleCommandEntry(*project, entry, flags, false, &temp);
|
2015-08-12 23:05:50 +00:00
|
|
|
}
|
|
|
|
|
2015-08-14 04:24:58 +00:00
|
|
|
if (type == wxEVT_KEY_UP && entry->wantKeyup)
|
2015-08-12 23:05:50 +00:00
|
|
|
{
|
2020-01-04 01:43:19 +00:00
|
|
|
return HandleCommandEntry(*project, entry, flags, false, &temp);
|
2015-08-08 05:01:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-02-03 11:02:49 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
/// HandleCommandEntry() takes a CommandListEntry and executes it
|
|
|
|
/// returning true iff successful. If you pass any flags,
|
|
|
|
///the command won't be executed unless the flags are compatible
|
|
|
|
///with the command's flags.
|
2020-01-04 01:43:19 +00:00
|
|
|
bool CommandManager::HandleCommandEntry(AudacityProject &project,
|
|
|
|
const CommandListEntry * entry,
|
2019-06-09 06:02:36 +00:00
|
|
|
CommandFlag flags, bool alwaysEnabled, const wxEvent * evt)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2018-08-13 15:33:40 +00:00
|
|
|
if (!entry )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (flags != AlwaysEnabledFlag && !entry->enabled)
|
2010-01-23 19:44:49 +00:00
|
|
|
return false;
|
|
|
|
|
2019-06-09 06:02:36 +00:00
|
|
|
if (!alwaysEnabled && entry->flags.any()) {
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2019-12-19 15:23:18 +00:00
|
|
|
const auto NiceName = entry->label.Stripped(
|
2019-12-15 18:51:21 +00:00
|
|
|
TranslatableString::Ellipses | TranslatableString::MenuCodes );
|
2010-01-23 19:44:49 +00:00
|
|
|
// NB: The call may have the side effect of changing flags.
|
2017-08-20 04:16:22 +00:00
|
|
|
bool allowed =
|
2020-01-04 01:43:19 +00:00
|
|
|
MenuManager::Get(project).ReportIfActionNotAllowed(
|
2019-06-09 06:05:39 +00:00
|
|
|
NiceName, flags, entry->flags );
|
2017-06-09 21:36:05 +00:00
|
|
|
// If the function was disallowed, it STILL should count as having been
|
|
|
|
// handled (by doing nothing or by telling the user of the problem).
|
|
|
|
// Otherwise we may get other handlers having a go at obeying the command.
|
2010-01-23 19:44:49 +00:00
|
|
|
if (!allowed)
|
2017-06-09 21:36:05 +00:00
|
|
|
return true;
|
2021-02-03 11:02:49 +00:00
|
|
|
mNiceName = NiceName;
|
|
|
|
}
|
|
|
|
else {
|
2021-05-22 00:41:27 +00:00
|
|
|
mNiceName = {};
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2020-01-04 01:43:19 +00:00
|
|
|
const CommandContext context{ project, evt, entry->index, entry->parameter };
|
|
|
|
auto &handler = entry->finder(project);
|
2017-08-19 14:15:32 +00:00
|
|
|
(handler.*(entry->callback))(context);
|
2021-02-03 11:02:49 +00:00
|
|
|
mLastProcessId = 0;
|
2010-01-23 19:44:49 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-02-03 11:02:49 +00:00
|
|
|
// Called by Contrast and Plot Spectrum Plug-ins to mark them as Last Analzers.
|
|
|
|
// Note that Repeat data has previously been collected
|
|
|
|
void CommandManager::RegisterLastAnalyzer(const CommandContext& context) {
|
|
|
|
if (mLastProcessId != 0) {
|
|
|
|
auto& menuManager = MenuManager::Get(context.project);
|
|
|
|
menuManager.mLastAnalyzerRegistration = MenuCreator::repeattypeunique;
|
|
|
|
menuManager.mLastAnalyzerRegisteredId = mLastProcessId;
|
|
|
|
auto lastEffectDesc = XO("Repeat %s").Format(mNiceName);
|
|
|
|
Modify(wxT("RepeatLastAnalyzer"), lastEffectDesc);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Called by Selected Tools to mark them as Last Tools.
|
|
|
|
// Note that Repeat data has previously been collected
|
|
|
|
void CommandManager::RegisterLastTool(const CommandContext& context) {
|
|
|
|
if (mLastProcessId != 0) {
|
|
|
|
auto& menuManager = MenuManager::Get(context.project);
|
|
|
|
menuManager.mLastToolRegistration = MenuCreator::repeattypeunique;
|
|
|
|
menuManager.mLastToolRegisteredId = mLastProcessId;
|
|
|
|
auto lastEffectDesc = XO("Repeat %s").Format(mNiceName);
|
|
|
|
Modify(wxT("RepeatLastTool"), lastEffectDesc);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Used to invoke Repeat Last Analyzer Process for built-in, non-nyquist plug-ins.
|
|
|
|
void CommandManager::DoRepeatProcess(const CommandContext& context, int id) {
|
|
|
|
mLastProcessId = 0; //Don't Process this as repeat
|
|
|
|
CommandListEntry* entry = mCommandNumericIDHash[id];
|
|
|
|
auto& handler = entry->finder(context.project);
|
|
|
|
(handler.*(entry->callback))(context);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
///Call this when a menu event is received.
|
|
|
|
///If it matches a command, it will call the appropriate
|
|
|
|
///CommandManagerListener function. If you pass any flags,
|
|
|
|
///the command won't be executed unless the flags are compatible
|
|
|
|
///with the command's flags.
|
2020-01-04 01:43:19 +00:00
|
|
|
bool CommandManager::HandleMenuID(
|
|
|
|
AudacityProject &project, int id, CommandFlag flags, bool alwaysEnabled)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2021-02-03 11:02:49 +00:00
|
|
|
mLastProcessId = id;
|
2019-02-27 17:57:21 +00:00
|
|
|
CommandListEntry *entry = mCommandNumericIDHash[id];
|
2017-08-29 14:02:22 +00:00
|
|
|
|
2019-06-10 23:10:07 +00:00
|
|
|
auto hook = sMenuHook();
|
|
|
|
if (hook && hook(entry->name))
|
2017-08-29 14:02:22 +00:00
|
|
|
return true;
|
|
|
|
|
2020-01-04 01:43:19 +00:00
|
|
|
return HandleCommandEntry( project, entry, flags, alwaysEnabled );
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2020-04-11 07:08:33 +00:00
|
|
|
/// HandleTextualCommand() allows us a limited version of script/batch
|
2010-01-23 19:44:49 +00:00
|
|
|
/// behavior, since we can get from a string command name to the actual
|
|
|
|
/// code to run.
|
2019-06-14 04:50:21 +00:00
|
|
|
CommandManager::TextualCommandResult
|
|
|
|
CommandManager::HandleTextualCommand(const CommandID & Str,
|
|
|
|
const CommandContext & context, CommandFlag flags, bool alwaysEnabled)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2019-02-12 00:10:48 +00:00
|
|
|
if( Str.empty() )
|
2019-06-14 04:50:21 +00:00
|
|
|
return CommandFailure;
|
2010-01-23 19:44:49 +00:00
|
|
|
// Linear search for now...
|
2016-02-19 15:49:50 +00:00
|
|
|
for (const auto &entry : mCommandList)
|
The fabled realtime effects...
I've made it where you can enable and disable via experimentals:
EXPERIMENTAL_REALTIME_EFFECTS
EXPERIMENTAL_EFFECTS_RACK
You will notice that, as of now, the only effects currently set up for
realtime are VSTs. Now that this is in, I will start converting the
rest.
As I start to convert the effects, the astute of you may notice that
they no longer directly access tracks or any "internal" Audacity
objects. This isolates the effects from changes in Audacity and makes
it much easier to add new ones.
Anyway, all 3 platforms can now display VST effects in graphical mode.
Yes, that means Linux too. There are quite a few VSTs for Linux if
you search for them.
The so-called "rack" definitely needs some discussion, work, and attention
from someone much better at graphics than me. I'm not really sure it should
stay in as-is. I'd originally planned for it to be simply a utility window
where you can store your (preconfigured) favorite effects. It should probably
revert back to that idea.
You may notice that this DOES include the API work I did. The realtime effects
were too tied to it and I didn't want to redo the whole thing. As I mentioned
elsewhere, the API stuff may or may not be very future proof.
So, let the critter complaints commence. I absolute KNOW there will be some.
(I know I'll be hearing from the Linux peeps pretty darn quickly. ;-))
2014-10-26 03:24:10 +00:00
|
|
|
{
|
2016-02-19 15:49:50 +00:00
|
|
|
if (!entry->multi)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2017-06-02 22:52:53 +00:00
|
|
|
// Testing against labelPrefix too allows us to call Nyquist functions by name.
|
2019-02-27 18:14:25 +00:00
|
|
|
if( Str == entry->name ||
|
|
|
|
// PRL: uh oh, mixing internal string (Str) with user-visible
|
|
|
|
// (labelPrefix, which was initialized from a user-visible
|
|
|
|
// sub-menu name)
|
2019-01-09 20:26:32 +00:00
|
|
|
Str == entry->labelPrefix.Translation() )
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2020-01-04 01:43:19 +00:00
|
|
|
return HandleCommandEntry(
|
|
|
|
context.project, entry.get(), flags, alwaysEnabled)
|
2019-06-14 04:50:21 +00:00
|
|
|
? CommandSuccess : CommandFailure;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
2018-02-17 16:08:38 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
// Handle multis too...
|
2019-02-27 18:14:25 +00:00
|
|
|
if( Str == entry->name )
|
2018-02-17 16:08:38 +00:00
|
|
|
{
|
2020-01-04 01:43:19 +00:00
|
|
|
return HandleCommandEntry(
|
|
|
|
context.project, entry.get(), flags, alwaysEnabled)
|
2019-06-14 04:50:21 +00:00
|
|
|
? CommandSuccess : CommandFailure;
|
2018-02-17 16:08:38 +00:00
|
|
|
}
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2019-06-14 04:50:21 +00:00
|
|
|
return CommandNotFound;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2020-05-05 20:16:56 +00:00
|
|
|
TranslatableStrings CommandManager::GetCategories( AudacityProject& )
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2020-05-05 20:16:56 +00:00
|
|
|
TranslatableStrings cats;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2016-02-19 15:49:50 +00:00
|
|
|
for (const auto &entry : mCommandList) {
|
2020-05-05 20:16:56 +00:00
|
|
|
auto &cat = entry->labelTop;
|
2019-03-04 07:13:40 +00:00
|
|
|
if ( ! make_iterator_range( cats ).contains(cat) ) {
|
2019-02-12 00:10:48 +00:00
|
|
|
cats.push_back(cat);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
#if 0
|
2019-02-12 00:10:48 +00:00
|
|
|
mCommandList.size(); i++) {
|
2010-01-23 19:44:49 +00:00
|
|
|
if (includeMultis || !mCommandList[i]->multi)
|
2019-02-12 00:10:48 +00:00
|
|
|
names.push_back(mCommandList[i]->name);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (p == NULL) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
wxMenuBar *bar = p->GetMenuBar();
|
|
|
|
size_t cnt = bar->GetMenuCount();
|
|
|
|
for (size_t i = 0; i < cnt; i++) {
|
2019-02-12 00:10:48 +00:00
|
|
|
cats.push_back(bar->GetMenuLabelText(i));
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2019-02-12 00:10:48 +00:00
|
|
|
cats.push_back(COMMAND);
|
2010-01-23 19:44:49 +00:00
|
|
|
#endif
|
2020-05-05 20:16:56 +00:00
|
|
|
|
|
|
|
return cats;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2019-03-11 02:05:37 +00:00
|
|
|
void CommandManager::GetAllCommandNames(CommandIDs &names,
|
2018-03-06 20:28:05 +00:00
|
|
|
bool includeMultis) const
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-02-19 15:49:50 +00:00
|
|
|
for(const auto &entry : mCommandList) {
|
2018-03-01 14:05:57 +00:00
|
|
|
if ( entry->isEffect )
|
|
|
|
continue;
|
2016-02-19 15:49:50 +00:00
|
|
|
if (!entry->multi)
|
2019-02-12 00:10:48 +00:00
|
|
|
names.push_back(entry->name);
|
2012-03-29 20:31:49 +00:00
|
|
|
else if( includeMultis )
|
2019-02-12 00:10:48 +00:00
|
|
|
names.push_back(entry->name );// + wxT(":")/*+ mCommandList[i]->label*/);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-15 18:51:21 +00:00
|
|
|
void CommandManager::GetAllCommandLabels(TranslatableStrings &names,
|
2019-12-12 18:37:33 +00:00
|
|
|
std::vector<bool> &vExcludeFromMacros,
|
2018-03-06 20:28:05 +00:00
|
|
|
bool includeMultis) const
|
2012-03-29 20:31:49 +00:00
|
|
|
{
|
2019-12-12 18:37:33 +00:00
|
|
|
vExcludeFromMacros.clear();
|
2016-02-19 15:49:50 +00:00
|
|
|
for(const auto &entry : mCommandList) {
|
2018-03-01 14:05:57 +00:00
|
|
|
// This is fetching commands from the menus, for use as batch commands.
|
|
|
|
// Until we have properly merged EffectManager and CommandManager
|
2018-08-15 10:29:12 +00:00
|
|
|
// we explicitly exclude effects, as they are already handled by the
|
2018-03-01 14:05:57 +00:00
|
|
|
// effects Manager.
|
|
|
|
if ( entry->isEffect )
|
|
|
|
continue;
|
2016-02-19 15:49:50 +00:00
|
|
|
if (!entry->multi)
|
2019-12-12 18:37:33 +00:00
|
|
|
names.push_back(entry->longLabel), vExcludeFromMacros.push_back(entry->excludeFromMacros);
|
2012-03-29 20:31:49 +00:00
|
|
|
else if( includeMultis )
|
2019-12-12 18:37:33 +00:00
|
|
|
names.push_back(entry->longLabel), vExcludeFromMacros.push_back(entry->excludeFromMacros);
|
2012-03-29 20:31:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CommandManager::GetAllCommandData(
|
2019-03-11 02:05:37 +00:00
|
|
|
CommandIDs &names,
|
2018-02-09 02:16:13 +00:00
|
|
|
std::vector<NormalizedKeyString> &keys,
|
|
|
|
std::vector<NormalizedKeyString> &default_keys,
|
2019-12-15 18:51:21 +00:00
|
|
|
TranslatableStrings &labels,
|
2020-05-05 20:16:56 +00:00
|
|
|
TranslatableStrings &categories,
|
2013-10-01 06:00:13 +00:00
|
|
|
#if defined(EXPERIMENTAL_KEY_VIEW)
|
2019-01-09 20:26:32 +00:00
|
|
|
TranslatableStrings &prefixes,
|
2013-10-01 06:00:13 +00:00
|
|
|
#endif
|
2012-03-29 20:31:49 +00:00
|
|
|
bool includeMultis)
|
|
|
|
{
|
2016-02-19 15:49:50 +00:00
|
|
|
for(const auto &entry : mCommandList) {
|
2018-03-17 21:20:09 +00:00
|
|
|
// GetAllCommandData is used by KeyConfigPrefs.
|
|
|
|
// It does need the effects.
|
|
|
|
//if ( entry->isEffect )
|
|
|
|
// continue;
|
2020-05-05 20:16:56 +00:00
|
|
|
if ( !entry->multi || includeMultis )
|
2012-03-29 20:31:49 +00:00
|
|
|
{
|
2019-02-12 00:10:48 +00:00
|
|
|
names.push_back(entry->name);
|
2018-02-09 02:16:13 +00:00
|
|
|
keys.push_back(entry->key);
|
|
|
|
default_keys.push_back(entry->defaultKey);
|
2019-02-12 00:10:48 +00:00
|
|
|
labels.push_back(entry->label);
|
|
|
|
categories.push_back(entry->labelTop);
|
2013-10-01 06:00:13 +00:00
|
|
|
#if defined(EXPERIMENTAL_KEY_VIEW)
|
2019-02-12 00:10:48 +00:00
|
|
|
prefixes.push_back(entry->labelPrefix);
|
2013-10-01 06:00:13 +00:00
|
|
|
#endif
|
2012-03-29 20:31:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-02-10 13:25:10 +00:00
|
|
|
|
2019-02-27 17:57:21 +00:00
|
|
|
CommandID CommandManager::GetNameFromNumericID(int id)
|
2018-02-10 13:25:10 +00:00
|
|
|
{
|
2019-02-27 17:57:21 +00:00
|
|
|
CommandListEntry *entry = mCommandNumericIDHash[id];
|
2018-02-10 13:25:10 +00:00
|
|
|
if (!entry)
|
2019-03-11 02:05:37 +00:00
|
|
|
return {};
|
2018-02-10 13:25:10 +00:00
|
|
|
return entry->name;
|
|
|
|
}
|
|
|
|
|
2019-12-15 18:51:21 +00:00
|
|
|
TranslatableString CommandManager::GetLabelFromName(const CommandID &name)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
CommandListEntry *entry = mCommandNameHash[name];
|
|
|
|
if (!entry)
|
2019-12-15 18:51:21 +00:00
|
|
|
return {};
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2018-03-19 19:08:37 +00:00
|
|
|
return entry->longLabel;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2019-12-15 18:51:21 +00:00
|
|
|
TranslatableString CommandManager::GetPrefixedLabelFromName(const CommandID &name)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
CommandListEntry *entry = mCommandNameHash[name];
|
|
|
|
if (!entry)
|
2019-12-15 18:51:21 +00:00
|
|
|
return {};
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2019-12-15 18:51:21 +00:00
|
|
|
if (!entry->labelPrefix.empty())
|
2019-12-19 21:20:41 +00:00
|
|
|
return Verbatim( wxT("%s - %s") )
|
2020-05-25 23:56:06 +00:00
|
|
|
.Format(entry->labelPrefix, entry->label)
|
|
|
|
.Stripped();
|
2019-12-15 18:51:21 +00:00
|
|
|
else
|
2020-05-25 23:56:06 +00:00
|
|
|
return entry->label.Stripped();
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2020-05-05 20:16:56 +00:00
|
|
|
TranslatableString CommandManager::GetCategoryFromName(const CommandID &name)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
CommandListEntry *entry = mCommandNameHash[name];
|
|
|
|
if (!entry)
|
2020-05-05 20:16:56 +00:00
|
|
|
return {};
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
return entry->labelTop;
|
|
|
|
}
|
|
|
|
|
2019-03-11 02:05:37 +00:00
|
|
|
NormalizedKeyString CommandManager::GetKeyFromName(const CommandID &name) const
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2017-02-22 19:23:35 +00:00
|
|
|
CommandListEntry *entry =
|
|
|
|
// May create a NULL entry
|
|
|
|
const_cast<CommandManager*>(this)->mCommandNameHash[name];
|
2010-01-23 19:44:49 +00:00
|
|
|
if (!entry)
|
2018-02-09 02:16:13 +00:00
|
|
|
return {};
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
return entry->key;
|
|
|
|
}
|
|
|
|
|
2019-03-11 02:05:37 +00:00
|
|
|
NormalizedKeyString CommandManager::GetDefaultKeyFromName(const CommandID &name)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
CommandListEntry *entry = mCommandNameHash[name];
|
|
|
|
if (!entry)
|
2018-02-09 02:16:13 +00:00
|
|
|
return {};
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
return entry->defaultKey;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CommandManager::HandleXMLTag(const wxChar *tag, const wxChar **attrs)
|
|
|
|
{
|
|
|
|
if (!wxStrcmp(tag, wxT("audacitykeyboard"))) {
|
|
|
|
mXMLKeysRead = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!wxStrcmp(tag, wxT("command"))) {
|
|
|
|
wxString name;
|
2018-02-09 02:16:13 +00:00
|
|
|
NormalizedKeyString key;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
while(*attrs) {
|
|
|
|
const wxChar *attr = *attrs++;
|
|
|
|
const wxChar *value = *attrs++;
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
if (!value)
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (!wxStrcmp(attr, wxT("name")) && XMLValueChecker::IsGoodString(value))
|
|
|
|
name = value;
|
|
|
|
if (!wxStrcmp(attr, wxT("key")) && XMLValueChecker::IsGoodString(value))
|
2018-02-09 02:16:13 +00:00
|
|
|
key = NormalizedKeyString{ value };
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (mCommandNameHash[name]) {
|
Bug 1762: Importing keystrokes not in standard set
Problem:
1. Use either no prior config files, or set the default shortcuts to standard.
2. If a set of keyboard shortcuts is imported, then any shortcut for a command which only has a default shortcut in the full set is not set, unless it differs from the default shortcut.
This occurs because in CommandManager::HandleXMLTag(), the imported shortcut is only used if it's different from the default shortcut. But for all commands, even those commands which only have a default shortcut in the full set, GetDefaultKeyFromName() returns the default shortcut.
Fix:
Use the imported shortcut, regardless of whether it is different from the default shortcut. (I've left the function GetDefaultKeyFromName(), even though it is not longer used.)
Note:
This results in a change of behaviour:
Before: if you'd customized the shortcut for a command, then if you imported a set of shortcuts, that customized shortcut would only have been changed if the imported shortcut was also customized.
Now: If you import a set of shortcuts, then these become your shortcuts, regardless of any customizations present before the import.
I think the new behaviour is what a user would expect.
2019-04-02 13:24:42 +00:00
|
|
|
mCommandNameHash[name]->key = key;
|
|
|
|
mXMLKeysRead++;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
Bug 624: Keyboard Prefs: importing XML file can cause duplicated bindings
The fix follows the agreed behavior (see emails from around October 25) . For the sake of convenience see the agreed behavior below:
_"- first, check the xml-file. If it contains illegal shortcut duplicates, refuse importing. Shortcut duplicates are LEGAL if default settings also have those operations with the matching shortcuts. A refusal to import shortcuts must happen with the message that warns the user of a failure and explains the reason.
if the xml-file looks ok, import the shortcuts. As discussed before, because different versions of Audacity might have different sets of operations with shortcuts, it is still possible to end up with illegal shortcut duplicates for a perfectly correct xml-file. This situation must be monitored. In case of any conflicts, the shortcut from the xml-file is used, and the pre-existing shortcut is wiped clean.
When telling the user the commands which have had their shortcut removed, I think it would be useful to tell the user the name of the command, the shortcut, and and name of the command which still has that shortcut."_
I didn't find a clean way to intercept the imported content before it makes its way to the shortcut preferences, so I had to jump through some hoops right in KeyConfigPrefs::OnImport().
In general, I tried to keep changes minimal.
2020-01-09 19:47:50 +00:00
|
|
|
// This message is displayed now in KeyConfigPrefs::OnImport()
|
2010-01-23 19:44:49 +00:00
|
|
|
void CommandManager::HandleXMLEndTag(const wxChar *tag)
|
|
|
|
{
|
Bug 624: Keyboard Prefs: importing XML file can cause duplicated bindings
The fix follows the agreed behavior (see emails from around October 25) . For the sake of convenience see the agreed behavior below:
_"- first, check the xml-file. If it contains illegal shortcut duplicates, refuse importing. Shortcut duplicates are LEGAL if default settings also have those operations with the matching shortcuts. A refusal to import shortcuts must happen with the message that warns the user of a failure and explains the reason.
if the xml-file looks ok, import the shortcuts. As discussed before, because different versions of Audacity might have different sets of operations with shortcuts, it is still possible to end up with illegal shortcut duplicates for a perfectly correct xml-file. This situation must be monitored. In case of any conflicts, the shortcut from the xml-file is used, and the pre-existing shortcut is wiped clean.
When telling the user the commands which have had their shortcut removed, I think it would be useful to tell the user the name of the command, the shortcut, and and name of the command which still has that shortcut."_
I didn't find a clean way to intercept the imported content before it makes its way to the shortcut preferences, so I had to jump through some hoops right in KeyConfigPrefs::OnImport().
In general, I tried to keep changes minimal.
2020-01-09 19:47:50 +00:00
|
|
|
/*
|
2010-01-23 19:44:49 +00:00
|
|
|
if (!wxStrcmp(tag, wxT("audacitykeyboard"))) {
|
2019-12-07 19:30:07 +00:00
|
|
|
AudacityMessageBox(
|
|
|
|
XO("Loaded %d keyboard shortcuts\n")
|
|
|
|
.Format( mXMLKeysRead ),
|
|
|
|
XO("Loading Keyboard Shortcuts"),
|
|
|
|
wxOK | wxCENTRE);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
Bug 624: Keyboard Prefs: importing XML file can cause duplicated bindings
The fix follows the agreed behavior (see emails from around October 25) . For the sake of convenience see the agreed behavior below:
_"- first, check the xml-file. If it contains illegal shortcut duplicates, refuse importing. Shortcut duplicates are LEGAL if default settings also have those operations with the matching shortcuts. A refusal to import shortcuts must happen with the message that warns the user of a failure and explains the reason.
if the xml-file looks ok, import the shortcuts. As discussed before, because different versions of Audacity might have different sets of operations with shortcuts, it is still possible to end up with illegal shortcut duplicates for a perfectly correct xml-file. This situation must be monitored. In case of any conflicts, the shortcut from the xml-file is used, and the pre-existing shortcut is wiped clean.
When telling the user the commands which have had their shortcut removed, I think it would be useful to tell the user the name of the command, the shortcut, and and name of the command which still has that shortcut."_
I didn't find a clean way to intercept the imported content before it makes its way to the shortcut preferences, so I had to jump through some hoops right in KeyConfigPrefs::OnImport().
In general, I tried to keep changes minimal.
2020-01-09 19:47:50 +00:00
|
|
|
*/
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2013-08-25 12:54:24 +00:00
|
|
|
XMLTagHandler *CommandManager::HandleXMLChild(const wxChar * WXUNUSED(tag))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
2017-02-22 19:23:35 +00:00
|
|
|
void CommandManager::WriteXML(XMLWriter &xmlFile) const
|
2016-12-01 22:03:40 +00:00
|
|
|
// may throw
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
xmlFile.StartTag(wxT("audacitykeyboard"));
|
|
|
|
xmlFile.WriteAttr(wxT("audacityversion"), AUDACITY_VERSION_STRING);
|
|
|
|
|
2016-02-19 15:49:50 +00:00
|
|
|
for(const auto &entry : mCommandList) {
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2013-08-25 22:21:32 +00:00
|
|
|
xmlFile.StartTag(wxT("command"));
|
2016-02-19 15:49:50 +00:00
|
|
|
xmlFile.WriteAttr(wxT("name"), entry->name);
|
2019-02-28 12:37:10 +00:00
|
|
|
xmlFile.WriteAttr(wxT("key"), entry->key);
|
2013-08-25 22:21:32 +00:00
|
|
|
xmlFile.EndTag(wxT("command"));
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
xmlFile.EndTag(wxT("audacitykeyboard"));
|
|
|
|
}
|
|
|
|
|
2018-10-20 18:01:20 +00:00
|
|
|
void CommandManager::BeginOccultCommands()
|
2017-04-21 14:47:17 +00:00
|
|
|
{
|
2018-10-20 18:01:20 +00:00
|
|
|
// To do: perhaps allow occult item switching at lower levels of the
|
|
|
|
// menu tree.
|
|
|
|
wxASSERT( !CurrentMenu() );
|
|
|
|
|
|
|
|
// Make a temporary menu bar collecting items added after.
|
|
|
|
// This bar will be discarded but other side effects on the command
|
|
|
|
// manager persist.
|
|
|
|
mTempMenuBar = AddMenuBar(wxT("ext-menu"));
|
|
|
|
bMakingOccultCommands = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CommandManager::EndOccultCommands()
|
|
|
|
{
|
|
|
|
PopMenuBar();
|
|
|
|
bMakingOccultCommands = false;
|
|
|
|
mTempMenuBar.reset();
|
2017-04-21 14:47:17 +00:00
|
|
|
}
|
|
|
|
|
2019-03-11 02:05:37 +00:00
|
|
|
void CommandManager::SetCommandFlags(const CommandID &name,
|
2019-06-13 20:56:05 +00:00
|
|
|
CommandFlag flags)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
CommandListEntry *entry = mCommandNameHash[name];
|
2019-06-13 20:56:05 +00:00
|
|
|
if (entry)
|
2010-01-23 19:44:49 +00:00
|
|
|
entry->flags = flags;
|
|
|
|
}
|
|
|
|
|
2020-04-25 07:36:27 +00:00
|
|
|
#if defined(_DEBUG)
|
2010-01-23 19:44:49 +00:00
|
|
|
void CommandManager::CheckDups()
|
|
|
|
{
|
2016-02-19 15:49:50 +00:00
|
|
|
int cnt = mCommandList.size();
|
2010-01-23 19:44:49 +00:00
|
|
|
for (size_t j = 0; (int)j < cnt; j++) {
|
2018-02-09 02:16:13 +00:00
|
|
|
if (mCommandList[j]->key.empty()) {
|
2010-01-23 19:44:49 +00:00
|
|
|
continue;
|
|
|
|
}
|
2020-05-05 04:02:21 +00:00
|
|
|
|
|
|
|
if (mCommandList[j]->allowDup)
|
|
|
|
continue;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
for (size_t i = 0; (int)i < cnt; i++) {
|
|
|
|
if (i == j) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mCommandList[i]->key == mCommandList[j]->key) {
|
|
|
|
wxString msg;
|
|
|
|
msg.Printf(wxT("key combo '%s' assigned to '%s' and '%s'"),
|
2019-02-28 12:37:10 +00:00
|
|
|
// using GET to form debug message
|
|
|
|
mCommandList[i]->key.GET(),
|
2019-12-15 18:51:21 +00:00
|
|
|
mCommandList[i]->label.Debug(),
|
|
|
|
mCommandList[j]->label.Debug());
|
2010-01-23 19:44:49 +00:00
|
|
|
wxASSERT_MSG(mCommandList[i]->key != mCommandList[j]->key, msg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-07-28 19:35:09 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
#endif
|
2019-05-14 17:59:24 +00:00
|
|
|
|
2020-02-14 14:56:24 +00:00
|
|
|
// If a default shortcut of a command is introduced or changed, then this
|
|
|
|
// shortcut may be the same shortcut a user has previously assigned to another
|
|
|
|
// command. This function removes such duplicates by removing the shortcut
|
|
|
|
// from the command whose default has changed.
|
|
|
|
// Note that two commands may have the same shortcut if their default shortcuts
|
|
|
|
// are the same. However, in this function default shortcuts are checked against
|
|
|
|
// user assigned shortcuts. Two such commands with the same shortcut
|
|
|
|
// must both be in either the first or the second group, so there is no need
|
|
|
|
// to test for this case.
|
|
|
|
// Note that if a user is using the full set of default shortcuts, and one
|
|
|
|
// of these is changed, then if /GUI/Shortcuts/FullDefaults is not set in audacity.cfg,
|
2021-01-12 11:56:09 +00:00
|
|
|
// because the defaults appear as user assigned shortcuts in audacity.cfg,
|
2020-02-14 14:56:24 +00:00
|
|
|
// the previous default overrides the changed default, and no duplicate can
|
|
|
|
// be introduced.
|
|
|
|
void CommandManager::RemoveDuplicateShortcuts()
|
|
|
|
{
|
|
|
|
TranslatableString disabledShortcuts;
|
|
|
|
|
|
|
|
for (auto& entry : mCommandList) {
|
|
|
|
if (!entry->key.empty() && entry->key != entry->defaultKey) { // user assigned
|
|
|
|
for (auto& entry2 : mCommandList) {
|
|
|
|
if (!entry2->key.empty() && entry2->key == entry2->defaultKey) { // default
|
|
|
|
if (entry2->key == entry->key) {
|
|
|
|
auto name = wxT("/NewKeys/") + entry2->name.GET();
|
|
|
|
gPrefs->Write(name, NormalizedKeyString{});
|
|
|
|
|
|
|
|
disabledShortcuts +=
|
|
|
|
XO("\n* %s, because you have assigned the shortcut %s to %s")
|
|
|
|
.Format(entry2->label.Strip(), entry->key.GET(), entry->label.Strip());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!disabledShortcuts.Translation().empty()) {
|
|
|
|
TranslatableString message = XO("The following commands have had their shortcuts removed,"
|
|
|
|
" because their default shortcut is new or changed, and is the same shortcut"
|
|
|
|
" that you have assigned to another command.")
|
|
|
|
+ disabledShortcuts;
|
|
|
|
AudacityMessageBox(message, XO("Shortcuts have been removed"), wxOK | wxCENTRE);
|
|
|
|
|
|
|
|
gPrefs->Flush();
|
|
|
|
MenuCreator::RebuildAllMenuBars();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-14 17:59:24 +00:00
|
|
|
#include "../KeyboardCapture.h"
|
|
|
|
|
|
|
|
static struct InstallHandlers
|
|
|
|
{
|
|
|
|
InstallHandlers()
|
|
|
|
{
|
|
|
|
KeyboardCapture::SetPreFilter( []( wxKeyEvent & ) {
|
|
|
|
// We must have a project since we will be working with the
|
|
|
|
// CommandManager, which is tied to individual projects.
|
|
|
|
AudacityProject *project = GetActiveProject();
|
2019-05-28 17:12:56 +00:00
|
|
|
return project && GetProjectFrame( *project ).IsEnabled();
|
2019-05-14 17:59:24 +00:00
|
|
|
} );
|
|
|
|
KeyboardCapture::SetPostFilter( []( wxKeyEvent &key ) {
|
|
|
|
// Capture handler window didn't want it, so ask the CommandManager.
|
|
|
|
AudacityProject *project = GetActiveProject();
|
2019-04-26 21:17:04 +00:00
|
|
|
auto &manager = CommandManager::Get( *project );
|
|
|
|
return manager.FilterKeyEvent(project, key);
|
2019-05-14 17:59:24 +00:00
|
|
|
} );
|
|
|
|
}
|
|
|
|
} installHandlers;
|
2019-06-13 16:29:15 +00:00
|
|
|
|