migrating to the latest JUCE version

This commit is contained in:
2022-11-04 23:11:33 +01:00
committed by Nikolai Rodionov
parent 4257a0f8ba
commit faf8f18333
2796 changed files with 888518 additions and 784244 deletions

View File

@ -1,90 +1,90 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/** A type used to hold the unique ID for an application command.
This is a numeric type, so it can be stored as an integer.
@see ApplicationCommandInfo, ApplicationCommandManager,
ApplicationCommandTarget, KeyPressMappingSet
*/
using CommandID = int;
//==============================================================================
/** A set of general-purpose application command IDs.
Because these commands are likely to be used in most apps, they're defined
here to help different apps to use the same numeric values for them.
Of course you don't have to use these, but some of them are used internally by
JUCE - e.g. the quit ID is recognised as a command by the JUCEApplication class.
@see ApplicationCommandInfo, ApplicationCommandManager,
ApplicationCommandTarget, KeyPressMappingSet
*/
namespace StandardApplicationCommandIDs
{
enum
{
/** This command ID should be used to send a "Quit the App" command.
This command is recognised by the JUCEApplication class, so if it is invoked
and no other ApplicationCommandTarget handles the event first, the JUCEApplication
object will catch it and call JUCEApplicationBase::systemRequestedQuit().
*/
quit = 0x1001,
/** The command ID that should be used to send a "Delete" command. */
del = 0x1002,
/** The command ID that should be used to send a "Cut" command. */
cut = 0x1003,
/** The command ID that should be used to send a "Copy to clipboard" command. */
copy = 0x1004,
/** The command ID that should be used to send a "Paste from clipboard" command. */
paste = 0x1005,
/** The command ID that should be used to send a "Select all" command. */
selectAll = 0x1006,
/** The command ID that should be used to send a "Deselect all" command. */
deselectAll = 0x1007,
/** The command ID that should be used to send a "undo" command. */
undo = 0x1008,
/** The command ID that should be used to send a "redo" command. */
redo = 0x1009
};
}
} // namespace juce
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/** A type used to hold the unique ID for an application command.
This is a numeric type, so it can be stored as an integer.
@see ApplicationCommandInfo, ApplicationCommandManager,
ApplicationCommandTarget, KeyPressMappingSet
*/
using CommandID = int;
//==============================================================================
/** A set of general-purpose application command IDs.
Because these commands are likely to be used in most apps, they're defined
here to help different apps to use the same numeric values for them.
Of course you don't have to use these, but some of them are used internally by
JUCE - e.g. the quit ID is recognised as a command by the JUCEApplication class.
@see ApplicationCommandInfo, ApplicationCommandManager,
ApplicationCommandTarget, KeyPressMappingSet
*/
namespace StandardApplicationCommandIDs
{
enum
{
/** This command ID should be used to send a "Quit the App" command.
This command is recognised by the JUCEApplication class, so if it is invoked
and no other ApplicationCommandTarget handles the event first, the JUCEApplication
object will catch it and call JUCEApplicationBase::systemRequestedQuit().
*/
quit = 0x1001,
/** The command ID that should be used to send a "Delete" command. */
del = 0x1002,
/** The command ID that should be used to send a "Cut" command. */
cut = 0x1003,
/** The command ID that should be used to send a "Copy to clipboard" command. */
copy = 0x1004,
/** The command ID that should be used to send a "Paste from clipboard" command. */
paste = 0x1005,
/** The command ID that should be used to send a "Select all" command. */
selectAll = 0x1006,
/** The command ID that should be used to send a "Deselect all" command. */
deselectAll = 0x1007,
/** The command ID that should be used to send a "undo" command. */
undo = 0x1008,
/** The command ID that should be used to send a "redo" command. */
redo = 0x1009
};
}
} // namespace juce

View File

@ -1,66 +1,66 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
ApplicationCommandInfo::ApplicationCommandInfo (const CommandID cid) noexcept
: commandID (cid), flags (0)
{
}
void ApplicationCommandInfo::setInfo (const String& shortName_,
const String& description_,
const String& categoryName_,
const int flags_) noexcept
{
shortName = shortName_;
description = description_;
categoryName = categoryName_;
flags = flags_;
}
void ApplicationCommandInfo::setActive (const bool b) noexcept
{
if (b)
flags &= ~isDisabled;
else
flags |= isDisabled;
}
void ApplicationCommandInfo::setTicked (const bool b) noexcept
{
if (b)
flags |= isTicked;
else
flags &= ~isTicked;
}
void ApplicationCommandInfo::addDefaultKeypress (const int keyCode, ModifierKeys modifiers) noexcept
{
defaultKeypresses.add (KeyPress (keyCode, modifiers, 0));
}
} // namespace juce
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
ApplicationCommandInfo::ApplicationCommandInfo (const CommandID cid) noexcept
: commandID (cid), flags (0)
{
}
void ApplicationCommandInfo::setInfo (const String& shortName_,
const String& description_,
const String& categoryName_,
const int flags_) noexcept
{
shortName = shortName_;
description = description_;
categoryName = categoryName_;
flags = flags_;
}
void ApplicationCommandInfo::setActive (const bool b) noexcept
{
if (b)
flags &= ~isDisabled;
else
flags |= isDisabled;
}
void ApplicationCommandInfo::setTicked (const bool b) noexcept
{
if (b)
flags |= isTicked;
else
flags &= ~isTicked;
}
void ApplicationCommandInfo::addDefaultKeypress (const int keyCode, ModifierKeys modifiers) noexcept
{
defaultKeypresses.add (KeyPress (keyCode, modifiers, 0));
}
} // namespace juce

View File

@ -1,190 +1,190 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
Holds information describing an application command.
This object is used to pass information about a particular command, such as its
name, description and other usage flags.
When an ApplicationCommandTarget is asked to provide information about the commands
it can perform, this is the structure gets filled-in to describe each one.
@see ApplicationCommandTarget, ApplicationCommandTarget::getCommandInfo(),
ApplicationCommandManager
@tags{GUI}
*/
struct JUCE_API ApplicationCommandInfo
{
//==============================================================================
explicit ApplicationCommandInfo (CommandID commandID) noexcept;
//==============================================================================
/** Sets a number of the structures values at once.
The meanings of each of the parameters is described below, in the appropriate
member variable's description.
*/
void setInfo (const String& shortName,
const String& description,
const String& categoryName,
int flags) noexcept;
/** An easy way to set or remove the isDisabled bit in the structure's flags field.
If isActive is true, the flags member has the isDisabled bit cleared; if isActive
is false, the bit is set.
*/
void setActive (bool isActive) noexcept;
/** An easy way to set or remove the isTicked bit in the structure's flags field.
*/
void setTicked (bool isTicked) noexcept;
/** Handy method for adding a keypress to the defaultKeypresses array.
This is just so you can write things like:
@code
myinfo.addDefaultKeypress ('s', ModifierKeys::commandModifier);
@endcode
instead of
@code
myinfo.defaultKeypresses.add (KeyPress ('s', ModifierKeys::commandModifier));
@endcode
*/
void addDefaultKeypress (int keyCode, ModifierKeys modifiers) noexcept;
//==============================================================================
/** The command's unique ID number.
*/
CommandID commandID;
/** A short name to describe the command.
This should be suitable for use in menus, on buttons that trigger the command, etc.
You can use the setInfo() method to quickly set this and some of the command's
other properties.
*/
String shortName;
/** A longer description of the command.
This should be suitable for use in contexts such as a KeyMappingEditorComponent or
pop-up tooltip describing what the command does.
You can use the setInfo() method to quickly set this and some of the command's
other properties.
*/
String description;
/** A named category that the command fits into.
You can give your commands any category you like, and these will be displayed in
contexts such as the KeyMappingEditorComponent, where the category is used to group
commands together.
You can use the setInfo() method to quickly set this and some of the command's
other properties.
*/
String categoryName;
/** A list of zero or more keypresses that should be used as the default keys for
this command.
Methods such as KeyPressMappingSet::resetToDefaultMappings() will use the keypresses in
this list to initialise the default set of key-to-command mappings.
@see addDefaultKeypress
*/
Array<KeyPress> defaultKeypresses;
//==============================================================================
/** Flags describing the ways in which this command should be used.
A bitwise-OR of these values is stored in the ApplicationCommandInfo::flags
variable.
*/
enum CommandFlags
{
/** Indicates that the command can't currently be performed.
The ApplicationCommandTarget::getCommandInfo() method must set this flag if it's
not currently permissible to perform the command. If the flag is set, then
components that trigger the command, e.g. PopupMenu, may choose to grey-out the
command or show themselves as not being enabled.
@see ApplicationCommandInfo::setActive
*/
isDisabled = 1 << 0,
/** Indicates that the command should have a tick next to it on a menu.
If your command is shown on a menu and this is set, it'll show a tick next to
it. Other components such as buttons may also use this flag to indicate that it
is a value that can be toggled, and is currently in the 'on' state.
@see ApplicationCommandInfo::setTicked
*/
isTicked = 1 << 1,
/** If this flag is present, then when a KeyPressMappingSet invokes the command,
it will call the command twice, once on key-down and again on key-up.
@see ApplicationCommandTarget::InvocationInfo
*/
wantsKeyUpDownCallbacks = 1 << 2,
/** If this flag is present, then a KeyMappingEditorComponent will not display the
command in its list.
*/
hiddenFromKeyEditor = 1 << 3,
/** If this flag is present, then a KeyMappingEditorComponent will display the
command in its list, but won't allow the assigned keypress to be changed.
*/
readOnlyInKeyEditor = 1 << 4,
/** If this flag is present and the command is invoked from a keypress, then any
buttons or menus that are also connected to the command will not flash to
indicate that they've been triggered.
*/
dontTriggerVisualFeedback = 1 << 5
};
/** A bitwise-OR of the values specified in the CommandFlags enum.
You can use the setInfo() method to quickly set this and some of the command's
other properties.
*/
int flags;
};
} // namespace juce
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
Holds information describing an application command.
This object is used to pass information about a particular command, such as its
name, description and other usage flags.
When an ApplicationCommandTarget is asked to provide information about the commands
it can perform, this is the structure gets filled-in to describe each one.
@see ApplicationCommandTarget, ApplicationCommandTarget::getCommandInfo(),
ApplicationCommandManager
@tags{GUI}
*/
struct JUCE_API ApplicationCommandInfo
{
//==============================================================================
explicit ApplicationCommandInfo (CommandID commandID) noexcept;
//==============================================================================
/** Sets a number of the structures values at once.
The meanings of each of the parameters is described below, in the appropriate
member variable's description.
*/
void setInfo (const String& shortName,
const String& description,
const String& categoryName,
int flags) noexcept;
/** An easy way to set or remove the isDisabled bit in the structure's flags field.
If isActive is true, the flags member has the isDisabled bit cleared; if isActive
is false, the bit is set.
*/
void setActive (bool isActive) noexcept;
/** An easy way to set or remove the isTicked bit in the structure's flags field.
*/
void setTicked (bool isTicked) noexcept;
/** Handy method for adding a keypress to the defaultKeypresses array.
This is just so you can write things like:
@code
myinfo.addDefaultKeypress ('s', ModifierKeys::commandModifier);
@endcode
instead of
@code
myinfo.defaultKeypresses.add (KeyPress ('s', ModifierKeys::commandModifier));
@endcode
*/
void addDefaultKeypress (int keyCode, ModifierKeys modifiers) noexcept;
//==============================================================================
/** The command's unique ID number.
*/
CommandID commandID;
/** A short name to describe the command.
This should be suitable for use in menus, on buttons that trigger the command, etc.
You can use the setInfo() method to quickly set this and some of the command's
other properties.
*/
String shortName;
/** A longer description of the command.
This should be suitable for use in contexts such as a KeyMappingEditorComponent or
pop-up tooltip describing what the command does.
You can use the setInfo() method to quickly set this and some of the command's
other properties.
*/
String description;
/** A named category that the command fits into.
You can give your commands any category you like, and these will be displayed in
contexts such as the KeyMappingEditorComponent, where the category is used to group
commands together.
You can use the setInfo() method to quickly set this and some of the command's
other properties.
*/
String categoryName;
/** A list of zero or more keypresses that should be used as the default keys for
this command.
Methods such as KeyPressMappingSet::resetToDefaultMappings() will use the keypresses in
this list to initialise the default set of key-to-command mappings.
@see addDefaultKeypress
*/
Array<KeyPress> defaultKeypresses;
//==============================================================================
/** Flags describing the ways in which this command should be used.
A bitwise-OR of these values is stored in the ApplicationCommandInfo::flags
variable.
*/
enum CommandFlags
{
/** Indicates that the command can't currently be performed.
The ApplicationCommandTarget::getCommandInfo() method must set this flag if it's
not currently permissible to perform the command. If the flag is set, then
components that trigger the command, e.g. PopupMenu, may choose to grey-out the
command or show themselves as not being enabled.
@see ApplicationCommandInfo::setActive
*/
isDisabled = 1 << 0,
/** Indicates that the command should have a tick next to it on a menu.
If your command is shown on a menu and this is set, it'll show a tick next to
it. Other components such as buttons may also use this flag to indicate that it
is a value that can be toggled, and is currently in the 'on' state.
@see ApplicationCommandInfo::setTicked
*/
isTicked = 1 << 1,
/** If this flag is present, then when a KeyPressMappingSet invokes the command,
it will call the command twice, once on key-down and again on key-up.
@see ApplicationCommandTarget::InvocationInfo
*/
wantsKeyUpDownCallbacks = 1 << 2,
/** If this flag is present, then a KeyMappingEditorComponent will not display the
command in its list.
*/
hiddenFromKeyEditor = 1 << 3,
/** If this flag is present, then a KeyMappingEditorComponent will display the
command in its list, but won't allow the assigned keypress to be changed.
*/
readOnlyInKeyEditor = 1 << 4,
/** If this flag is present and the command is invoked from a keypress, then any
buttons or menus that are also connected to the command will not flash to
indicate that they've been triggered.
*/
dontTriggerVisualFeedback = 1 << 5
};
/** A bitwise-OR of the values specified in the CommandFlags enum.
You can use the setInfo() method to quickly set this and some of the command's
other properties.
*/
int flags;
};
} // namespace juce

