2 ==============================================================================
4 This file is part of the JUCE library - "Jules' Utility Class Extensions"
5 Copyright 2004-11 by Raw Material Software Ltd.
7 ------------------------------------------------------------------------------
9 JUCE can be redistributed and/or modified under the terms of the GNU General
10 Public License (Version 2), as published by the Free Software Foundation.
11 A copy of the license is included in the JUCE distribution, or can be found
12 online at www.gnu.org/licenses.
14 JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
15 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 A PARTICULAR PURPOSE. See the GNU General Public License for more details.
18 ------------------------------------------------------------------------------
20 To release a closed-source product which uses JUCE, commercial licenses are
21 available: visit www.rawmaterialsoftware.com/juce for more information.
23 ==============================================================================
26 #include "../../../core/juce_StandardHeader.h"
30 #include "juce_KeyPressMappingSet.h"
31 #include "../../../core/juce_Time.h"
32 #include "../../../core/juce_PlatformUtilities.h"
33 #include "../lookandfeel/juce_LookAndFeel.h"
36 //==============================================================================
37 KeyPressMappingSet::KeyPressMappingSet (ApplicationCommandManager
* const commandManager_
)
38 : commandManager (commandManager_
)
40 // A manager is needed to get the descriptions of commands, and will be called when
41 // a command is invoked. So you can't leave this null..
42 jassert (commandManager_
!= nullptr);
44 Desktop::getInstance().addFocusChangeListener (this);
47 KeyPressMappingSet::KeyPressMappingSet (const KeyPressMappingSet
& other
)
48 : commandManager (other
.commandManager
)
50 Desktop::getInstance().addFocusChangeListener (this);
53 KeyPressMappingSet::~KeyPressMappingSet()
55 Desktop::getInstance().removeFocusChangeListener (this);
58 //==============================================================================
59 const Array
<KeyPress
> KeyPressMappingSet::getKeyPressesAssignedToCommand (const CommandID commandID
) const
61 for (int i
= 0; i
< mappings
.size(); ++i
)
62 if (mappings
.getUnchecked(i
)->commandID
== commandID
)
63 return mappings
.getUnchecked (i
)->keypresses
;
65 return Array
<KeyPress
> ();
68 void KeyPressMappingSet::addKeyPress (const CommandID commandID
,
69 const KeyPress
& newKeyPress
,
72 // If you specify an upper-case letter but no shift key, how is the user supposed to press it!?
73 // Stick to lower-case letters when defining a keypress, to avoid ambiguity.
74 jassert (! (CharacterFunctions::isUpperCase (newKeyPress
.getTextCharacter())
75 && ! newKeyPress
.getModifiers().isShiftDown()));
77 if (findCommandForKeyPress (newKeyPress
) != commandID
)
79 removeKeyPress (newKeyPress
);
81 if (newKeyPress
.isValid())
83 for (int i
= mappings
.size(); --i
>= 0;)
85 if (mappings
.getUnchecked(i
)->commandID
== commandID
)
87 mappings
.getUnchecked(i
)->keypresses
.insert (insertIndex
, newKeyPress
);
94 const ApplicationCommandInfo
* const ci
= commandManager
->getCommandForID (commandID
);
98 CommandMapping
* const cm
= new CommandMapping();
99 cm
->commandID
= commandID
;
100 cm
->keypresses
.add (newKeyPress
);
101 cm
->wantsKeyUpDownCallbacks
= (ci
->flags
& ApplicationCommandInfo::wantsKeyUpDownCallbacks
) != 0;
110 void KeyPressMappingSet::resetToDefaultMappings()
114 for (int i
= 0; i
< commandManager
->getNumCommands(); ++i
)
116 const ApplicationCommandInfo
* const ci
= commandManager
->getCommandForIndex (i
);
118 for (int j
= 0; j
< ci
->defaultKeypresses
.size(); ++j
)
120 addKeyPress (ci
->commandID
,
121 ci
->defaultKeypresses
.getReference (j
));
128 void KeyPressMappingSet::resetToDefaultMapping (const CommandID commandID
)
130 clearAllKeyPresses (commandID
);
132 const ApplicationCommandInfo
* const ci
= commandManager
->getCommandForID (commandID
);
134 for (int j
= 0; j
< ci
->defaultKeypresses
.size(); ++j
)
136 addKeyPress (ci
->commandID
,
137 ci
->defaultKeypresses
.getReference (j
));
141 void KeyPressMappingSet::clearAllKeyPresses()
143 if (mappings
.size() > 0)
150 void KeyPressMappingSet::clearAllKeyPresses (const CommandID commandID
)
152 for (int i
= mappings
.size(); --i
>= 0;)
154 if (mappings
.getUnchecked(i
)->commandID
== commandID
)
162 void KeyPressMappingSet::removeKeyPress (const KeyPress
& keypress
)
164 if (keypress
.isValid())
166 for (int i
= mappings
.size(); --i
>= 0;)
168 CommandMapping
* const cm
= mappings
.getUnchecked(i
);
170 for (int j
= cm
->keypresses
.size(); --j
>= 0;)
172 if (keypress
== cm
->keypresses
[j
])
174 cm
->keypresses
.remove (j
);
182 void KeyPressMappingSet::removeKeyPress (const CommandID commandID
, const int keyPressIndex
)
184 for (int i
= mappings
.size(); --i
>= 0;)
186 if (mappings
.getUnchecked(i
)->commandID
== commandID
)
188 mappings
.getUnchecked(i
)->keypresses
.remove (keyPressIndex
);
195 //==============================================================================
196 CommandID
KeyPressMappingSet::findCommandForKeyPress (const KeyPress
& keyPress
) const noexcept
198 for (int i
= 0; i
< mappings
.size(); ++i
)
199 if (mappings
.getUnchecked(i
)->keypresses
.contains (keyPress
))
200 return mappings
.getUnchecked(i
)->commandID
;
205 bool KeyPressMappingSet::containsMapping (const CommandID commandID
, const KeyPress
& keyPress
) const noexcept
207 for (int i
= mappings
.size(); --i
>= 0;)
208 if (mappings
.getUnchecked(i
)->commandID
== commandID
)
209 return mappings
.getUnchecked(i
)->keypresses
.contains (keyPress
);
214 void KeyPressMappingSet::invokeCommand (const CommandID commandID
,
216 const bool isKeyDown
,
217 const int millisecsSinceKeyPressed
,
218 Component
* const originatingComponent
) const
220 ApplicationCommandTarget::InvocationInfo
info (commandID
);
222 info
.invocationMethod
= ApplicationCommandTarget::InvocationInfo::fromKeyPress
;
223 info
.isKeyDown
= isKeyDown
;
225 info
.millisecsSinceKeyPressed
= millisecsSinceKeyPressed
;
226 info
.originatingComponent
= originatingComponent
;
228 commandManager
->invoke (info
, false);
231 //==============================================================================
232 bool KeyPressMappingSet::restoreFromXml (const XmlElement
& xmlVersion
)
234 if (xmlVersion
.hasTagName ("KEYMAPPINGS"))
236 if (xmlVersion
.getBoolAttribute ("basedOnDefaults", true))
238 // if the XML was created as a set of differences from the default mappings,
239 // (i.e. by calling createXml (true)), then we need to first restore the defaults.
240 resetToDefaultMappings();
244 // if the XML was created calling createXml (false), then we need to clear all
245 // the keys and treat the xml as describing the entire set of mappings.
246 clearAllKeyPresses();
249 forEachXmlChildElement (xmlVersion
, map
)
251 const CommandID commandId
= map
->getStringAttribute ("commandId").getHexValue32();
255 const KeyPress
key (KeyPress::createFromDescription (map
->getStringAttribute ("key")));
257 if (map
->hasTagName ("MAPPING"))
259 addKeyPress (commandId
, key
);
261 else if (map
->hasTagName ("UNMAPPING"))
263 if (containsMapping (commandId
, key
))
264 removeKeyPress (key
);
275 XmlElement
* KeyPressMappingSet::createXml (const bool saveDifferencesFromDefaultSet
) const
277 ScopedPointer
<KeyPressMappingSet
> defaultSet
;
279 if (saveDifferencesFromDefaultSet
)
281 defaultSet
= new KeyPressMappingSet (commandManager
);
282 defaultSet
->resetToDefaultMappings();
285 XmlElement
* const doc
= new XmlElement ("KEYMAPPINGS");
287 doc
->setAttribute ("basedOnDefaults", saveDifferencesFromDefaultSet
);
290 for (i
= 0; i
< mappings
.size(); ++i
)
292 const CommandMapping
* const cm
= mappings
.getUnchecked(i
);
294 for (int j
= 0; j
< cm
->keypresses
.size(); ++j
)
296 if (defaultSet
== nullptr
297 || ! defaultSet
->containsMapping (cm
->commandID
, cm
->keypresses
.getReference (j
)))
299 XmlElement
* const map
= doc
->createNewChildElement ("MAPPING");
301 map
->setAttribute ("commandId", String::toHexString ((int) cm
->commandID
));
302 map
->setAttribute ("description", commandManager
->getDescriptionOfCommand (cm
->commandID
));
303 map
->setAttribute ("key", cm
->keypresses
.getReference (j
).getTextDescription());
308 if (defaultSet
!= nullptr)
310 for (i
= 0; i
< defaultSet
->mappings
.size(); ++i
)
312 const CommandMapping
* const cm
= defaultSet
->mappings
.getUnchecked(i
);
314 for (int j
= 0; j
< cm
->keypresses
.size(); ++j
)
316 if (! containsMapping (cm
->commandID
, cm
->keypresses
.getReference (j
)))
318 XmlElement
* const map
= doc
->createNewChildElement ("UNMAPPING");
320 map
->setAttribute ("commandId", String::toHexString ((int) cm
->commandID
));
321 map
->setAttribute ("description", commandManager
->getDescriptionOfCommand (cm
->commandID
));
322 map
->setAttribute ("key", cm
->keypresses
.getReference (j
).getTextDescription());
331 //==============================================================================
332 bool KeyPressMappingSet::keyPressed (const KeyPress
& key
,
333 Component
* originatingComponent
)
337 const CommandID commandID
= findCommandForKeyPress (key
);
339 const ApplicationCommandInfo
* const ci
= commandManager
->getCommandForID (commandID
);
342 && (ci
->flags
& ApplicationCommandInfo::wantsKeyUpDownCallbacks
) == 0)
344 ApplicationCommandInfo
info (0);
346 if (commandManager
->getTargetForCommand (commandID
, info
) != 0
347 && (info
.flags
& ApplicationCommandInfo::isDisabled
) == 0)
349 invokeCommand (commandID
, key
, true, 0, originatingComponent
);
354 if (originatingComponent
!= nullptr)
355 originatingComponent
->getLookAndFeel().playAlertSound();
362 bool KeyPressMappingSet::keyStateChanged (const bool /*isKeyDown*/, Component
* originatingComponent
)
365 const uint32 now
= Time::getMillisecondCounter();
367 for (int i
= mappings
.size(); --i
>= 0;)
369 CommandMapping
* const cm
= mappings
.getUnchecked(i
);
371 if (cm
->wantsKeyUpDownCallbacks
)
373 for (int j
= cm
->keypresses
.size(); --j
>= 0;)
375 const KeyPress
key (cm
->keypresses
.getReference (j
));
376 const bool isDown
= key
.isCurrentlyDown();
378 int keyPressEntryIndex
= 0;
379 bool wasDown
= false;
381 for (int k
= keysDown
.size(); --k
>= 0;)
383 if (key
== keysDown
.getUnchecked(k
)->key
)
385 keyPressEntryIndex
= k
;
392 if (isDown
!= wasDown
)
398 KeyPressTime
* const k
= new KeyPressTime();
400 k
->timeWhenPressed
= now
;
406 const uint32 pressTime
= keysDown
.getUnchecked (keyPressEntryIndex
)->timeWhenPressed
;
409 millisecs
= now
- pressTime
;
411 keysDown
.remove (keyPressEntryIndex
);
414 invokeCommand (cm
->commandID
, key
, isDown
, millisecs
, originatingComponent
);
424 void KeyPressMappingSet::globalFocusChanged (Component
* focusedComponent
)
426 if (focusedComponent
!= nullptr)
427 focusedComponent
->keyStateChanged (false);