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 // N.B. these two includes are put here deliberately to avoid problems with
31 // old GCCs failing on long include paths
32 #include "../../../containers/juce_Array.h"
33 #include "../../../containers/juce_OwnedArray.h"
35 #include "juce_KeyMappingEditorComponent.h"
36 #include "../menus/juce_PopupMenu.h"
37 #include "../windows/juce_AlertWindow.h"
38 #include "../lookandfeel/juce_LookAndFeel.h"
39 #include "../../../text/juce_LocalisedStrings.h"
42 //==============================================================================
43 class KeyMappingEditorComponent::ChangeKeyButton
: public Button
46 ChangeKeyButton (KeyMappingEditorComponent
& owner_
,
47 const CommandID commandID_
,
48 const String
& keyName
,
52 commandID (commandID_
),
55 setWantsKeyboardFocus (false);
56 setTriggeredOnMouseDown (keyNum
>= 0);
58 setTooltip (keyNum_
< 0 ? TRANS("adds a new key-mapping")
59 : TRANS("click to change this key-mapping"));
62 void paintButton (Graphics
& g
, bool /*isOver*/, bool /*isDown*/)
64 getLookAndFeel().drawKeymapChangeButton (g
, getWidth(), getHeight(), *this,
65 keyNum
>= 0 ? getName() : String::empty
);
68 static void menuCallback (int result
, ChangeKeyButton
* button
)
70 if (button
!= nullptr)
74 case 1: button
->assignNewKey(); break;
75 case 2: button
->owner
.getMappings().removeKeyPress (button
->commandID
, button
->keyNum
); break;
85 // existing key clicked..
87 m
.addItem (1, TRANS("change this key-mapping"));
89 m
.addItem (2, TRANS("remove this key-mapping"));
91 m
.showMenuAsync (PopupMenu::Options(),
92 ModalCallbackFunction::forComponent (menuCallback
, this));
96 assignNewKey(); // + button pressed..
100 void fitToContent (const int h
) noexcept
109 setSize (jlimit (h
* 4, h
* 8, 6 + f
.getStringWidth (getName())), h
);
113 //==============================================================================
114 class KeyEntryWindow
: public AlertWindow
117 KeyEntryWindow (KeyMappingEditorComponent
& owner_
)
118 : AlertWindow (TRANS("New key-mapping"),
119 TRANS("Please press a key combination now..."),
120 AlertWindow::NoIcon
),
123 addButton (TRANS("Ok"), 1);
124 addButton (TRANS("Cancel"), 0);
126 // (avoid return + escape keys getting processed by the buttons..)
127 for (int i
= getNumChildComponents(); --i
>= 0;)
128 getChildComponent (i
)->setWantsKeyboardFocus (false);
130 setWantsKeyboardFocus (true);
134 bool keyPressed (const KeyPress
& key
)
137 String
message (TRANS("Key: ") + owner
.getDescriptionForKeyPress (key
));
139 const CommandID previousCommand
= owner
.getMappings().findCommandForKeyPress (key
);
141 if (previousCommand
!= 0)
142 message
<< "\n\n" << TRANS("(Currently assigned to \"")
143 << owner
.getMappings().getCommandManager()->getNameOfCommand (previousCommand
) << "\")";
145 setMessage (message
);
149 bool keyStateChanged (bool)
157 KeyMappingEditorComponent
& owner
;
159 JUCE_DECLARE_NON_COPYABLE (KeyEntryWindow
);
162 static void assignNewKeyCallback (int result
, ChangeKeyButton
* button
, KeyPress newKey
)
164 if (result
!= 0 && button
!= nullptr)
165 button
->setNewKey (newKey
, true);
168 void setNewKey (const KeyPress
& newKey
, bool dontAskUser
)
170 if (newKey
.isValid())
172 const CommandID previousCommand
= owner
.getMappings().findCommandForKeyPress (newKey
);
174 if (previousCommand
== 0 || dontAskUser
)
176 owner
.getMappings().removeKeyPress (newKey
);
179 owner
.getMappings().removeKeyPress (commandID
, keyNum
);
181 owner
.getMappings().addKeyPress (commandID
, newKey
, keyNum
);
185 AlertWindow::showOkCancelBox (AlertWindow::WarningIcon
,
186 TRANS("Change key-mapping"),
187 TRANS("This key is already assigned to the command \"")
188 + owner
.getMappings().getCommandManager()->getNameOfCommand (previousCommand
)
189 + TRANS("\"\n\nDo you want to re-assign it to this new command instead?"),
193 ModalCallbackFunction::forComponent (assignNewKeyCallback
,
194 this, KeyPress (newKey
)));
199 static void keyChosen (int result
, ChangeKeyButton
* button
)
201 if (result
!= 0 && button
!= nullptr && button
->currentKeyEntryWindow
!= nullptr)
203 button
->currentKeyEntryWindow
->setVisible (false);
204 button
->setNewKey (button
->currentKeyEntryWindow
->lastPress
, false);
207 button
->currentKeyEntryWindow
= nullptr;
212 currentKeyEntryWindow
= new KeyEntryWindow (owner
);
213 currentKeyEntryWindow
->enterModalState (true, ModalCallbackFunction::forComponent (keyChosen
, this));
217 KeyMappingEditorComponent
& owner
;
218 const CommandID commandID
;
220 ScopedPointer
<KeyEntryWindow
> currentKeyEntryWindow
;
222 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChangeKeyButton
);
225 //==============================================================================
226 class KeyMappingEditorComponent::ItemComponent
: public Component
229 ItemComponent (KeyMappingEditorComponent
& owner_
, const CommandID commandID_
)
230 : owner (owner_
), commandID (commandID_
)
232 setInterceptsMouseClicks (false, true);
234 const bool isReadOnly
= owner
.isCommandReadOnly (commandID
);
236 const Array
<KeyPress
> keyPresses (owner
.getMappings().getKeyPressesAssignedToCommand (commandID
));
238 for (int i
= 0; i
< jmin ((int) maxNumAssignments
, keyPresses
.size()); ++i
)
239 addKeyPressButton (owner
.getDescriptionForKeyPress (keyPresses
.getReference (i
)), i
, isReadOnly
);
241 addKeyPressButton (String::empty
, -1, isReadOnly
);
244 void addKeyPressButton (const String
& desc
, const int index
, const bool isReadOnly
)
246 ChangeKeyButton
* const b
= new ChangeKeyButton (owner
, commandID
, desc
, index
);
247 keyChangeButtons
.add (b
);
249 b
->setEnabled (! isReadOnly
);
250 b
->setVisible (keyChangeButtons
.size() <= (int) maxNumAssignments
);
251 addChildComponent (b
);
254 void paint (Graphics
& g
)
256 g
.setFont (getHeight() * 0.7f
);
257 g
.setColour (findColour (KeyMappingEditorComponent::textColourId
));
259 g
.drawFittedText (owner
.getMappings().getCommandManager()->getNameOfCommand (commandID
),
260 4, 0, jmax (40, getChildComponent (0)->getX() - 5), getHeight(),
261 Justification::centredLeft
, true);
266 int x
= getWidth() - 4;
268 for (int i
= keyChangeButtons
.size(); --i
>= 0;)
270 ChangeKeyButton
* const b
= keyChangeButtons
.getUnchecked(i
);
272 b
->fitToContent (getHeight() - 2);
273 b
->setTopRightPosition (x
, 1);
279 KeyMappingEditorComponent
& owner
;
280 OwnedArray
<ChangeKeyButton
> keyChangeButtons
;
281 const CommandID commandID
;
283 enum { maxNumAssignments
= 3 };
285 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ItemComponent
);
288 //==============================================================================
289 class KeyMappingEditorComponent::MappingItem
: public TreeViewItem
292 MappingItem (KeyMappingEditorComponent
& owner_
, const CommandID commandID_
)
293 : owner (owner_
), commandID (commandID_
)
297 const String
getUniqueName() const { return String ((int) commandID
) + "_id"; }
298 bool mightContainSubItems() { return false; }
299 int getItemHeight() const { return 20; }
301 Component
* createItemComponent()
303 return new ItemComponent (owner
, commandID
);
307 KeyMappingEditorComponent
& owner
;
308 const CommandID commandID
;
310 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MappingItem
);
314 //==============================================================================
315 class KeyMappingEditorComponent::CategoryItem
: public TreeViewItem
318 CategoryItem (KeyMappingEditorComponent
& owner_
, const String
& name
)
319 : owner (owner_
), categoryName (name
)
323 const String
getUniqueName() const { return categoryName
+ "_cat"; }
324 bool mightContainSubItems() { return true; }
325 int getItemHeight() const { return 28; }
327 void paintItem (Graphics
& g
, int width
, int height
)
329 g
.setFont (height
* 0.6f
, Font::bold
);
330 g
.setColour (owner
.findColour (KeyMappingEditorComponent::textColourId
));
332 g
.drawText (categoryName
,
333 2, 0, width
- 2, height
,
334 Justification::centredLeft
, true);
337 void itemOpennessChanged (bool isNowOpen
)
341 if (getNumSubItems() == 0)
343 Array
<CommandID
> commands (owner
.getMappings().getCommandManager()->getCommandsInCategory (categoryName
));
345 for (int i
= 0; i
< commands
.size(); ++i
)
347 if (owner
.shouldCommandBeIncluded (commands
[i
]))
348 addSubItem (new MappingItem (owner
, commands
[i
]));
359 KeyMappingEditorComponent
& owner
;
362 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CategoryItem
);
365 //==============================================================================
366 class KeyMappingEditorComponent::TopLevelItem
: public TreeViewItem
,
367 public ChangeListener
,
368 public ButtonListener
371 TopLevelItem (KeyMappingEditorComponent
& owner_
)
374 setLinesDrawnForSubItems (false);
375 owner
.getMappings().addChangeListener (this);
380 owner
.getMappings().removeChangeListener (this);
383 bool mightContainSubItems() { return true; }
384 const String
getUniqueName() const { return "keys"; }
386 void changeListenerCallback (ChangeBroadcaster
*)
388 const OpennessRestorer
opennessRestorer (*this);
391 const StringArray
categories (owner
.getMappings().getCommandManager()->getCommandCategories());
393 for (int i
= 0; i
< categories
.size(); ++i
)
395 const Array
<CommandID
> commands (owner
.getMappings().getCommandManager()->getCommandsInCategory (categories
[i
]));
398 for (int j
= 0; j
< commands
.size(); ++j
)
399 if (owner
.shouldCommandBeIncluded (commands
[j
]))
403 addSubItem (new CategoryItem (owner
, categories
[i
]));
407 static void resetToDefaultsCallback (int result
, KeyMappingEditorComponent
* owner
)
409 if (result
!= 0 && owner
!= nullptr)
410 owner
->getMappings().resetToDefaultMappings();
413 void buttonClicked (Button
*)
415 AlertWindow::showOkCancelBox (AlertWindow::QuestionIcon
,
416 TRANS("Reset to defaults"),
417 TRANS("Are you sure you want to reset all the key-mappings to their default state?"),
421 ModalCallbackFunction::forComponent (resetToDefaultsCallback
, &owner
));
425 KeyMappingEditorComponent
& owner
;
429 //==============================================================================
430 KeyMappingEditorComponent::KeyMappingEditorComponent (KeyPressMappingSet
& mappingManager
,
431 const bool showResetToDefaultButton
)
432 : mappings (mappingManager
),
433 resetButton (TRANS ("reset to defaults"))
435 treeItem
= new TopLevelItem (*this);
437 if (showResetToDefaultButton
)
439 addAndMakeVisible (&resetButton
);
440 resetButton
.addListener (treeItem
);
443 addAndMakeVisible (&tree
);
444 tree
.setColour (TreeView::backgroundColourId
, findColour (backgroundColourId
));
445 tree
.setRootItemVisible (false);
446 tree
.setDefaultOpenness (true);
447 tree
.setRootItem (treeItem
);
450 KeyMappingEditorComponent::~KeyMappingEditorComponent()
452 tree
.setRootItem (nullptr);
455 //==============================================================================
456 void KeyMappingEditorComponent::setColours (const Colour
& mainBackground
,
457 const Colour
& textColour
)
459 setColour (backgroundColourId
, mainBackground
);
460 setColour (textColourId
, textColour
);
461 tree
.setColour (TreeView::backgroundColourId
, mainBackground
);
464 void KeyMappingEditorComponent::parentHierarchyChanged()
466 treeItem
->changeListenerCallback (nullptr);
469 void KeyMappingEditorComponent::resized()
473 if (resetButton
.isVisible())
475 const int buttonHeight
= 20;
476 h
-= buttonHeight
+ 8;
477 int x
= getWidth() - 8;
479 resetButton
.changeWidthToFitText (buttonHeight
);
480 resetButton
.setTopRightPosition (x
, h
+ 6);
483 tree
.setBounds (0, 0, getWidth(), h
);
486 //==============================================================================
487 bool KeyMappingEditorComponent::shouldCommandBeIncluded (const CommandID commandID
)
489 const ApplicationCommandInfo
* const ci
= mappings
.getCommandManager()->getCommandForID (commandID
);
491 return ci
!= nullptr && (ci
->flags
& ApplicationCommandInfo::hiddenFromKeyEditor
) == 0;
494 bool KeyMappingEditorComponent::isCommandReadOnly (const CommandID commandID
)
496 const ApplicationCommandInfo
* const ci
= mappings
.getCommandManager()->getCommandForID (commandID
);
498 return ci
!= nullptr && (ci
->flags
& ApplicationCommandInfo::readOnlyInKeyEditor
) != 0;
501 const String
KeyMappingEditorComponent::getDescriptionForKeyPress (const KeyPress
& key
)
503 return key
.getTextDescription();