View File

@ -1,322 +1,322 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
ApplicationCommandManager::ApplicationCommandManager()
{
keyMappings.reset (new KeyPressMappingSet (*this));
Desktop::getInstance().addFocusChangeListener (this);
}
ApplicationCommandManager::~ApplicationCommandManager()
{
Desktop::getInstance().removeFocusChangeListener (this);
keyMappings.reset();
}
//==============================================================================
void ApplicationCommandManager::clearCommands()
{
commands.clear();
keyMappings->clearAllKeyPresses();
triggerAsyncUpdate();
}
void ApplicationCommandManager::registerCommand (const ApplicationCommandInfo& newCommand)
{
// zero isn't a valid command ID!
jassert (newCommand.commandID != 0);
// the name isn't optional!
jassert (newCommand.shortName.isNotEmpty());
if (auto* command = getMutableCommandForID (newCommand.commandID))
{
// Trying to re-register the same command ID with different parameters can often indicate a typo.
// This assertion is here because I've found it useful catching some mistakes, but it may also cause
// false alarms if you're deliberately updating some flags for a command.
jassert (newCommand.shortName == getCommandForID (newCommand.commandID)->shortName
&& newCommand.categoryName == getCommandForID (newCommand.commandID)->categoryName
&& newCommand.defaultKeypresses == getCommandForID (newCommand.commandID)->defaultKeypresses
&& (newCommand.flags & (ApplicationCommandInfo::wantsKeyUpDownCallbacks | ApplicationCommandInfo::hiddenFromKeyEditor | ApplicationCommandInfo::readOnlyInKeyEditor))
== (getCommandForID (newCommand.commandID)->flags & (ApplicationCommandInfo::wantsKeyUpDownCallbacks | ApplicationCommandInfo::hiddenFromKeyEditor | ApplicationCommandInfo::readOnlyInKeyEditor)));
*command = newCommand;
}
else
{
auto* newInfo = new ApplicationCommandInfo (newCommand);
newInfo->flags &= ~ApplicationCommandInfo::isTicked;
commands.add (newInfo);
keyMappings->resetToDefaultMapping (newCommand.commandID);
triggerAsyncUpdate();
}
}
void ApplicationCommandManager::registerAllCommandsForTarget (ApplicationCommandTarget* target)
{
if (target != nullptr)
{
Array<CommandID> commandIDs;
target->getAllCommands (commandIDs);
for (int i = 0; i < commandIDs.size(); ++i)
{
ApplicationCommandInfo info (commandIDs.getUnchecked(i));
target->getCommandInfo (info.commandID, info);
registerCommand (info);
}
}
}
void ApplicationCommandManager::removeCommand (const CommandID commandID)
{
for (int i = commands.size(); --i >= 0;)
{
if (commands.getUnchecked (i)->commandID == commandID)
{
commands.remove (i);
triggerAsyncUpdate();
const Array<KeyPress> keys (keyMappings->getKeyPressesAssignedToCommand (commandID));
for (int j = keys.size(); --j >= 0;)
keyMappings->removeKeyPress (keys.getReference (j));
}
}
}
void ApplicationCommandManager::commandStatusChanged()
{
triggerAsyncUpdate();
}
//==============================================================================
ApplicationCommandInfo* ApplicationCommandManager::getMutableCommandForID (CommandID commandID) const noexcept
{
for (int i = commands.size(); --i >= 0;)
if (commands.getUnchecked(i)->commandID == commandID)
return commands.getUnchecked(i);
return nullptr;
}
const ApplicationCommandInfo* ApplicationCommandManager::getCommandForID (CommandID commandID) const noexcept
{
return getMutableCommandForID (commandID);
}
String ApplicationCommandManager::getNameOfCommand (CommandID commandID) const noexcept
{
if (auto* ci = getCommandForID (commandID))
return ci->shortName;
return {};
}
String ApplicationCommandManager::getDescriptionOfCommand (CommandID commandID) const noexcept
{
if (auto* ci = getCommandForID (commandID))
return ci->description.isNotEmpty() ? ci->description
: ci->shortName;
return {};
}
StringArray ApplicationCommandManager::getCommandCategories() const
{
StringArray s;
for (int i = 0; i < commands.size(); ++i)
s.addIfNotAlreadyThere (commands.getUnchecked(i)->categoryName, false);
return s;
}
Array<CommandID> ApplicationCommandManager::getCommandsInCategory (const String& categoryName) const
{
Array<CommandID> results;
for (int i = 0; i < commands.size(); ++i)
if (commands.getUnchecked(i)->categoryName == categoryName)
results.add (commands.getUnchecked(i)->commandID);
return results;
}
//==============================================================================
bool ApplicationCommandManager::invokeDirectly (CommandID commandID, bool asynchronously)
{
ApplicationCommandTarget::InvocationInfo info (commandID);
info.invocationMethod = ApplicationCommandTarget::InvocationInfo::direct;
return invoke (info, asynchronously);
}
bool ApplicationCommandManager::invoke (const ApplicationCommandTarget::InvocationInfo& inf, bool asynchronously)
{
// This call isn't thread-safe for use from a non-UI thread without locking the message
// manager first..
JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
bool ok = false;
ApplicationCommandInfo commandInfo (0);
if (auto* target = getTargetForCommand (inf.commandID, commandInfo))
{
ApplicationCommandTarget::InvocationInfo info (inf);
info.commandFlags = commandInfo.flags;
sendListenerInvokeCallback (info);
ok = target->invoke (info, asynchronously);
commandStatusChanged();
}
return ok;
}
//==============================================================================
ApplicationCommandTarget* ApplicationCommandManager::getFirstCommandTarget (CommandID)
{
return firstTarget != nullptr ? firstTarget
: findDefaultComponentTarget();
}
void ApplicationCommandManager::setFirstCommandTarget (ApplicationCommandTarget* newTarget) noexcept
{
firstTarget = newTarget;
}
ApplicationCommandTarget* ApplicationCommandManager::getTargetForCommand (CommandID commandID,
ApplicationCommandInfo& upToDateInfo)
{
auto* target = getFirstCommandTarget (commandID);
if (target == nullptr)
target = JUCEApplication::getInstance();
if (target != nullptr)
target = target->getTargetForCommand (commandID);
if (target != nullptr)
{
upToDateInfo.commandID = commandID;
target->getCommandInfo (commandID, upToDateInfo);
}
return target;
}
//==============================================================================
ApplicationCommandTarget* ApplicationCommandManager::findTargetForComponent (Component* c)
{
auto* target = dynamic_cast<ApplicationCommandTarget*> (c);
if (target == nullptr && c != nullptr)
target = c->findParentComponentOfClass<ApplicationCommandTarget>();
return target;
}
ApplicationCommandTarget* ApplicationCommandManager::findDefaultComponentTarget()
{
auto* c = Component::getCurrentlyFocusedComponent();
if (c == nullptr)
{
if (auto* activeWindow = TopLevelWindow::getActiveTopLevelWindow())
{
if (auto* peer = activeWindow->getPeer())
{
c = peer->getLastFocusedSubcomponent();
if (c == nullptr)
c = activeWindow;
}
}
}
if (c == nullptr)
{
auto& desktop = Desktop::getInstance();
// getting a bit desperate now: try all desktop comps..
for (int i = desktop.getNumComponents(); --i >= 0;)
if (auto* component = desktop.getComponent (i))
if (isForegroundOrEmbeddedProcess (component))
if (auto* peer = component->getPeer())
if (auto* target = findTargetForComponent (peer->getLastFocusedSubcomponent()))
return target;
}
if (c != nullptr)
{
// if we're focused on a ResizableWindow, chances are that it's the content
// component that really should get the event. And if not, the event will
// still be passed up to the top level window anyway, so let's send it to the
// content comp.
if (auto* resizableWindow = dynamic_cast<ResizableWindow*> (c))
if (auto* content = resizableWindow->getContentComponent())
c = content;
if (auto* target = findTargetForComponent (c))
return target;
}
return JUCEApplication::getInstance();
}
//==============================================================================
void ApplicationCommandManager::addListener (ApplicationCommandManagerListener* listener)
{
listeners.add (listener);
}
void ApplicationCommandManager::removeListener (ApplicationCommandManagerListener* listener)
{
listeners.remove (listener);
}
void ApplicationCommandManager::sendListenerInvokeCallback (const ApplicationCommandTarget::InvocationInfo& info)
{
listeners.call ([&] (ApplicationCommandManagerListener& l) { l.applicationCommandInvoked (info); });
}
void ApplicationCommandManager::handleAsyncUpdate()
{
listeners.call ([] (ApplicationCommandManagerListener& l) { l.applicationCommandListChanged(); });
}
void ApplicationCommandManager::globalFocusChanged (Component*)
{
commandStatusChanged();
}
} // namespace juce
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
ApplicationCommandManager::ApplicationCommandManager()
{
keyMappings.reset (new KeyPressMappingSet (*this));
Desktop::getInstance().addFocusChangeListener (this);
}
ApplicationCommandManager::~ApplicationCommandManager()
{
Desktop::getInstance().removeFocusChangeListener (this);
keyMappings.reset();
}
//==============================================================================
void ApplicationCommandManager::clearCommands()
{
commands.clear();
keyMappings->clearAllKeyPresses();
triggerAsyncUpdate();
}
void ApplicationCommandManager::registerCommand (const ApplicationCommandInfo& newCommand)
{
// zero isn't a valid command ID!
jassert (newCommand.commandID != 0);
// the name isn't optional!
jassert (newCommand.shortName.isNotEmpty());
if (auto* command = getMutableCommandForID (newCommand.commandID))
{
// Trying to re-register the same command ID with different parameters can often indicate a typo.
// This assertion is here because I've found it useful catching some mistakes, but it may also cause
// false alarms if you're deliberately updating some flags for a command.
jassert (newCommand.shortName == getCommandForID (newCommand.commandID)->shortName
&& newCommand.categoryName == getCommandForID (newCommand.commandID)->categoryName
&& newCommand.defaultKeypresses == getCommandForID (newCommand.commandID)->defaultKeypresses
&& (newCommand.flags & (ApplicationCommandInfo::wantsKeyUpDownCallbacks | ApplicationCommandInfo::hiddenFromKeyEditor | ApplicationCommandInfo::readOnlyInKeyEditor))
== (getCommandForID (newCommand.commandID)->flags & (ApplicationCommandInfo::wantsKeyUpDownCallbacks | ApplicationCommandInfo::hiddenFromKeyEditor | ApplicationCommandInfo::readOnlyInKeyEditor)));
*command = newCommand;
}
else
{
auto* newInfo = new ApplicationCommandInfo (newCommand);
newInfo->flags &= ~ApplicationCommandInfo::isTicked;
commands.add (newInfo);
keyMappings->resetToDefaultMapping (newCommand.commandID);
triggerAsyncUpdate();
}
}
void ApplicationCommandManager::registerAllCommandsForTarget (ApplicationCommandTarget* target)
{
if (target != nullptr)
{
Array<CommandID> commandIDs;
target->getAllCommands (commandIDs);
for (int i = 0; i < commandIDs.size(); ++i)
{
ApplicationCommandInfo info (commandIDs.getUnchecked(i));
target->getCommandInfo (info.commandID, info);
registerCommand (info);
}
}
}
void ApplicationCommandManager::removeCommand (const CommandID commandID)
{
for (int i = commands.size(); --i >= 0;)
{
if (commands.getUnchecked (i)->commandID == commandID)
{
commands.remove (i);
triggerAsyncUpdate();
const Array<KeyPress> keys (keyMappings->getKeyPressesAssignedToCommand (commandID));
for (int j = keys.size(); --j >= 0;)
keyMappings->removeKeyPress (keys.getReference (j));
}
}
}
void ApplicationCommandManager::commandStatusChanged()
{
triggerAsyncUpdate();
}
//==============================================================================
ApplicationCommandInfo* ApplicationCommandManager::getMutableCommandForID (CommandID commandID) const noexcept
{
for (int i = commands.size(); --i >= 0;)
if (commands.getUnchecked(i)->commandID == commandID)
return commands.getUnchecked(i);
return nullptr;
}
const ApplicationCommandInfo* ApplicationCommandManager::getCommandForID (CommandID commandID) const noexcept
{
return getMutableCommandForID (commandID);
}
String ApplicationCommandManager::getNameOfCommand (CommandID commandID) const noexcept
{
if (auto* ci = getCommandForID (commandID))
return ci->shortName;
return {};
}
String ApplicationCommandManager::getDescriptionOfCommand (CommandID commandID) const noexcept
{
if (auto* ci = getCommandForID (commandID))
return ci->description.isNotEmpty() ? ci->description
: ci->shortName;
return {};
}
StringArray ApplicationCommandManager::getCommandCategories() const
{
StringArray s;
for (int i = 0; i < commands.size(); ++i)
s.addIfNotAlreadyThere (commands.getUnchecked(i)->categoryName, false);
return s;
}
Array<CommandID> ApplicationCommandManager::getCommandsInCategory (const String& categoryName) const
{
Array<CommandID> results;
for (int i = 0; i < commands.size(); ++i)
if (commands.getUnchecked(i)->categoryName == categoryName)
results.add (commands.getUnchecked(i)->commandID);
return results;
}
//==============================================================================
bool ApplicationCommandManager::invokeDirectly (CommandID commandID, bool asynchronously)
{
ApplicationCommandTarget::InvocationInfo info (commandID);
info.invocationMethod = ApplicationCommandTarget::InvocationInfo::direct;
return invoke (info, asynchronously);
}
bool ApplicationCommandManager::invoke (const ApplicationCommandTarget::InvocationInfo& inf, bool asynchronously)
{
// This call isn't thread-safe for use from a non-UI thread without locking the message
// manager first..
JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
bool ok = false;
ApplicationCommandInfo commandInfo (0);
if (auto* target = getTargetForCommand (inf.commandID, commandInfo))
{
ApplicationCommandTarget::InvocationInfo info (inf);
info.commandFlags = commandInfo.flags;
sendListenerInvokeCallback (info);
ok = target->invoke (info, asynchronously);
commandStatusChanged();
}
return ok;
}
//==============================================================================
ApplicationCommandTarget* ApplicationCommandManager::getFirstCommandTarget (CommandID)
{
return firstTarget != nullptr ? firstTarget
: findDefaultComponentTarget();
}
void ApplicationCommandManager::setFirstCommandTarget (ApplicationCommandTarget* newTarget) noexcept
{
firstTarget = newTarget;
}
ApplicationCommandTarget* ApplicationCommandManager::getTargetForCommand (CommandID commandID,
ApplicationCommandInfo& upToDateInfo)
{
auto* target = getFirstCommandTarget (commandID);
if (target == nullptr)
target = JUCEApplication::getInstance();
if (target != nullptr)
target = target->getTargetForCommand (commandID);
if (target != nullptr)
{
upToDateInfo.commandID = commandID;
target->getCommandInfo (commandID, upToDateInfo);
}
return target;
}
//==============================================================================
ApplicationCommandTarget* ApplicationCommandManager::findTargetForComponent (Component* c)
{
auto* target = dynamic_cast<ApplicationCommandTarget*> (c);
if (target == nullptr && c != nullptr)
target = c->findParentComponentOfClass<ApplicationCommandTarget>();
return target;
}
ApplicationCommandTarget* ApplicationCommandManager::findDefaultComponentTarget()
{
auto* c = Component::getCurrentlyFocusedComponent();
if (c == nullptr)
{
if (auto* activeWindow = TopLevelWindow::getActiveTopLevelWindow())
{
if (auto* peer = activeWindow->getPeer())
{
c = peer->getLastFocusedSubcomponent();
if (c == nullptr)
c = activeWindow;
}
}
}
if (c == nullptr)
{
auto& desktop = Desktop::getInstance();
// getting a bit desperate now: try all desktop comps..
for (int i = desktop.getNumComponents(); --i >= 0;)
if (auto* component = desktop.getComponent (i))
if (isForegroundOrEmbeddedProcess (component))
if (auto* peer = component->getPeer())
if (auto* target = findTargetForComponent (peer->getLastFocusedSubcomponent()))
return target;
}
if (c != nullptr)
{
// if we're focused on a ResizableWindow, chances are that it's the content
// component that really should get the event. And if not, the event will
// still be passed up to the top level window anyway, so let's send it to the
// content comp.
if (auto* resizableWindow = dynamic_cast<ResizableWindow*> (c))
if (auto* content = resizableWindow->getContentComponent())
c = content;
if (auto* target = findTargetForComponent (c))
return target;
}
return JUCEApplication::getInstance();
}
//==============================================================================
void ApplicationCommandManager::addListener (ApplicationCommandManagerListener* listener)
{
listeners.add (listener);
}
void ApplicationCommandManager::removeListener (ApplicationCommandManagerListener* listener)
{
listeners.remove (listener);
}
void ApplicationCommandManager::sendListenerInvokeCallback (const ApplicationCommandTarget::InvocationInfo& info)
{
listeners.call ([&] (ApplicationCommandManagerListener& l) { l.applicationCommandInvoked (info); });
}
void ApplicationCommandManager::handleAsyncUpdate()
{
listeners.call ([] (ApplicationCommandManagerListener& l) { l.applicationCommandListChanged(); });
}
void ApplicationCommandManager::globalFocusChanged (Component*)
{
commandStatusChanged();
}
} // namespace juce

