1 /*
2   ==============================================================================
3 
4    This file is part of the JUCE library.
5    Copyright (c) 2020 - Raw Material Software Limited
6 
7    JUCE is an open source library subject to commercial or open-source
8    licensing.
9 
10    By using JUCE, you agree to the terms of both the JUCE 6 End-User License
11    Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
12 
13    End User License Agreement: www.juce.com/juce-6-licence
14    Privacy Policy: www.juce.com/juce-privacy-policy
15 
16    Or: You may also use this code under the terms of the GPL v3 (see
17    www.gnu.org/licenses).
18 
19    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21    DISCLAIMED.
22 
23   ==============================================================================
24 */
25 
26 namespace juce
27 {
28 
29 //==============================================================================
30 /**
31     Manages and edits a list of keypresses, which it uses to invoke the appropriate
32     command in an ApplicationCommandManager.
33 
34     Normally, you won't actually create a KeyPressMappingSet directly, because
35     each ApplicationCommandManager contains its own KeyPressMappingSet, so typically
36     you'd create yourself an ApplicationCommandManager, and call its
37     ApplicationCommandManager::getKeyMappings() method to get a pointer to its
38     KeyPressMappingSet.
39 
40     For one of these to actually use keypresses, you'll need to add it as a KeyListener
41     to the top-level component for which you want to handle keystrokes. So for example:
42 
43     @code
44     class MyMainWindow  : public Component
45     {
46         ApplicationCommandManager* myCommandManager;
47 
48     public:
49         MyMainWindow()
50         {
51             myCommandManager = new ApplicationCommandManager();
52 
53             // first, make sure the command manager has registered all the commands that its
54             // targets can perform..
55             myCommandManager->registerAllCommandsForTarget (myCommandTarget1);
56             myCommandManager->registerAllCommandsForTarget (myCommandTarget2);
57 
58             // this will use the command manager to initialise the KeyPressMappingSet with
59             // the default keypresses that were specified when the targets added their commands
60             // to the manager.
61             myCommandManager->getKeyMappings()->resetToDefaultMappings();
62 
63             // having set up the default key-mappings, you might now want to load the last set
64             // of mappings that the user configured.
65             myCommandManager->getKeyMappings()->restoreFromXml (lastSavedKeyMappingsXML);
66 
67             // Now tell our top-level window to send any keypresses that arrive to the
68             // KeyPressMappingSet, which will use them to invoke the appropriate commands.
69             addKeyListener (myCommandManager->getKeyMappings());
70         }
71 
72         ...
73     }
74     @endcode
75 
76     KeyPressMappingSet derives from ChangeBroadcaster so that interested parties can
77     register to be told when a command or mapping is added, removed, etc.
78 
79     There's also a UI component called KeyMappingEditorComponent that can be used
80     to easily edit the key mappings.
81 
82     @see Component::addKeyListener(), KeyMappingEditorComponent, ApplicationCommandManager
83 
84     @tags{GUI}
85 */
86 class JUCE_API  KeyPressMappingSet  : public KeyListener,
87                                       public ChangeBroadcaster,
88                                       private FocusChangeListener
89 {
90 public:
91     //==============================================================================
92     /** Creates a KeyPressMappingSet for a given command manager.
93 
94         Normally, you won't actually create a KeyPressMappingSet directly, because
95         each ApplicationCommandManager contains its own KeyPressMappingSet, so the
96         best thing to do is to create your ApplicationCommandManager, and use the
97         ApplicationCommandManager::getKeyMappings() method to access its mappings.
98 
99         When a suitable keypress happens, the manager's invoke() method will be
100         used to invoke the appropriate command.
101 
102         @see ApplicationCommandManager
103     */
104     explicit KeyPressMappingSet (ApplicationCommandManager&);
105 
106     /** Creates an copy of a KeyPressMappingSet. */
107     KeyPressMappingSet (const KeyPressMappingSet&);
108 
109     /** Destructor. */
110     ~KeyPressMappingSet() override;
111 
112     //==============================================================================
getCommandManager()113     ApplicationCommandManager& getCommandManager() const noexcept       { return commandManager; }
114 
115     //==============================================================================
116     /** Returns a list of keypresses that are assigned to a particular command.
117 
118         @param commandID        the command's ID
119     */
120     Array<KeyPress> getKeyPressesAssignedToCommand (CommandID commandID) const;
121 
122     /** Assigns a keypress to a command.
123 
124         If the keypress is already assigned to a different command, it will first be
125         removed from that command, to avoid it triggering multiple functions.
126 
127         @param commandID    the ID of the command that you want to add a keypress to. If
128                             this is 0, the keypress will be removed from anything that it
129                             was previously assigned to, but not re-assigned
130         @param newKeyPress  the new key-press
131         @param insertIndex  if this is less than zero, the key will be appended to the
132                             end of the list of keypresses; otherwise the new keypress will
133                             be inserted into the existing list at this index
134     */
135     void addKeyPress (CommandID commandID,
136                       const KeyPress& newKeyPress,
137                       int insertIndex = -1);
138 
139     /** Reset all mappings to the defaults, as dictated by the ApplicationCommandManager.
140         @see resetToDefaultMapping
141     */
142     void resetToDefaultMappings();
143 
144     /** Resets all key-mappings to the defaults for a particular command.
145         @see resetToDefaultMappings
146     */
147     void resetToDefaultMapping (CommandID commandID);
148 
149     /** Removes all keypresses that are assigned to any commands. */
150     void clearAllKeyPresses();
151 
152     /** Removes all keypresses that are assigned to a particular command. */
153     void clearAllKeyPresses (CommandID commandID);
154 
155     /** Removes one of the keypresses that are assigned to a command.
156         See the getKeyPressesAssignedToCommand() for the list of keypresses to
157         which the keyPressIndex refers.
158     */
159     void removeKeyPress (CommandID commandID, int keyPressIndex);
160 
161     /** Removes a keypress from any command that it may be assigned to. */
162     void removeKeyPress (const KeyPress& keypress);
163 
164     /** Returns true if the given command is linked to this key. */
165     bool containsMapping (CommandID commandID, const KeyPress& keyPress) const noexcept;
166 
167     //==============================================================================
168     /** Looks for a command that corresponds to a keypress.
169         @returns the UID of the command or 0 if none was found
170     */
171     CommandID findCommandForKeyPress (const KeyPress& keyPress) const noexcept;
172 
173     //==============================================================================
174     /** Tries to recreate the mappings from a previously stored state.
175 
176         The XML passed in must have been created by the createXml() method.
177 
178         If the stored state makes any reference to commands that aren't
179         currently available, these will be ignored.
180 
181         If the set of mappings being loaded was a set of differences (using createXml (true)),
182         then this will call resetToDefaultMappings() and then merge the saved mappings
183         on top. If the saved set was created with createXml (false), then this method
184         will first clear all existing mappings and load the saved ones as a complete set.
185 
186         @returns true if it manages to load the XML correctly
187         @see createXml
188     */
189     bool restoreFromXml (const XmlElement& xmlVersion);
190 
191     /** Creates an XML representation of the current mappings.
192 
193         This will produce a lump of XML that can be later reloaded using
194         restoreFromXml() to recreate the current mapping state.
195 
196         @param saveDifferencesFromDefaultSet    if this is false, then all keypresses
197                             will be saved into the XML. If it's true, then the XML will
198                             only store the differences between the current mappings and
199                             the default mappings you'd get from calling resetToDefaultMappings().
200                             The advantage of saving a set of differences from the default is that
201                             if you change the default mappings (in a new version of your app, for
202                             example), then these will be merged into a user's saved preferences.
203 
204         @see restoreFromXml
205     */
206     std::unique_ptr<XmlElement> createXml (bool saveDifferencesFromDefaultSet) const;
207 
208     //==============================================================================
209     /** @internal */
210     bool keyPressed (const KeyPress&, Component*) override;
211     /** @internal */
212     bool keyStateChanged (bool isKeyDown, Component*) override;
213     /** @internal */
214     void globalFocusChanged (Component*) override;
215 
216 private:
217     //==============================================================================
218     ApplicationCommandManager& commandManager;
219 
220     struct CommandMapping
221     {
222         CommandID commandID;
223         Array<KeyPress> keypresses;
224         bool wantsKeyUpDownCallbacks;
225     };
226 
227     OwnedArray<CommandMapping> mappings;
228 
229     struct KeyPressTime
230     {
231         KeyPress key;
232         uint32 timeWhenPressed;
233     };
234 
235     OwnedArray<KeyPressTime> keysDown;
236 
237     void invokeCommand (const CommandID, const KeyPress&, const bool isKeyDown,
238                         const int millisecsSinceKeyPressed, Component* originator) const;
239 
240     KeyPressMappingSet& operator= (const KeyPressMappingSet&);
241     JUCE_LEAK_DETECTOR (KeyPressMappingSet)
242 };
243 
244 } // namespace juce
245