View File

@ -1,349 +1,349 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
One of these objects holds a list of all the commands your app can perform,
and despatches these commands when needed.
Application commands are a good way to trigger actions in your app, e.g. "Quit",
"Copy", "Paste", etc. Menus, buttons and keypresses can all be given commands
to invoke automatically, which means you don't have to handle the result of a menu
or button click manually. Commands are despatched to ApplicationCommandTarget objects
which can choose which events they want to handle.
This architecture also allows for nested ApplicationCommandTargets, so that for example
you could have two different objects, one inside the other, both of which can respond to
a "delete" command. Depending on which one has focus, the command will be sent to the
appropriate place, regardless of whether it was triggered by a menu, keypress or some other
method.
To set up your app to use commands, you'll need to do the following:
- Create a global ApplicationCommandManager to hold the list of all possible
commands. (This will also manage a set of key-mappings for them).
- Make some of your UI components (or other objects) inherit from ApplicationCommandTarget.
This allows the object to provide a list of commands that it can perform, and
to handle them.
- Register each type of command using ApplicationCommandManager::registerAllCommandsForTarget(),
or ApplicationCommandManager::registerCommand().
- If you want key-presses to trigger your commands, use the ApplicationCommandManager::getKeyMappings()
method to access the key-mapper object, which you will need to register as a key-listener
in whatever top-level component you're using. See the KeyPressMappingSet class for more help
about setting this up.
- Use methods such as PopupMenu::addCommandItem() or Button::setCommandToTrigger() to
cause these commands to be invoked automatically.
- Commands can be invoked directly by your code using ApplicationCommandManager::invokeDirectly().
When a command is invoked, the ApplicationCommandManager will try to choose the best
ApplicationCommandTarget to receive the specified command. To do this it will use the
current keyboard focus to see which component might be interested, and will search the
component hierarchy for those that also implement the ApplicationCommandTarget interface.
If an ApplicationCommandTarget isn't interested in the command that is being invoked, then
the next one in line will be tried (see the ApplicationCommandTarget::getNextCommandTarget()
method), and so on until ApplicationCommandTarget::getNextCommandTarget() returns nullptr.
At this point if the command still hasn't been performed, it will be passed to the current
JUCEApplication object (which is itself an ApplicationCommandTarget).
To exert some custom control over which ApplicationCommandTarget is chosen to invoke a command,
you can override the ApplicationCommandManager::getFirstCommandTarget() method and choose
the object yourself.
@see ApplicationCommandTarget, ApplicationCommandInfo
@tags{GUI}
*/
class JUCE_API ApplicationCommandManager : private AsyncUpdater,
private FocusChangeListener
{
public:
//==============================================================================
/** Creates an ApplicationCommandManager.
Once created, you'll need to register all your app's commands with it, using
ApplicationCommandManager::registerAllCommandsForTarget() or
ApplicationCommandManager::registerCommand().
*/
ApplicationCommandManager();
/** Destructor.
Make sure that you don't delete this if pointers to it are still being used by
objects such as PopupMenus or Buttons.
*/
~ApplicationCommandManager() override;
//==============================================================================
/** Clears the current list of all commands.
Note that this will also clear the contents of the KeyPressMappingSet.
*/
void clearCommands();
/** Adds a command to the list of registered commands.
@see registerAllCommandsForTarget
*/
void registerCommand (const ApplicationCommandInfo& newCommand);
/** Adds all the commands that this target publishes to the manager's list.
This will use ApplicationCommandTarget::getAllCommands() and ApplicationCommandTarget::getCommandInfo()
to get details about all the commands that this target can do, and will call
registerCommand() to add each one to the manger's list.
@see registerCommand
*/
void registerAllCommandsForTarget (ApplicationCommandTarget* target);
/** Removes the command with a specified ID.
Note that this will also remove any key mappings that are mapped to the command.
*/
void removeCommand (CommandID commandID);
/** This should be called to tell the manager that one of its registered commands may have changed
its active status.
Because the command manager only finds out whether a command is active or inactive by querying
the current ApplicationCommandTarget, this is used to tell it that things may have changed. It
allows things like buttons to update their enablement, etc.
This method will cause an asynchronous call to ApplicationCommandManagerListener::applicationCommandListChanged()
for any registered listeners.
*/
void commandStatusChanged();
//==============================================================================
/** Returns the number of commands that have been registered.
@see registerCommand
*/
int getNumCommands() const noexcept { return commands.size(); }
/** Returns the details about one of the registered commands.
The index is between 0 and (getNumCommands() - 1).
*/
const ApplicationCommandInfo* getCommandForIndex (int index) const noexcept { return commands [index]; }
/** Returns the details about a given command ID.
This will search the list of registered commands for one with the given command
ID number, and return its associated info. If no matching command is found, this
will return nullptr.
*/
const ApplicationCommandInfo* getCommandForID (CommandID commandID) const noexcept;
/** Returns the name field for a command.
An empty string is returned if no command with this ID has been registered.
@see getDescriptionOfCommand
*/
String getNameOfCommand (CommandID commandID) const noexcept;
/** Returns the description field for a command.
An empty string is returned if no command with this ID has been registered. If the
command has no description, this will return its short name field instead.
@see getNameOfCommand
*/
String getDescriptionOfCommand (CommandID commandID) const noexcept;
/** Returns the list of categories.
This will go through all registered commands, and return a list of all the distinct
categoryName values from their ApplicationCommandInfo structure.
@see getCommandsInCategory()
*/
StringArray getCommandCategories() const;
/** Returns a list of all the command UIDs in a particular category.
@see getCommandCategories()
*/
Array<CommandID> getCommandsInCategory (const String& categoryName) const;
//==============================================================================
/** Returns the manager's internal set of key mappings.
This object can be used to edit the keypresses. To actually link this object up
to invoke commands when a key is pressed, see the comments for the KeyPressMappingSet
class.
@see KeyPressMappingSet
*/
KeyPressMappingSet* getKeyMappings() const noexcept { return keyMappings.get(); }
//==============================================================================
/** Invokes the given command directly, sending it to the default target.
This is just an easy way to call invoke() without having to fill out the InvocationInfo
structure.
*/
bool invokeDirectly (CommandID commandID, bool asynchronously);
/** Sends a command to the default target.
This will choose a target using getFirstCommandTarget(), and send the specified command
to it using the ApplicationCommandTarget::invoke() method. This means that if the
first target can't handle the command, it will be passed on to targets further down the
chain (see ApplicationCommandTarget::invoke() for more info).
@param invocationInfo this must be correctly filled-in, describing the context for
the invocation.
@param asynchronously if false, the command will be performed before this method returns.
If true, a message will be posted so that the command will be performed
later on the message thread, and this method will return immediately.
@see ApplicationCommandTarget::invoke
*/
bool invoke (const ApplicationCommandTarget::InvocationInfo& invocationInfo,
bool asynchronously);
//==============================================================================
/** Chooses the ApplicationCommandTarget to which a command should be sent.
Whenever the manager needs to know which target a command should be sent to, it calls
this method to determine the first one to try.
By default, this method will return the target that was set by calling setFirstCommandTarget().
If no target is set, it will return the result of findDefaultComponentTarget().
If you need to make sure all commands go via your own custom target, then you can
either use setFirstCommandTarget() to specify a single target, or override this method
if you need more complex logic to choose one.
It may return nullptr if no targets are available.
@see getTargetForCommand, invoke, invokeDirectly
*/
virtual ApplicationCommandTarget* getFirstCommandTarget (CommandID commandID);
/** Sets a target to be returned by getFirstCommandTarget().
If this is set to nullptr, then getFirstCommandTarget() will by default return the
result of findDefaultComponentTarget().
If you use this to set a target, make sure you call setFirstCommandTarget(nullptr)
before deleting the target object.
*/
void setFirstCommandTarget (ApplicationCommandTarget* newTarget) noexcept;
/** Tries to find the best target to use to perform a given command.
This will call getFirstCommandTarget() to find the preferred target, and will
check whether that target can handle the given command. If it can't, then it'll use
ApplicationCommandTarget::getNextCommandTarget() to find the next one to try, and
so on until no more are available.
If no targets are found that can perform the command, this method will return nullptr.
If a target is found, then it will get the target to fill-in the upToDateInfo
structure with the latest info about that command, so that the caller can see
whether the command is disabled, ticked, etc.
*/
ApplicationCommandTarget* getTargetForCommand (CommandID commandID,
ApplicationCommandInfo& upToDateInfo);
//==============================================================================
/** Registers a listener that will be called when various events occur. */
void addListener (ApplicationCommandManagerListener* listener);
/** Deregisters a previously-added listener. */
void removeListener (ApplicationCommandManagerListener* listener);
//==============================================================================
/** Looks for a suitable command target based on which Components have the keyboard focus.
This is used by the default implementation of ApplicationCommandTarget::getFirstCommandTarget(),
but is exposed here in case it's useful.
It tries to pick the best ApplicationCommandTarget by looking at focused components, top level
windows, etc., and using the findTargetForComponent() method.
*/
static ApplicationCommandTarget* findDefaultComponentTarget();
/** Examines this component and all its parents in turn, looking for the first one
which is an ApplicationCommandTarget.
Returns the first ApplicationCommandTarget that it finds, or nullptr if none of them
implement that class.
*/
static ApplicationCommandTarget* findTargetForComponent (Component*);
private:
//==============================================================================
OwnedArray<ApplicationCommandInfo> commands;
ListenerList<ApplicationCommandManagerListener> listeners;
std::unique_ptr<KeyPressMappingSet> keyMappings;
ApplicationCommandTarget* firstTarget = nullptr;
void sendListenerInvokeCallback (const ApplicationCommandTarget::InvocationInfo&);
void handleAsyncUpdate() override;
void globalFocusChanged (Component*) override;
ApplicationCommandInfo* getMutableCommandForID (CommandID) const noexcept;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ApplicationCommandManager)
};
//==============================================================================
/**
A listener that receives callbacks from an ApplicationCommandManager when
commands are invoked or the command list is changed.
@see ApplicationCommandManager::addListener, ApplicationCommandManager::removeListener
@tags{GUI}
*/
class JUCE_API ApplicationCommandManagerListener
{
public:
//==============================================================================
/** Destructor. */
virtual ~ApplicationCommandManagerListener() = default;
/** Called when an app command is about to be invoked. */
virtual void applicationCommandInvoked (const ApplicationCommandTarget::InvocationInfo&) = 0;
/** Called when commands are registered or deregistered from the
command manager, or when commands are made active or inactive.
Note that if you're using this to watch for changes to whether a command is disabled,
you'll need to make sure that ApplicationCommandManager::commandStatusChanged() is called
whenever the status of your command might have changed.
*/
virtual void applicationCommandListChanged() = 0;
};
} // namespace juce
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
One of these objects holds a list of all the commands your app can perform,
and despatches these commands when needed.
Application commands are a good way to trigger actions in your app, e.g. "Quit",
"Copy", "Paste", etc. Menus, buttons and keypresses can all be given commands
to invoke automatically, which means you don't have to handle the result of a menu
or button click manually. Commands are despatched to ApplicationCommandTarget objects
which can choose which events they want to handle.
This architecture also allows for nested ApplicationCommandTargets, so that for example
you could have two different objects, one inside the other, both of which can respond to
a "delete" command. Depending on which one has focus, the command will be sent to the
appropriate place, regardless of whether it was triggered by a menu, keypress or some other
method.
To set up your app to use commands, you'll need to do the following:
- Create a global ApplicationCommandManager to hold the list of all possible
commands. (This will also manage a set of key-mappings for them).
- Make some of your UI components (or other objects) inherit from ApplicationCommandTarget.
This allows the object to provide a list of commands that it can perform, and
to handle them.
- Register each type of command using ApplicationCommandManager::registerAllCommandsForTarget(),
or ApplicationCommandManager::registerCommand().
- If you want key-presses to trigger your commands, use the ApplicationCommandManager::getKeyMappings()
method to access the key-mapper object, which you will need to register as a key-listener
in whatever top-level component you're using. See the KeyPressMappingSet class for more help
about setting this up.
- Use methods such as PopupMenu::addCommandItem() or Button::setCommandToTrigger() to
cause these commands to be invoked automatically.
- Commands can be invoked directly by your code using ApplicationCommandManager::invokeDirectly().
When a command is invoked, the ApplicationCommandManager will try to choose the best
ApplicationCommandTarget to receive the specified command. To do this it will use the
current keyboard focus to see which component might be interested, and will search the
component hierarchy for those that also implement the ApplicationCommandTarget interface.
If an ApplicationCommandTarget isn't interested in the command that is being invoked, then
the next one in line will be tried (see the ApplicationCommandTarget::getNextCommandTarget()
method), and so on until ApplicationCommandTarget::getNextCommandTarget() returns nullptr.
At this point if the command still hasn't been performed, it will be passed to the current
JUCEApplication object (which is itself an ApplicationCommandTarget).
To exert some custom control over which ApplicationCommandTarget is chosen to invoke a command,
you can override the ApplicationCommandManager::getFirstCommandTarget() method and choose
the object yourself.
@see ApplicationCommandTarget, ApplicationCommandInfo
@tags{GUI}
*/
class JUCE_API ApplicationCommandManager : private AsyncUpdater,
private FocusChangeListener
{
public:
//==============================================================================
/** Creates an ApplicationCommandManager.
Once created, you'll need to register all your app's commands with it, using
ApplicationCommandManager::registerAllCommandsForTarget() or
ApplicationCommandManager::registerCommand().
*/
ApplicationCommandManager();
/** Destructor.
Make sure that you don't delete this if pointers to it are still being used by
objects such as PopupMenus or Buttons.
*/
~ApplicationCommandManager() override;
//==============================================================================
/** Clears the current list of all commands.
Note that this will also clear the contents of the KeyPressMappingSet.
*/
void clearCommands();
/** Adds a command to the list of registered commands.
@see registerAllCommandsForTarget
*/
void registerCommand (const ApplicationCommandInfo& newCommand);
/** Adds all the commands that this target publishes to the manager's list.
This will use ApplicationCommandTarget::getAllCommands() and ApplicationCommandTarget::getCommandInfo()
to get details about all the commands that this target can do, and will call
registerCommand() to add each one to the manger's list.
@see registerCommand
*/
void registerAllCommandsForTarget (ApplicationCommandTarget* target);
/** Removes the command with a specified ID.
Note that this will also remove any key mappings that are mapped to the command.
*/
void removeCommand (CommandID commandID);
/** This should be called to tell the manager that one of its registered commands may have changed
its active status.
Because the command manager only finds out whether a command is active or inactive by querying
the current ApplicationCommandTarget, this is used to tell it that things may have changed. It
allows things like buttons to update their enablement, etc.
This method will cause an asynchronous call to ApplicationCommandManagerListener::applicationCommandListChanged()
for any registered listeners.
*/
void commandStatusChanged();
//==============================================================================
/** Returns the number of commands that have been registered.
@see registerCommand
*/
int getNumCommands() const noexcept { return commands.size(); }
/** Returns the details about one of the registered commands.
The index is between 0 and (getNumCommands() - 1).
*/
const ApplicationCommandInfo* getCommandForIndex (int index) const noexcept { return commands [index]; }
/** Returns the details about a given command ID.
This will search the list of registered commands for one with the given command
ID number, and return its associated info. If no matching command is found, this
will return nullptr.
*/
const ApplicationCommandInfo* getCommandForID (CommandID commandID) const noexcept;
/** Returns the name field for a command.
An empty string is returned if no command with this ID has been registered.
@see getDescriptionOfCommand
*/
String getNameOfCommand (CommandID commandID) const noexcept;
/** Returns the description field for a command.
An empty string is returned if no command with this ID has been registered. If the
command has no description, this will return its short name field instead.
@see getNameOfCommand
*/
String getDescriptionOfCommand (CommandID commandID) const noexcept;
/** Returns the list of categories.
This will go through all registered commands, and return a list of all the distinct
categoryName values from their ApplicationCommandInfo structure.
@see getCommandsInCategory()
*/
StringArray getCommandCategories() const;
/** Returns a list of all the command UIDs in a particular category.
@see getCommandCategories()
*/
Array<CommandID> getCommandsInCategory (const String& categoryName) const;
//==============================================================================
/** Returns the manager's internal set of key mappings.
This object can be used to edit the keypresses. To actually link this object up
to invoke commands when a key is pressed, see the comments for the KeyPressMappingSet
class.
@see KeyPressMappingSet
*/
KeyPressMappingSet* getKeyMappings() const noexcept { return keyMappings.get(); }
//==============================================================================
/** Invokes the given command directly, sending it to the default target.
This is just an easy way to call invoke() without having to fill out the InvocationInfo
structure.
*/
bool invokeDirectly (CommandID commandID, bool asynchronously);
/** Sends a command to the default target.
This will choose a target using getFirstCommandTarget(), and send the specified command
to it using the ApplicationCommandTarget::invoke() method. This means that if the
first target can't handle the command, it will be passed on to targets further down the
chain (see ApplicationCommandTarget::invoke() for more info).
@param invocationInfo this must be correctly filled-in, describing the context for
the invocation.
@param asynchronously if false, the command will be performed before this method returns.
If true, a message will be posted so that the command will be performed
later on the message thread, and this method will return immediately.
@see ApplicationCommandTarget::invoke
*/
bool invoke (const ApplicationCommandTarget::InvocationInfo& invocationInfo,
bool asynchronously);
//==============================================================================
/** Chooses the ApplicationCommandTarget to which a command should be sent.
Whenever the manager needs to know which target a command should be sent to, it calls
this method to determine the first one to try.
By default, this method will return the target that was set by calling setFirstCommandTarget().
If no target is set, it will return the result of findDefaultComponentTarget().
If you need to make sure all commands go via your own custom target, then you can
either use setFirstCommandTarget() to specify a single target, or override this method
if you need more complex logic to choose one.
It may return nullptr if no targets are available.
@see getTargetForCommand, invoke, invokeDirectly
*/
virtual ApplicationCommandTarget* getFirstCommandTarget (CommandID commandID);
/** Sets a target to be returned by getFirstCommandTarget().
If this is set to nullptr, then getFirstCommandTarget() will by default return the
result of findDefaultComponentTarget().
If you use this to set a target, make sure you call setFirstCommandTarget(nullptr)
before deleting the target object.
*/
void setFirstCommandTarget (ApplicationCommandTarget* newTarget) noexcept;
/** Tries to find the best target to use to perform a given command.
This will call getFirstCommandTarget() to find the preferred target, and will
check whether that target can handle the given command. If it can't, then it'll use
ApplicationCommandTarget::getNextCommandTarget() to find the next one to try, and
so on until no more are available.
If no targets are found that can perform the command, this method will return nullptr.
If a target is found, then it will get the target to fill-in the upToDateInfo
structure with the latest info about that command, so that the caller can see
whether the command is disabled, ticked, etc.
*/
ApplicationCommandTarget* getTargetForCommand (CommandID commandID,
ApplicationCommandInfo& upToDateInfo);
//==============================================================================
/** Registers a listener that will be called when various events occur. */
void addListener (ApplicationCommandManagerListener* listener);
/** Deregisters a previously-added listener. */
void removeListener (ApplicationCommandManagerListener* listener);
//==============================================================================
/** Looks for a suitable command target based on which Components have the keyboard focus.
This is used by the default implementation of ApplicationCommandTarget::getFirstCommandTarget(),
but is exposed here in case it's useful.
It tries to pick the best ApplicationCommandTarget by looking at focused components, top level
windows, etc., and using the findTargetForComponent() method.
*/
static ApplicationCommandTarget* findDefaultComponentTarget();
/** Examines this component and all its parents in turn, looking for the first one
which is an ApplicationCommandTarget.
Returns the first ApplicationCommandTarget that it finds, or nullptr if none of them
implement that class.
*/
static ApplicationCommandTarget* findTargetForComponent (Component*);
private:
//==============================================================================
OwnedArray<ApplicationCommandInfo> commands;
ListenerList<ApplicationCommandManagerListener> listeners;
std::unique_ptr<KeyPressMappingSet> keyMappings;
ApplicationCommandTarget* firstTarget = nullptr;
void sendListenerInvokeCallback (const ApplicationCommandTarget::InvocationInfo&);
void handleAsyncUpdate() override;
void globalFocusChanged (Component*) override;
ApplicationCommandInfo* getMutableCommandForID (CommandID) const noexcept;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ApplicationCommandManager)
};
//==============================================================================
/**
A listener that receives callbacks from an ApplicationCommandManager when
commands are invoked or the command list is changed.
@see ApplicationCommandManager::addListener, ApplicationCommandManager::removeListener
@tags{GUI}
*/
class JUCE_API ApplicationCommandManagerListener
{
public:
//==============================================================================
/** Destructor. */
virtual ~ApplicationCommandManagerListener() = default;
/** Called when an app command is about to be invoked. */
virtual void applicationCommandInvoked (const ApplicationCommandTarget::InvocationInfo&) = 0;
/** Called when commands are registered or deregistered from the
command manager, or when commands are made active or inactive.
Note that if you're using this to watch for changes to whether a command is disabled,
you'll need to make sure that ApplicationCommandManager::commandStatusChanged() is called
whenever the status of your command might have changed.
*/
virtual void applicationCommandListChanged() = 0;
};
} // namespace juce

View File

@ -1,186 +1,186 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
class ApplicationCommandTarget::CommandMessage : public MessageManager::MessageBase
{
public:
CommandMessage (ApplicationCommandTarget* const target, const InvocationInfo& inf)
: owner (target), info (inf)
{
}
void messageCallback() override
{
if (ApplicationCommandTarget* const target = owner)
target->tryToInvoke (info, false);
}
private:
WeakReference<ApplicationCommandTarget> owner;
const InvocationInfo info;
JUCE_DECLARE_NON_COPYABLE (CommandMessage)
};
//==============================================================================
ApplicationCommandTarget::ApplicationCommandTarget() {}
ApplicationCommandTarget::~ApplicationCommandTarget() {}
//==============================================================================
bool ApplicationCommandTarget::tryToInvoke (const InvocationInfo& info, const bool async)
{
if (isCommandActive (info.commandID))
{
if (async)
{
(new CommandMessage (this, info))->post();
return true;
}
if (perform (info))
return true;
// Hmm.. your target claimed that it could perform this command, but failed to do so.
// If it can't do it at the moment for some reason, it should clear the 'isActive' flag
// when it returns the command's info.
jassertfalse;
}
return false;
}
ApplicationCommandTarget* ApplicationCommandTarget::findFirstTargetParentComponent()
{
if (Component* const c = dynamic_cast<Component*> (this))
return c->findParentComponentOfClass<ApplicationCommandTarget>();
return nullptr;
}
ApplicationCommandTarget* ApplicationCommandTarget::getTargetForCommand (const CommandID commandID)
{
ApplicationCommandTarget* target = this;
int depth = 0;
while (target != nullptr)
{
Array<CommandID> commandIDs;
target->getAllCommands (commandIDs);
if (commandIDs.contains (commandID))
return target;
target = target->getNextCommandTarget();
++depth;
jassert (depth < 100); // could be a recursive command chain??
jassert (target != this); // definitely a recursive command chain!
if (depth > 100 || target == this)
break;
}
if (target == nullptr)
{
target = JUCEApplication::getInstance();
if (target != nullptr)
{
Array<CommandID> commandIDs;
target->getAllCommands (commandIDs);
if (commandIDs.contains (commandID))
return target;
}
}
return nullptr;
}
bool ApplicationCommandTarget::isCommandActive (const CommandID commandID)
{
ApplicationCommandInfo info (commandID);
info.flags = ApplicationCommandInfo::isDisabled;
getCommandInfo (commandID, info);
return (info.flags & ApplicationCommandInfo::isDisabled) == 0;
}
//==============================================================================
bool ApplicationCommandTarget::invoke (const InvocationInfo& info, const bool async)
{
ApplicationCommandTarget* target = this;
int depth = 0;
while (target != nullptr)
{
if (target->tryToInvoke (info, async))
return true;
target = target->getNextCommandTarget();
++depth;
jassert (depth < 100); // could be a recursive command chain??
jassert (target != this); // definitely a recursive command chain!
if (depth > 100 || target == this)
break;
}
if (target == nullptr)
{
target = JUCEApplication::getInstance();
if (target != nullptr)
return target->tryToInvoke (info, async);
}
return false;
}
bool ApplicationCommandTarget::invokeDirectly (const CommandID commandID, const bool asynchronously)
{
ApplicationCommandTarget::InvocationInfo info (commandID);
info.invocationMethod = ApplicationCommandTarget::InvocationInfo::direct;
return invoke (info, asynchronously);
}
//==============================================================================
ApplicationCommandTarget::InvocationInfo::InvocationInfo (const CommandID command)
: commandID (command),
commandFlags (0),
invocationMethod (direct),
originatingComponent (nullptr),
isKeyDown (false),
millisecsSinceKeyPressed (0)
{
}
} // namespace juce
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
class ApplicationCommandTarget::CommandMessage : public MessageManager::MessageBase
{
public:
CommandMessage (ApplicationCommandTarget* const target, const InvocationInfo& inf)
: owner (target), info (inf)
{
}
void messageCallback() override
{
if (ApplicationCommandTarget* const target = owner)
target->tryToInvoke (info, false);
}
private:
WeakReference<ApplicationCommandTarget> owner;
const InvocationInfo info;
JUCE_DECLARE_NON_COPYABLE (CommandMessage)
};
//==============================================================================
ApplicationCommandTarget::ApplicationCommandTarget() {}
ApplicationCommandTarget::~ApplicationCommandTarget() {}
//==============================================================================
bool ApplicationCommandTarget::tryToInvoke (const InvocationInfo& info, const bool async)
{
if (isCommandActive (info.commandID))
{
if (async)
{
(new CommandMessage (this, info))->post();
return true;
}
if (perform (info))
return true;
// Hmm.. your target claimed that it could perform this command, but failed to do so.
// If it can't do it at the moment for some reason, it should clear the 'isActive' flag
// when it returns the command's info.
jassertfalse;
}
return false;
}
ApplicationCommandTarget* ApplicationCommandTarget::findFirstTargetParentComponent()
{
if (Component* const c = dynamic_cast<Component*> (this))
return c->findParentComponentOfClass<ApplicationCommandTarget>();
return nullptr;
}
ApplicationCommandTarget* ApplicationCommandTarget::getTargetForCommand (const CommandID commandID)
{
ApplicationCommandTarget* target = this;
int depth = 0;
while (target != nullptr)
{
Array<CommandID> commandIDs;
target->getAllCommands (commandIDs);
if (commandIDs.contains (commandID))
return target;
target = target->getNextCommandTarget();
++depth;
jassert (depth < 100); // could be a recursive command chain??
jassert (target != this); // definitely a recursive command chain!
if (depth > 100 || target == this)
break;
}
if (target == nullptr)
{
target = JUCEApplication::getInstance();
if (target != nullptr)
{
Array<CommandID> commandIDs;
target->getAllCommands (commandIDs);
if (commandIDs.contains (commandID))
return target;
}
}
return nullptr;
}
bool ApplicationCommandTarget::isCommandActive (const CommandID commandID)
{
ApplicationCommandInfo info (commandID);
info.flags = ApplicationCommandInfo::isDisabled;
getCommandInfo (commandID, info);
return (info.flags & ApplicationCommandInfo::isDisabled) == 0;
}
//==============================================================================
bool ApplicationCommandTarget::invoke (const InvocationInfo& info, const bool async)
{
ApplicationCommandTarget* target = this;
int depth = 0;
while (target != nullptr)
{
if (target->tryToInvoke (info, async))
return true;
target = target->getNextCommandTarget();
++depth;
jassert (depth < 100); // could be a recursive command chain??
jassert (target != this); // definitely a recursive command chain!
if (depth > 100 || target == this)
break;
}
if (target == nullptr)
{
target = JUCEApplication::getInstance();
if (target != nullptr)
return target->tryToInvoke (info, async);
}
return false;
}
bool ApplicationCommandTarget::invokeDirectly (const CommandID commandID, const bool asynchronously)
{
ApplicationCommandTarget::InvocationInfo info (commandID);
info.invocationMethod = ApplicationCommandTarget::InvocationInfo::direct;
return invoke (info, asynchronously);
}
//==============================================================================
ApplicationCommandTarget::InvocationInfo::InvocationInfo (const CommandID command)
: commandID (command),
commandFlags (0),
invocationMethod (direct),
originatingComponent (nullptr),
isKeyDown (false),
millisecsSinceKeyPressed (0)
{
}
} // namespace juce

View File

@ -1,244 +1,244 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
A command target publishes a list of command IDs that it can perform.
An ApplicationCommandManager despatches commands to targets, which must be
able to provide information about what commands they can handle.
To create a target, you'll need to inherit from this class, implementing all of
its pure virtual methods.
For info about how a target is chosen to receive a command, see
ApplicationCommandManager::getFirstCommandTarget().
@see ApplicationCommandManager, ApplicationCommandInfo
@tags{GUI}
*/
class JUCE_API ApplicationCommandTarget
{
public:
//==============================================================================
/** Creates a command target. */
ApplicationCommandTarget();
/** Destructor. */
virtual ~ApplicationCommandTarget();
//==============================================================================
/**
Contains contextual details about the invocation of a command.
*/
struct JUCE_API InvocationInfo
{
//==============================================================================
InvocationInfo (const CommandID commandID);
//==============================================================================
/** The UID of the command that should be performed. */
CommandID commandID;
/** The command's flags.
See ApplicationCommandInfo for a description of these flag values.
*/
int commandFlags;
//==============================================================================
/** The types of context in which the command might be called. */
enum InvocationMethod
{
direct = 0, /**< The command is being invoked directly by a piece of code. */
fromKeyPress, /**< The command is being invoked by a key-press. */
fromMenu, /**< The command is being invoked by a menu selection. */
fromButton /**< The command is being invoked by a button click. */
};
/** The type of event that triggered this command. */
InvocationMethod invocationMethod;
//==============================================================================
/** If triggered by a keypress or menu, this will be the component that had the
keyboard focus at the time.
If triggered by a button, it may be set to that component, or it may be null.
*/
Component* originatingComponent;
//==============================================================================
/** The keypress that was used to invoke it.
Note that this will be an invalid keypress if the command was invoked
by some other means than a keyboard shortcut.
*/
KeyPress keyPress;
/** True if the callback is being invoked when the key is pressed,
false if the key is being released.
@see KeyPressMappingSet::addCommand()
*/
bool isKeyDown;
/** If the key is being released, this indicates how long it had been held
down for.
(Only relevant if isKeyDown is false.)
*/
int millisecsSinceKeyPressed;
};
//==============================================================================
/** This must return the next target to try after this one.
When a command is being sent, and the first target can't handle
that command, this method is used to determine the next target that should
be tried.
It may return nullptr if it doesn't know of another target.
If your target is a Component, you would usually use the findFirstTargetParentComponent()
method to return a parent component that might want to handle it.
@see invoke
*/
virtual ApplicationCommandTarget* getNextCommandTarget() = 0;
/** This must return a complete list of commands that this target can handle.
Your target should add all the command IDs that it handles to the array that is
passed-in.
*/
virtual void getAllCommands (Array<CommandID>& commands) = 0;
/** This must provide details about one of the commands that this target can perform.
This will be called with one of the command IDs that the target provided in its
getAllCommands() methods.
It should fill-in all appropriate fields of the ApplicationCommandInfo structure with
suitable information about the command. (The commandID field will already have been filled-in
by the caller).
The easiest way to set the info is using the ApplicationCommandInfo::setInfo() method to
set all the fields at once.
If the command is currently inactive for some reason, this method must use
ApplicationCommandInfo::setActive() to make that clear, (or it should set the isDisabled
bit of the ApplicationCommandInfo::flags field).
Any default key-presses for the command should be appended to the
ApplicationCommandInfo::defaultKeypresses field.
Note that if you change something that affects the status of the commands
that would be returned by this method (e.g. something that makes some commands
active or inactive), you should call ApplicationCommandManager::commandStatusChanged()
to cause the manager to refresh its status.
*/
virtual void getCommandInfo (CommandID commandID, ApplicationCommandInfo& result) = 0;
/** This must actually perform the specified command.
If this target is able to perform the command specified by the commandID field of the
InvocationInfo structure, then it should do so, and must return true.
If it can't handle this command, it should return false, which tells the caller to pass
the command on to the next target in line.
@see invoke, ApplicationCommandManager::invoke
*/
virtual bool perform (const InvocationInfo& info) = 0;
//==============================================================================
/** Makes this target invoke a command.
Your code can call this method to invoke a command on this target, but normally
you'd call it indirectly via ApplicationCommandManager::invoke() or
ApplicationCommandManager::invokeDirectly().
If this target can perform the given command, it will call its perform() method to
do so. If not, then getNextCommandTarget() will be used to determine the next target
to try, and the command will be passed along to it.
@param invocationInfo this must be correctly filled-in, describing the context for
the invocation.
@param asynchronously if false, the command will be performed before this method returns.
If true, a message will be posted so that the command will be performed
later on the message thread, and this method will return immediately.
@see perform, ApplicationCommandManager::invoke
*/
bool invoke (const InvocationInfo& invocationInfo,
const bool asynchronously);
/** Invokes a given command directly on this target.
This is just an easy way to call invoke() without having to fill out the InvocationInfo
structure.
*/
bool invokeDirectly (const CommandID commandID,
const bool asynchronously);
//==============================================================================
/** Searches this target and all subsequent ones for the first one that can handle
the specified command.
This will use getNextCommandTarget() to determine the chain of targets to try
after this one.
*/
ApplicationCommandTarget* getTargetForCommand (const CommandID commandID);
/** Checks whether this command can currently be performed by this target.
This will return true only if a call to getCommandInfo() doesn't set the
isDisabled flag to indicate that the command is inactive.
*/
bool isCommandActive (const CommandID commandID);
/** If this object is a Component, this method will search upwards in its current
UI hierarchy for the next parent component that implements the
ApplicationCommandTarget class.
If your target is a Component, this is a very handy method to use in your
getNextCommandTarget() implementation.
*/
ApplicationCommandTarget* findFirstTargetParentComponent();
private:
//==============================================================================
class CommandMessage;
friend class CommandMessage;
bool tryToInvoke (const InvocationInfo&, bool async);
JUCE_DECLARE_WEAK_REFERENCEABLE (ApplicationCommandTarget)
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ApplicationCommandTarget)
};
} // namespace juce
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
A command target publishes a list of command IDs that it can perform.
An ApplicationCommandManager despatches commands to targets, which must be
able to provide information about what commands they can handle.
To create a target, you'll need to inherit from this class, implementing all of
its pure virtual methods.
For info about how a target is chosen to receive a command, see
ApplicationCommandManager::getFirstCommandTarget().
@see ApplicationCommandManager, ApplicationCommandInfo
@tags{GUI}
*/
class JUCE_API ApplicationCommandTarget
{
public:
//==============================================================================
/** Creates a command target. */
ApplicationCommandTarget();
/** Destructor. */
virtual ~ApplicationCommandTarget();
//==============================================================================
/**
Contains contextual details about the invocation of a command.
*/
struct JUCE_API InvocationInfo
{
//==============================================================================
InvocationInfo (const CommandID commandID);
//==============================================================================
/** The UID of the command that should be performed. */
CommandID commandID;
/** The command's flags.
See ApplicationCommandInfo for a description of these flag values.
*/
int commandFlags;
//==============================================================================
/** The types of context in which the command might be called. */
enum InvocationMethod
{
direct = 0, /**< The command is being invoked directly by a piece of code. */
fromKeyPress, /**< The command is being invoked by a key-press. */
fromMenu, /**< The command is being invoked by a menu selection. */
fromButton /**< The command is being invoked by a button click. */
};
/** The type of event that triggered this command. */
InvocationMethod invocationMethod;
//==============================================================================
/** If triggered by a keypress or menu, this will be the component that had the
keyboard focus at the time.
If triggered by a button, it may be set to that component, or it may be null.
*/
Component* originatingComponent;
//==============================================================================
/** The keypress that was used to invoke it.
Note that this will be an invalid keypress if the command was invoked
by some other means than a keyboard shortcut.
*/
KeyPress keyPress;
/** True if the callback is being invoked when the key is pressed,
false if the key is being released.
@see KeyPressMappingSet::addCommand()
*/
bool isKeyDown;
/** If the key is being released, this indicates how long it had been held
down for.
(Only relevant if isKeyDown is false.)
*/
int millisecsSinceKeyPressed;
};
//==============================================================================
/** This must return the next target to try after this one.
When a command is being sent, and the first target can't handle
that command, this method is used to determine the next target that should
be tried.
It may return nullptr if it doesn't know of another target.
If your target is a Component, you would usually use the findFirstTargetParentComponent()
method to return a parent component that might want to handle it.
@see invoke
*/
virtual ApplicationCommandTarget* getNextCommandTarget() = 0;
/** This must return a complete list of commands that this target can handle.
Your target should add all the command IDs that it handles to the array that is
passed-in.
*/
virtual void getAllCommands (Array<CommandID>& commands) = 0;
/** This must provide details about one of the commands that this target can perform.
This will be called with one of the command IDs that the target provided in its
getAllCommands() methods.
It should fill-in all appropriate fields of the ApplicationCommandInfo structure with
suitable information about the command. (The commandID field will already have been filled-in
by the caller).
The easiest way to set the info is using the ApplicationCommandInfo::setInfo() method to
set all the fields at once.
If the command is currently inactive for some reason, this method must use
ApplicationCommandInfo::setActive() to make that clear, (or it should set the isDisabled
bit of the ApplicationCommandInfo::flags field).
Any default key-presses for the command should be appended to the
ApplicationCommandInfo::defaultKeypresses field.
Note that if you change something that affects the status of the commands
that would be returned by this method (e.g. something that makes some commands
active or inactive), you should call ApplicationCommandManager::commandStatusChanged()
to cause the manager to refresh its status.
*/
virtual void getCommandInfo (CommandID commandID, ApplicationCommandInfo& result) = 0;
/** This must actually perform the specified command.
If this target is able to perform the command specified by the commandID field of the
InvocationInfo structure, then it should do so, and must return true.
If it can't handle this command, it should return false, which tells the caller to pass
the command on to the next target in line.
@see invoke, ApplicationCommandManager::invoke
*/
virtual bool perform (const InvocationInfo& info) = 0;
//==============================================================================
/** Makes this target invoke a command.
Your code can call this method to invoke a command on this target, but normally
you'd call it indirectly via ApplicationCommandManager::invoke() or
ApplicationCommandManager::invokeDirectly().
If this target can perform the given command, it will call its perform() method to
do so. If not, then getNextCommandTarget() will be used to determine the next target
to try, and the command will be passed along to it.
@param invocationInfo this must be correctly filled-in, describing the context for
the invocation.
@param asynchronously if false, the command will be performed before this method returns.
If true, a message will be posted so that the command will be performed
later on the message thread, and this method will return immediately.
@see perform, ApplicationCommandManager::invoke
*/
bool invoke (const InvocationInfo& invocationInfo,
const bool asynchronously);
/** Invokes a given command directly on this target.
This is just an easy way to call invoke() without having to fill out the InvocationInfo
structure.
*/
bool invokeDirectly (const CommandID commandID,
const bool asynchronously);
//==============================================================================
/** Searches this target and all subsequent ones for the first one that can handle
the specified command.
This will use getNextCommandTarget() to determine the chain of targets to try
after this one.
*/
ApplicationCommandTarget* getTargetForCommand (const CommandID commandID);
/** Checks whether this command can currently be performed by this target.
This will return true only if a call to getCommandInfo() doesn't set the
isDisabled flag to indicate that the command is inactive.
*/
bool isCommandActive (const CommandID commandID);
/** If this object is a Component, this method will search upwards in its current
UI hierarchy for the next parent component that implements the
ApplicationCommandTarget class.
If your target is a Component, this is a very handy method to use in your
getNextCommandTarget() implementation.
*/
ApplicationCommandTarget* findFirstTargetParentComponent();
private:
//==============================================================================
class CommandMessage;
friend class CommandMessage;
bool tryToInvoke (const InvocationInfo&, bool async);
JUCE_DECLARE_WEAK_REFERENCEABLE (ApplicationCommandTarget)
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ApplicationCommandTarget)
};
} // namespace juce

View File

@ -1,418 +1,418 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
KeyPressMappingSet::KeyPressMappingSet (ApplicationCommandManager& cm)
: commandManager (cm)
{
Desktop::getInstance().addFocusChangeListener (this);
}
KeyPressMappingSet::KeyPressMappingSet (const KeyPressMappingSet& other)
: KeyListener(), ChangeBroadcaster(), FocusChangeListener(), commandManager (other.commandManager)
{
Desktop::getInstance().addFocusChangeListener (this);
}
KeyPressMappingSet::~KeyPressMappingSet()
{
Desktop::getInstance().removeFocusChangeListener (this);
}
//==============================================================================
Array<KeyPress> KeyPressMappingSet::getKeyPressesAssignedToCommand (const CommandID commandID) const
{
for (int i = 0; i < mappings.size(); ++i)
if (mappings.getUnchecked(i)->commandID == commandID)
return mappings.getUnchecked (i)->keypresses;
return {};
}
void KeyPressMappingSet::addKeyPress (const CommandID commandID, const KeyPress& newKeyPress, int insertIndex)
{
// If you specify an upper-case letter but no shift key, how is the user supposed to press it!?
// Stick to lower-case letters when defining a keypress, to avoid ambiguity.
jassert (! (CharacterFunctions::isUpperCase (newKeyPress.getTextCharacter())
&& ! newKeyPress.getModifiers().isShiftDown()));
if (findCommandForKeyPress (newKeyPress) != commandID)
{
if (newKeyPress.isValid())
{
for (int i = mappings.size(); --i >= 0;)
{
if (mappings.getUnchecked(i)->commandID == commandID)
{
mappings.getUnchecked(i)->keypresses.insert (insertIndex, newKeyPress);
sendChangeMessage();
return;
}
}
if (const ApplicationCommandInfo* const ci = commandManager.getCommandForID (commandID))
{
CommandMapping* const cm = new CommandMapping();
cm->commandID = commandID;
cm->keypresses.add (newKeyPress);
cm->wantsKeyUpDownCallbacks = (ci->flags & ApplicationCommandInfo::wantsKeyUpDownCallbacks) != 0;
mappings.add (cm);
sendChangeMessage();
}
else
{
// If you hit this, you're trying to attach a keypress to a command ID that
// doesn't exist, so the key is not being attached.
jassertfalse;
}
}
}
}
static void addKeyPresses (KeyPressMappingSet& set, const ApplicationCommandInfo* const ci)
{
for (int j = 0; j < ci->defaultKeypresses.size(); ++j)
set.addKeyPress (ci->commandID, ci->defaultKeypresses.getReference (j));
}
void KeyPressMappingSet::resetToDefaultMappings()
{
mappings.clear();
for (int i = 0; i < commandManager.getNumCommands(); ++i)
addKeyPresses (*this, commandManager.getCommandForIndex (i));
sendChangeMessage();
}
void KeyPressMappingSet::resetToDefaultMapping (const CommandID commandID)
{
clearAllKeyPresses (commandID);
if (const ApplicationCommandInfo* const ci = commandManager.getCommandForID (commandID))
addKeyPresses (*this, ci);
}
void KeyPressMappingSet::clearAllKeyPresses()
{
if (mappings.size() > 0)
{
sendChangeMessage();
mappings.clear();
}
}
void KeyPressMappingSet::clearAllKeyPresses (const CommandID commandID)
{
for (int i = mappings.size(); --i >= 0;)
{
if (mappings.getUnchecked(i)->commandID == commandID)
{
mappings.remove (i);
sendChangeMessage();
}
}
}
void KeyPressMappingSet::removeKeyPress (const KeyPress& keypress)
{
if (keypress.isValid())
{
for (int i = mappings.size(); --i >= 0;)
{
CommandMapping& cm = *mappings.getUnchecked(i);
for (int j = cm.keypresses.size(); --j >= 0;)
{
if (keypress == cm.keypresses [j])
{
cm.keypresses.remove (j);
sendChangeMessage();
}
}
}
}
}
void KeyPressMappingSet::removeKeyPress (const CommandID commandID, const int keyPressIndex)
{
for (int i = mappings.size(); --i >= 0;)
{
if (mappings.getUnchecked(i)->commandID == commandID)
{
mappings.getUnchecked(i)->keypresses.remove (keyPressIndex);
sendChangeMessage();
break;
}
}
}
//==============================================================================
CommandID KeyPressMappingSet::findCommandForKeyPress (const KeyPress& keyPress) const noexcept
{
for (int i = 0; i < mappings.size(); ++i)
if (mappings.getUnchecked(i)->keypresses.contains (keyPress))
return mappings.getUnchecked(i)->commandID;
return 0;
}
bool KeyPressMappingSet::containsMapping (const CommandID commandID, const KeyPress& keyPress) const noexcept
{
for (int i = mappings.size(); --i >= 0;)
if (mappings.getUnchecked(i)->commandID == commandID)
return mappings.getUnchecked(i)->keypresses.contains (keyPress);
return false;
}
void KeyPressMappingSet::invokeCommand (const CommandID commandID,
const KeyPress& key,
const bool isKeyDown,
const int millisecsSinceKeyPressed,
Component* const originatingComponent) const
{
ApplicationCommandTarget::InvocationInfo info (commandID);
info.invocationMethod = ApplicationCommandTarget::InvocationInfo::fromKeyPress;
info.isKeyDown = isKeyDown;
info.keyPress = key;
info.millisecsSinceKeyPressed = millisecsSinceKeyPressed;
info.originatingComponent = originatingComponent;
commandManager.invoke (info, false);
}
//==============================================================================
bool KeyPressMappingSet::restoreFromXml (const XmlElement& xmlVersion)
{
if (xmlVersion.hasTagName ("KEYMAPPINGS"))
{
if (xmlVersion.getBoolAttribute ("basedOnDefaults", true))
{
// if the XML was created as a set of differences from the default mappings,
// (i.e. by calling createXml (true)), then we need to first restore the defaults.
resetToDefaultMappings();
}
else
{
// if the XML was created calling createXml (false), then we need to clear all
// the keys and treat the xml as describing the entire set of mappings.
clearAllKeyPresses();
}
for (auto* map : xmlVersion.getChildIterator())
{
const CommandID commandId = map->getStringAttribute ("commandId").getHexValue32();
if (commandId != 0)
{
auto key = KeyPress::createFromDescription (map->getStringAttribute ("key"));
if (map->hasTagName ("MAPPING"))
{
addKeyPress (commandId, key);
}
else if (map->hasTagName ("UNMAPPING"))
{
for (auto& m : mappings)
if (m->commandID == commandId)
m->keypresses.removeAllInstancesOf (key);
}
}
}
return true;
}
return false;
}
std::unique_ptr<XmlElement> KeyPressMappingSet::createXml (const bool saveDifferencesFromDefaultSet) const
{
std::unique_ptr<KeyPressMappingSet> defaultSet;
if (saveDifferencesFromDefaultSet)
{
defaultSet = std::make_unique<KeyPressMappingSet> (commandManager);
defaultSet->resetToDefaultMappings();
}
auto doc = std::make_unique<XmlElement> ("KEYMAPPINGS");
doc->setAttribute ("basedOnDefaults", saveDifferencesFromDefaultSet);
for (int i = 0; i < mappings.size(); ++i)
{
auto& cm = *mappings.getUnchecked(i);
for (int j = 0; j < cm.keypresses.size(); ++j)
{
if (defaultSet == nullptr
|| ! defaultSet->containsMapping (cm.commandID, cm.keypresses.getReference (j)))
{
auto map = doc->createNewChildElement ("MAPPING");
map->setAttribute ("commandId", String::toHexString ((int) cm.commandID));
map->setAttribute ("description", commandManager.getDescriptionOfCommand (cm.commandID));
map->setAttribute ("key", cm.keypresses.getReference (j).getTextDescription());
}
}
}
if (defaultSet != nullptr)
{
for (int i = 0; i < defaultSet->mappings.size(); ++i)
{
auto& cm = *defaultSet->mappings.getUnchecked(i);
for (int j = 0; j < cm.keypresses.size(); ++j)
{
if (! containsMapping (cm.commandID, cm.keypresses.getReference (j)))
{
auto map = doc->createNewChildElement ("UNMAPPING");
map->setAttribute ("commandId", String::toHexString ((int) cm.commandID));
map->setAttribute ("description", commandManager.getDescriptionOfCommand (cm.commandID));
map->setAttribute ("key", cm.keypresses.getReference (j).getTextDescription());
}
}
}
}
return doc;
}
//==============================================================================
bool KeyPressMappingSet::keyPressed (const KeyPress& key, Component* const originatingComponent)
{
bool commandWasDisabled = false;
for (int i = 0; i < mappings.size(); ++i)
{
CommandMapping& cm = *mappings.getUnchecked(i);
if (cm.keypresses.contains (key))
{
if (const ApplicationCommandInfo* const ci = commandManager.getCommandForID (cm.commandID))
{
if ((ci->flags & ApplicationCommandInfo::wantsKeyUpDownCallbacks) == 0)
{
ApplicationCommandInfo info (0);
if (commandManager.getTargetForCommand (cm.commandID, info) != nullptr)
{
if ((info.flags & ApplicationCommandInfo::isDisabled) == 0)
{
invokeCommand (cm.commandID, key, true, 0, originatingComponent);
return true;
}
commandWasDisabled = true;
}
}
}
}
}
if (originatingComponent != nullptr && commandWasDisabled)
originatingComponent->getLookAndFeel().playAlertSound();
return false;
}
bool KeyPressMappingSet::keyStateChanged (const bool /*isKeyDown*/, Component* originatingComponent)
{
bool used = false;
const uint32 now = Time::getMillisecondCounter();
for (int i = mappings.size(); --i >= 0;)
{
CommandMapping& cm = *mappings.getUnchecked(i);
if (cm.wantsKeyUpDownCallbacks)
{
for (int j = cm.keypresses.size(); --j >= 0;)
{
const KeyPress key (cm.keypresses.getReference (j));
const bool isDown = key.isCurrentlyDown();
int keyPressEntryIndex = 0;
bool wasDown = false;
for (int k = keysDown.size(); --k >= 0;)
{
if (key == keysDown.getUnchecked(k)->key)
{
keyPressEntryIndex = k;
wasDown = true;
used = true;
break;
}
}
if (isDown != wasDown)
{
int millisecs = 0;
if (isDown)
{
KeyPressTime* const k = new KeyPressTime();
k->key = key;
k->timeWhenPressed = now;
keysDown.add (k);
}
else
{
const uint32 pressTime = keysDown.getUnchecked (keyPressEntryIndex)->timeWhenPressed;
if (now > pressTime)
millisecs = (int) (now - pressTime);
keysDown.remove (keyPressEntryIndex);
}
invokeCommand (cm.commandID, key, isDown, millisecs, originatingComponent);
used = true;
}
}
}
}
return used;
}
void KeyPressMappingSet::globalFocusChanged (Component* focusedComponent)
{
if (focusedComponent != nullptr)
focusedComponent->keyStateChanged (false);
}
} // namespace juce
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
KeyPressMappingSet::KeyPressMappingSet (ApplicationCommandManager& cm)
: commandManager (cm)
{
Desktop::getInstance().addFocusChangeListener (this);
}
KeyPressMappingSet::KeyPressMappingSet (const KeyPressMappingSet& other)
: KeyListener(), ChangeBroadcaster(), FocusChangeListener(), commandManager (other.commandManager)
{
Desktop::getInstance().addFocusChangeListener (this);
}
KeyPressMappingSet::~KeyPressMappingSet()
{
Desktop::getInstance().removeFocusChangeListener (this);
}
//==============================================================================
Array<KeyPress> KeyPressMappingSet::getKeyPressesAssignedToCommand (const CommandID commandID) const
{
for (int i = 0; i < mappings.size(); ++i)
if (mappings.getUnchecked(i)->commandID == commandID)
return mappings.getUnchecked (i)->keypresses;
return {};
}
void KeyPressMappingSet::addKeyPress (const CommandID commandID, const KeyPress& newKeyPress, int insertIndex)
{
// If you specify an upper-case letter but no shift key, how is the user supposed to press it!?
// Stick to lower-case letters when defining a keypress, to avoid ambiguity.
jassert (! (CharacterFunctions::isUpperCase (newKeyPress.getTextCharacter())
&& ! newKeyPress.getModifiers().isShiftDown()));
if (findCommandForKeyPress (newKeyPress) != commandID)
{
if (newKeyPress.isValid())
{
for (int i = mappings.size(); --i >= 0;)
{
if (mappings.getUnchecked(i)->commandID == commandID)
{
mappings.getUnchecked(i)->keypresses.insert (insertIndex, newKeyPress);
sendChangeMessage();
return;
}
}
if (const ApplicationCommandInfo* const ci = commandManager.getCommandForID (commandID))
{
CommandMapping* const cm = new CommandMapping();
cm->commandID = commandID;
cm->keypresses.add (newKeyPress);
cm->wantsKeyUpDownCallbacks = (ci->flags & ApplicationCommandInfo::wantsKeyUpDownCallbacks) != 0;
mappings.add (cm);
sendChangeMessage();
}
else
{
// If you hit this, you're trying to attach a keypress to a command ID that
// doesn't exist, so the key is not being attached.
jassertfalse;
}
}
}
}
static void addKeyPresses (KeyPressMappingSet& set, const ApplicationCommandInfo* const ci)
{
for (int j = 0; j < ci->defaultKeypresses.size(); ++j)
set.addKeyPress (ci->commandID, ci->defaultKeypresses.getReference (j));
}
void KeyPressMappingSet::resetToDefaultMappings()
{
mappings.clear();
for (int i = 0; i < commandManager.getNumCommands(); ++i)
addKeyPresses (*this, commandManager.getCommandForIndex (i));
sendChangeMessage();
}
void KeyPressMappingSet::resetToDefaultMapping (const CommandID commandID)
{
clearAllKeyPresses (commandID);
if (const ApplicationCommandInfo* const ci = commandManager.getCommandForID (commandID))
addKeyPresses (*this, ci);
}
void KeyPressMappingSet::clearAllKeyPresses()
{
if (mappings.size() > 0)
{
sendChangeMessage();
mappings.clear();
}
}
void KeyPressMappingSet::clearAllKeyPresses (const CommandID commandID)
{
for (int i = mappings.size(); --i >= 0;)
{
if (mappings.getUnchecked(i)->commandID == commandID)
{
mappings.remove (i);
sendChangeMessage();
}
}
}
void KeyPressMappingSet::removeKeyPress (const KeyPress& keypress)
{
if (keypress.isValid())
{
for (int i = mappings.size(); --i >= 0;)
{
CommandMapping& cm = *mappings.getUnchecked(i);
for (int j = cm.keypresses.size(); --j >= 0;)
{
if (keypress == cm.keypresses [j])
{
cm.keypresses.remove (j);
sendChangeMessage();
}
}
}
}
}
void KeyPressMappingSet::removeKeyPress (const CommandID commandID, const int keyPressIndex)
{
for (int i = mappings.size(); --i >= 0;)
{
if (mappings.getUnchecked(i)->commandID == commandID)
{
mappings.getUnchecked(i)->keypresses.remove (keyPressIndex);
sendChangeMessage();
break;
}
}
}
//==============================================================================
CommandID KeyPressMappingSet::findCommandForKeyPress (const KeyPress& keyPress) const noexcept
{
for (int i = 0; i < mappings.size(); ++i)
if (mappings.getUnchecked(i)->keypresses.contains (keyPress))
return mappings.getUnchecked(i)->commandID;
return 0;
}
bool KeyPressMappingSet::containsMapping (const CommandID commandID, const KeyPress& keyPress) const noexcept
{
for (int i = mappings.size(); --i >= 0;)
if (mappings.getUnchecked(i)->commandID == commandID)
return mappings.getUnchecked(i)->keypresses.contains (keyPress);
return false;
}
void KeyPressMappingSet::invokeCommand (const CommandID commandID,
const KeyPress& key,
const bool isKeyDown,
const int millisecsSinceKeyPressed,
Component* const originatingComponent) const
{
ApplicationCommandTarget::InvocationInfo info (commandID);
info.invocationMethod = ApplicationCommandTarget::InvocationInfo::fromKeyPress;
info.isKeyDown = isKeyDown;
info.keyPress = key;
info.millisecsSinceKeyPressed = millisecsSinceKeyPressed;
info.originatingComponent = originatingComponent;
commandManager.invoke (info, false);
}
//==============================================================================
bool KeyPressMappingSet::restoreFromXml (const XmlElement& xmlVersion)
{
if (xmlVersion.hasTagName ("KEYMAPPINGS"))
{
if (xmlVersion.getBoolAttribute ("basedOnDefaults", true))
{
// if the XML was created as a set of differences from the default mappings,
// (i.e. by calling createXml (true)), then we need to first restore the defaults.
resetToDefaultMappings();
}
else
{
// if the XML was created calling createXml (false), then we need to clear all
// the keys and treat the xml as describing the entire set of mappings.
clearAllKeyPresses();
}
for (auto* map : xmlVersion.getChildIterator())
{
const CommandID commandId = map->getStringAttribute ("commandId").getHexValue32();
if (commandId != 0)
{
auto key = KeyPress::createFromDescription (map->getStringAttribute ("key"));
if (map->hasTagName ("MAPPING"))
{
addKeyPress (commandId, key);
}
else if (map->hasTagName ("UNMAPPING"))
{
for (auto& m : mappings)
if (m->commandID == commandId)
m->keypresses.removeAllInstancesOf (key);
}
}
}
return true;
}
return false;
}
std::unique_ptr<XmlElement> KeyPressMappingSet::createXml (const bool saveDifferencesFromDefaultSet) const
{
std::unique_ptr<KeyPressMappingSet> defaultSet;
if (saveDifferencesFromDefaultSet)
{
defaultSet = std::make_unique<KeyPressMappingSet> (commandManager);
defaultSet->resetToDefaultMappings();
}
auto doc = std::make_unique<XmlElement> ("KEYMAPPINGS");
doc->setAttribute ("basedOnDefaults", saveDifferencesFromDefaultSet);
for (int i = 0; i < mappings.size(); ++i)
{
auto& cm = *mappings.getUnchecked(i);
for (int j = 0; j < cm.keypresses.size(); ++j)
{
if (defaultSet == nullptr
|| ! defaultSet->containsMapping (cm.commandID, cm.keypresses.getReference (j)))
{
auto map = doc->createNewChildElement ("MAPPING");
map->setAttribute ("commandId", String::toHexString ((int) cm.commandID));
map->setAttribute ("description", commandManager.getDescriptionOfCommand (cm.commandID));
map->setAttribute ("key", cm.keypresses.getReference (j).getTextDescription());
}
}
}
if (defaultSet != nullptr)
{
for (int i = 0; i < defaultSet->mappings.size(); ++i)
{
auto& cm = *defaultSet->mappings.getUnchecked(i);
for (int j = 0; j < cm.keypresses.size(); ++j)
{
if (! containsMapping (cm.commandID, cm.keypresses.getReference (j)))
{
auto map = doc->createNewChildElement ("UNMAPPING");
map->setAttribute ("commandId", String::toHexString ((int) cm.commandID));
map->setAttribute ("description", commandManager.getDescriptionOfCommand (cm.commandID));
map->setAttribute ("key", cm.keypresses.getReference (j).getTextDescription());
}
}
}
}
return doc;
}
//==============================================================================
bool KeyPressMappingSet::keyPressed (const KeyPress& key, Component* const originatingComponent)
{
bool commandWasDisabled = false;
for (int i = 0; i < mappings.size(); ++i)
{
CommandMapping& cm = *mappings.getUnchecked(i);
if (cm.keypresses.contains (key))
{
if (const ApplicationCommandInfo* const ci = commandManager.getCommandForID (cm.commandID))
{
if ((ci->flags & ApplicationCommandInfo::wantsKeyUpDownCallbacks) == 0)
{
ApplicationCommandInfo info (0);
if (commandManager.getTargetForCommand (cm.commandID, info) != nullptr)
{
if ((info.flags & ApplicationCommandInfo::isDisabled) == 0)
{
invokeCommand (cm.commandID, key, true, 0, originatingComponent);
return true;
}
commandWasDisabled = true;
}
}
}
}
}
if (originatingComponent != nullptr && commandWasDisabled)
originatingComponent->getLookAndFeel().playAlertSound();
return false;
}
bool KeyPressMappingSet::keyStateChanged (const bool /*isKeyDown*/, Component* originatingComponent)
{
bool used = false;
const uint32 now = Time::getMillisecondCounter();
for (int i = mappings.size(); --i >= 0;)
{
CommandMapping& cm = *mappings.getUnchecked(i);
if (cm.wantsKeyUpDownCallbacks)
{
for (int j = cm.keypresses.size(); --j >= 0;)
{
const KeyPress key (cm.keypresses.getReference (j));
const bool isDown = key.isCurrentlyDown();
int keyPressEntryIndex = 0;
bool wasDown = false;
for (int k = keysDown.size(); --k >= 0;)
{
if (key == keysDown.getUnchecked(k)->key)
{
keyPressEntryIndex = k;
wasDown = true;
used = true;
break;
}
}
if (isDown != wasDown)
{
int millisecs = 0;
if (isDown)
{
KeyPressTime* const k = new KeyPressTime();
k->key = key;
k->timeWhenPressed = now;
keysDown.add (k);
}
else
{
const uint32 pressTime = keysDown.getUnchecked (keyPressEntryIndex)->timeWhenPressed;
if (now > pressTime)
millisecs = (int) (now - pressTime);
keysDown.remove (keyPressEntryIndex);
}
invokeCommand (cm.commandID, key, isDown, millisecs, originatingComponent);
used = true;
}
}
}
}
return used;
}
void KeyPressMappingSet::globalFocusChanged (Component* focusedComponent)
{
if (focusedComponent != nullptr)
focusedComponent->keyStateChanged (false);
}
} // namespace juce

View File

@ -1,244 +1,244 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
Manages and edits a list of keypresses, which it uses to invoke the appropriate
command in an ApplicationCommandManager.
Normally, you won't actually create a KeyPressMappingSet directly, because
each ApplicationCommandManager contains its own KeyPressMappingSet, so typically
you'd create yourself an ApplicationCommandManager, and call its
ApplicationCommandManager::getKeyMappings() method to get a pointer to its
KeyPressMappingSet.
For one of these to actually use keypresses, you'll need to add it as a KeyListener
to the top-level component for which you want to handle keystrokes. So for example:
@code
class MyMainWindow : public Component
{
ApplicationCommandManager* myCommandManager;
public:
MyMainWindow()
{
myCommandManager = new ApplicationCommandManager();
// first, make sure the command manager has registered all the commands that its
// targets can perform..
myCommandManager->registerAllCommandsForTarget (myCommandTarget1);
myCommandManager->registerAllCommandsForTarget (myCommandTarget2);
// this will use the command manager to initialise the KeyPressMappingSet with
// the default keypresses that were specified when the targets added their commands
// to the manager.
myCommandManager->getKeyMappings()->resetToDefaultMappings();
// having set up the default key-mappings, you might now want to load the last set
// of mappings that the user configured.
myCommandManager->getKeyMappings()->restoreFromXml (lastSavedKeyMappingsXML);
// Now tell our top-level window to send any keypresses that arrive to the
// KeyPressMappingSet, which will use them to invoke the appropriate commands.
addKeyListener (myCommandManager->getKeyMappings());
}
...
}
@endcode
KeyPressMappingSet derives from ChangeBroadcaster so that interested parties can
register to be told when a command or mapping is added, removed, etc.
There's also a UI component called KeyMappingEditorComponent that can be used
to easily edit the key mappings.
@see Component::addKeyListener(), KeyMappingEditorComponent, ApplicationCommandManager
@tags{GUI}
*/
class JUCE_API KeyPressMappingSet : public KeyListener,
public ChangeBroadcaster,
private FocusChangeListener
{
public:
//==============================================================================
/** Creates a KeyPressMappingSet for a given command manager.
Normally, you won't actually create a KeyPressMappingSet directly, because
each ApplicationCommandManager contains its own KeyPressMappingSet, so the
best thing to do is to create your ApplicationCommandManager, and use the
ApplicationCommandManager::getKeyMappings() method to access its mappings.
When a suitable keypress happens, the manager's invoke() method will be
used to invoke the appropriate command.
@see ApplicationCommandManager
*/
explicit KeyPressMappingSet (ApplicationCommandManager&);
/** Creates an copy of a KeyPressMappingSet. */
KeyPressMappingSet (const KeyPressMappingSet&);
/** Destructor. */
~KeyPressMappingSet() override;
//==============================================================================
ApplicationCommandManager& getCommandManager() const noexcept { return commandManager; }
//==============================================================================
/** Returns a list of keypresses that are assigned to a particular command.
@param commandID the command's ID
*/
Array<KeyPress> getKeyPressesAssignedToCommand (CommandID commandID) const;
/** Assigns a keypress to a command.
If the keypress is already assigned to a different command, it will first be
removed from that command, to avoid it triggering multiple functions.
@param commandID the ID of the command that you want to add a keypress to. If
this is 0, the keypress will be removed from anything that it
was previously assigned to, but not re-assigned
@param newKeyPress the new key-press
@param insertIndex if this is less than zero, the key will be appended to the
end of the list of keypresses; otherwise the new keypress will
be inserted into the existing list at this index
*/
void addKeyPress (CommandID commandID,
const KeyPress& newKeyPress,
int insertIndex = -1);
/** Reset all mappings to the defaults, as dictated by the ApplicationCommandManager.
@see resetToDefaultMapping
*/
void resetToDefaultMappings();
/** Resets all key-mappings to the defaults for a particular command.
@see resetToDefaultMappings
*/
void resetToDefaultMapping (CommandID commandID);
/** Removes all keypresses that are assigned to any commands. */
void clearAllKeyPresses();
/** Removes all keypresses that are assigned to a particular command. */
void clearAllKeyPresses (CommandID commandID);
/** Removes one of the keypresses that are assigned to a command.
See the getKeyPressesAssignedToCommand() for the list of keypresses to
which the keyPressIndex refers.
*/
void removeKeyPress (CommandID commandID, int keyPressIndex);
/** Removes a keypress from any command that it may be assigned to. */
void removeKeyPress (const KeyPress& keypress);
/** Returns true if the given command is linked to this key. */
bool containsMapping (CommandID commandID, const KeyPress& keyPress) const noexcept;
//==============================================================================
/** Looks for a command that corresponds to a keypress.
@returns the UID of the command or 0 if none was found
*/
CommandID findCommandForKeyPress (const KeyPress& keyPress) const noexcept;
//==============================================================================
/** Tries to recreate the mappings from a previously stored state.
The XML passed in must have been created by the createXml() method.
If the stored state makes any reference to commands that aren't
currently available, these will be ignored.
If the set of mappings being loaded was a set of differences (using createXml (true)),
then this will call resetToDefaultMappings() and then merge the saved mappings
on top. If the saved set was created with createXml (false), then this method
will first clear all existing mappings and load the saved ones as a complete set.
@returns true if it manages to load the XML correctly
@see createXml
*/
bool restoreFromXml (const XmlElement& xmlVersion);
/** Creates an XML representation of the current mappings.
This will produce a lump of XML that can be later reloaded using
restoreFromXml() to recreate the current mapping state.
@param saveDifferencesFromDefaultSet if this is false, then all keypresses
will be saved into the XML. If it's true, then the XML will
only store the differences between the current mappings and
the default mappings you'd get from calling resetToDefaultMappings().
The advantage of saving a set of differences from the default is that
if you change the default mappings (in a new version of your app, for
example), then these will be merged into a user's saved preferences.
@see restoreFromXml
*/
std::unique_ptr<XmlElement> createXml (bool saveDifferencesFromDefaultSet) const;
//==============================================================================
/** @internal */
bool keyPressed (const KeyPress&, Component*) override;
/** @internal */
bool keyStateChanged (bool isKeyDown, Component*) override;
/** @internal */
void globalFocusChanged (Component*) override;
private:
//==============================================================================
ApplicationCommandManager& commandManager;
struct CommandMapping
{
CommandID commandID;
Array<KeyPress> keypresses;
bool wantsKeyUpDownCallbacks;
};
OwnedArray<CommandMapping> mappings;
struct KeyPressTime
{
KeyPress key;
uint32 timeWhenPressed;
};
OwnedArray<KeyPressTime> keysDown;
void invokeCommand (const CommandID, const KeyPress&, const bool isKeyDown,
const int millisecsSinceKeyPressed, Component* originator) const;
KeyPressMappingSet& operator= (const KeyPressMappingSet&);
JUCE_LEAK_DETECTOR (KeyPressMappingSet)
};
} // namespace juce
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
Manages and edits a list of keypresses, which it uses to invoke the appropriate
command in an ApplicationCommandManager.
Normally, you won't actually create a KeyPressMappingSet directly, because
each ApplicationCommandManager contains its own KeyPressMappingSet, so typically
you'd create yourself an ApplicationCommandManager, and call its
ApplicationCommandManager::getKeyMappings() method to get a pointer to its
KeyPressMappingSet.
For one of these to actually use keypresses, you'll need to add it as a KeyListener
to the top-level component for which you want to handle keystrokes. So for example:
@code
class MyMainWindow : public Component
{
ApplicationCommandManager* myCommandManager;
public:
MyMainWindow()
{
myCommandManager = new ApplicationCommandManager();
// first, make sure the command manager has registered all the commands that its
// targets can perform..
myCommandManager->registerAllCommandsForTarget (myCommandTarget1);
myCommandManager->registerAllCommandsForTarget (myCommandTarget2);
// this will use the command manager to initialise the KeyPressMappingSet with
// the default keypresses that were specified when the targets added their commands
// to the manager.
myCommandManager->getKeyMappings()->resetToDefaultMappings();
// having set up the default key-mappings, you might now want to load the last set
// of mappings that the user configured.
myCommandManager->getKeyMappings()->restoreFromXml (lastSavedKeyMappingsXML);
// Now tell our top-level window to send any keypresses that arrive to the
// KeyPressMappingSet, which will use them to invoke the appropriate commands.
addKeyListener (myCommandManager->getKeyMappings());
}
...
}
@endcode
KeyPressMappingSet derives from ChangeBroadcaster so that interested parties can
register to be told when a command or mapping is added, removed, etc.
There's also a UI component called KeyMappingEditorComponent that can be used
to easily edit the key mappings.
@see Component::addKeyListener(), KeyMappingEditorComponent, ApplicationCommandManager
@tags{GUI}
*/
class JUCE_API KeyPressMappingSet : public KeyListener,
public ChangeBroadcaster,
private FocusChangeListener
{
public:
//==============================================================================
/** Creates a KeyPressMappingSet for a given command manager.
Normally, you won't actually create a KeyPressMappingSet directly, because
each ApplicationCommandManager contains its own KeyPressMappingSet, so the
best thing to do is to create your ApplicationCommandManager, and use the
ApplicationCommandManager::getKeyMappings() method to access its mappings.
When a suitable keypress happens, the manager's invoke() method will be
used to invoke the appropriate command.
@see ApplicationCommandManager
*/
explicit KeyPressMappingSet (ApplicationCommandManager&);
/** Creates an copy of a KeyPressMappingSet. */
KeyPressMappingSet (const KeyPressMappingSet&);
/** Destructor. */
~KeyPressMappingSet() override;
//==============================================================================
ApplicationCommandManager& getCommandManager() const noexcept { return commandManager; }
//==============================================================================
/** Returns a list of keypresses that are assigned to a particular command.
@param commandID the command's ID
*/
Array<KeyPress> getKeyPressesAssignedToCommand (CommandID commandID) const;
/** Assigns a keypress to a command.
If the keypress is already assigned to a different command, it will first be
removed from that command, to avoid it triggering multiple functions.
@param commandID the ID of the command that you want to add a keypress to. If
this is 0, the keypress will be removed from anything that it
was previously assigned to, but not re-assigned
@param newKeyPress the new key-press
@param insertIndex if this is less than zero, the key will be appended to the
end of the list of keypresses; otherwise the new keypress will
be inserted into the existing list at this index
*/
void addKeyPress (CommandID commandID,
const KeyPress& newKeyPress,
int insertIndex = -1);
/** Reset all mappings to the defaults, as dictated by the ApplicationCommandManager.
@see resetToDefaultMapping
*/
void resetToDefaultMappings();
/** Resets all key-mappings to the defaults for a particular command.
@see resetToDefaultMappings
*/
void resetToDefaultMapping (CommandID commandID);
/** Removes all keypresses that are assigned to any commands. */
void clearAllKeyPresses();
/** Removes all keypresses that are assigned to a particular command. */
void clearAllKeyPresses (CommandID commandID);
/** Removes one of the keypresses that are assigned to a command.
See the getKeyPressesAssignedToCommand() for the list of keypresses to
which the keyPressIndex refers.
*/
void removeKeyPress (CommandID commandID, int keyPressIndex);
/** Removes a keypress from any command that it may be assigned to. */
void removeKeyPress (const KeyPress& keypress);
/** Returns true if the given command is linked to this key. */
bool containsMapping (CommandID commandID, const KeyPress& keyPress) const noexcept;
//==============================================================================
/** Looks for a command that corresponds to a keypress.
@returns the UID of the command or 0 if none was found
*/
CommandID findCommandForKeyPress (const KeyPress& keyPress) const noexcept;
//==============================================================================
/** Tries to recreate the mappings from a previously stored state.
The XML passed in must have been created by the createXml() method.
If the stored state makes any reference to commands that aren't
currently available, these will be ignored.
If the set of mappings being loaded was a set of differences (using createXml (true)),
then this will call resetToDefaultMappings() and then merge the saved mappings
on top. If the saved set was created with createXml (false), then this method
will first clear all existing mappings and load the saved ones as a complete set.
@returns true if it manages to load the XML correctly
@see createXml
*/
bool restoreFromXml (const XmlElement& xmlVersion);
/** Creates an XML representation of the current mappings.
This will produce a lump of XML that can be later reloaded using
restoreFromXml() to recreate the current mapping state.
@param saveDifferencesFromDefaultSet if this is false, then all keypresses
will be saved into the XML. If it's true, then the XML will
only store the differences between the current mappings and
the default mappings you'd get from calling resetToDefaultMappings().
The advantage of saving a set of differences from the default is that
if you change the default mappings (in a new version of your app, for
example), then these will be merged into a user's saved preferences.
@see restoreFromXml
*/
std::unique_ptr<XmlElement> createXml (bool saveDifferencesFromDefaultSet) const;
//==============================================================================
/** @internal */
bool keyPressed (const KeyPress&, Component*) override;
/** @internal */
bool keyStateChanged (bool isKeyDown, Component*) override;
/** @internal */
void globalFocusChanged (Component*) override;
private:
//==============================================================================
ApplicationCommandManager& commandManager;
struct CommandMapping
{
CommandID commandID;
Array<KeyPress> keypresses;
bool wantsKeyUpDownCallbacks;
};
OwnedArray<CommandMapping> mappings;
struct KeyPressTime
{
KeyPress key;
uint32 timeWhenPressed;
};
OwnedArray<KeyPressTime> keysDown;
void invokeCommand (const CommandID, const KeyPress&, const bool isKeyDown,
const int millisecsSinceKeyPressed, Component* originator) const;
KeyPressMappingSet& operator= (const KeyPressMappingSet&);
JUCE_LEAK_DETECTOR (KeyPressMappingSet)
};
} // namespace juce