/*
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
This file is part of the JUCE library .
Copyright ( c ) 2015 - ROLI Ltd .
Permission is granted to use this software under the terms of either :
a ) the GPL v2 ( or any later version )
b ) the Affero GPL v3
Details of these licenses can be found at : www . gnu . org / licenses
JUCE is distributed in the hope that it will be useful , but WITHOUT ANY
WARRANTY ; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE . See the GNU General Public License for more details .
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
To release a closed - source product which uses JUCE , commercial licenses are
available : visit www . juce . com for more information .
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
*/
class KeyMappingEditorComponent : : ChangeKeyButton : public Button
{
public :
ChangeKeyButton ( KeyMappingEditorComponent & kec , const CommandID command ,
const String & keyName , const int keyIndex )
: Button ( keyName ) ,
owner ( kec ) ,
commandID ( command ) ,
keyNum ( keyIndex )
{
setWantsKeyboardFocus ( false ) ;
setTriggeredOnMouseDown ( keyNum > = 0 ) ;
setTooltip ( keyIndex < 0 ? TRANS ( " Adds a new key-mapping " )
: TRANS ( " Click to change this key-mapping " ) ) ;
}
void paintButton ( Graphics & g , bool /*isOver*/ , bool /*isDown*/ ) override
{
getLookAndFeel ( ) . drawKeymapChangeButton ( g , getWidth ( ) , getHeight ( ) , * this ,
keyNum > = 0 ? getName ( ) : String : : empty ) ;
}
static void menuCallback ( int result , ChangeKeyButton * button )
{
if ( button ! = nullptr )
{
switch ( result )
{
case 1 : button - > assignNewKey ( ) ; break ;
case 2 : button - > owner . getMappings ( ) . removeKeyPress ( button - > commandID , button - > keyNum ) ; break ;
default : break ;
}
}
}
void clicked ( ) override
{
if ( keyNum > = 0 )
{
// existing key clicked..
PopupMenu m ;
m . addItem ( 1 , TRANS ( " Change this key-mapping " ) ) ;
m . addSeparator ( ) ;
m . addItem ( 2 , TRANS ( " Remove this key-mapping " ) ) ;
m . showMenuAsync ( PopupMenu : : Options ( ) ,
ModalCallbackFunction : : forComponent ( menuCallback , this ) ) ;
}
else
{
assignNewKey ( ) ; // + button pressed..
}
}
void fitToContent ( const int h ) noexcept
{
if ( keyNum < 0 )
setSize ( h , h ) ;
else
setSize ( jlimit ( h * 4 , h * 8 , 6 + Font ( h * 0.6f ) . getStringWidth ( getName ( ) ) ) , h ) ;
}
//==============================================================================
class KeyEntryWindow : public AlertWindow
{
public :
KeyEntryWindow ( KeyMappingEditorComponent & kec )
: AlertWindow ( TRANS ( " New key-mapping " ) ,
TRANS ( " Please press a key combination now... " ) ,
AlertWindow : : NoIcon ) ,
owner ( kec )
{
addButton ( TRANS ( " OK " ) , 1 ) ;
addButton ( TRANS ( " Cancel " ) , 0 ) ;
// (avoid return + escape keys getting processed by the buttons..)
for ( int i = getNumChildComponents ( ) ; - - i > = 0 ; )
getChildComponent ( i ) - > setWantsKeyboardFocus ( false ) ;
setWantsKeyboardFocus ( true ) ;
grabKeyboardFocus ( ) ;
}
bool keyPressed ( const KeyPress & key ) override
{
lastPress = key ;
String message ( TRANS ( " Key " ) + " : " + owner . getDescriptionForKeyPress ( key ) ) ;
const CommandID previousCommand = owner . getMappings ( ) . findCommandForKeyPress ( key ) ;
if ( previousCommand ! = 0 )
message < < " \n \n ( "
< < TRANS ( " Currently assigned to \" CMDN \" " )
. replace ( " CMDN " , TRANS ( owner . getCommandManager ( ) . getNameOfCommand ( previousCommand ) ) )
< < ' ) ' ;
setMessage ( message ) ;
return true ;
}
bool keyStateChanged ( bool ) override
{
return true ;
}
KeyPress lastPress ;
private :
KeyMappingEditorComponent & owner ;
JUCE_DECLARE_NON_COPYABLE ( KeyEntryWindow )
} ;
static void assignNewKeyCallback ( int result , ChangeKeyButton * button , KeyPress newKey )
{
if ( result ! = 0 & & button ! = nullptr )
button - > setNewKey ( newKey , true ) ;
}
void setNewKey ( const KeyPress & newKey , bool dontAskUser )
{
if ( newKey . isValid ( ) )
{
const CommandID previousCommand = owner . getMappings ( ) . findCommandForKeyPress ( newKey ) ;
if ( previousCommand = = 0 | | dontAskUser )
{
owner . getMappings ( ) . removeKeyPress ( newKey ) ;
if ( keyNum > = 0 )
owner . getMappings ( ) . removeKeyPress ( commandID , keyNum ) ;
owner . getMappings ( ) . addKeyPress ( commandID , newKey , keyNum ) ;
}
else
{
AlertWindow : : showOkCancelBox ( AlertWindow : : WarningIcon ,
TRANS ( " Change key-mapping " ) ,
TRANS ( " This key is already assigned to the command \" CMDN \" " )
. replace ( " CMDN " , owner . getCommandManager ( ) . getNameOfCommand ( previousCommand ) )
+ " \n \n "
+ TRANS ( " Do you want to re-assign it to this new command instead? " ) ,
TRANS ( " Re-assign " ) ,
TRANS ( " Cancel " ) ,
this ,
ModalCallbackFunction : : forComponent ( assignNewKeyCallback ,
this , KeyPress ( newKey ) ) ) ;
}
}
}
static void keyChosen ( int result , ChangeKeyButton * button )
{
if ( result ! = 0 & & button ! = nullptr & & button - > currentKeyEntryWindow ! = nullptr )
{
button - > currentKeyEntryWindow - > setVisible ( false ) ;
button - > setNewKey ( button - > currentKeyEntryWindow - > lastPress , false ) ;
}
button - > currentKeyEntryWindow = nullptr ;
}
void assignNewKey ( )
{
currentKeyEntryWindow = new KeyEntryWindow ( owner ) ;
currentKeyEntryWindow - > enterModalState ( true , ModalCallbackFunction : : forComponent ( keyChosen , this ) ) ;
}
private :
KeyMappingEditorComponent & owner ;
const CommandID commandID ;
const int keyNum ;
ScopedPointer < KeyEntryWindow > currentKeyEntryWindow ;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR ( ChangeKeyButton )
} ;
//==============================================================================
class KeyMappingEditorComponent : : ItemComponent : public Component
{
public :
ItemComponent ( KeyMappingEditorComponent & kec , const CommandID command )
: owner ( kec ) , commandID ( command )
{
setInterceptsMouseClicks ( false , true ) ;
const bool isReadOnly = owner . isCommandReadOnly ( commandID ) ;
const Array < KeyPress > keyPresses ( owner . getMappings ( ) . getKeyPressesAssignedToCommand ( commandID ) ) ;
for ( int i = 0 ; i < jmin ( ( int ) maxNumAssignments , keyPresses . size ( ) ) ; + + i )
addKeyPressButton ( owner . getDescriptionForKeyPress ( keyPresses . getReference ( i ) ) , i , isReadOnly ) ;
addKeyPressButton ( String : : empty , - 1 , isReadOnly ) ;
}
void addKeyPressButton ( const String & desc , const int index , const bool isReadOnly )
{
ChangeKeyButton * const b = new ChangeKeyButton ( owner , commandID , desc , index ) ;
keyChangeButtons . add ( b ) ;
b - > setEnabled ( ! isReadOnly ) ;
b - > setVisible ( keyChangeButtons . size ( ) < = ( int ) maxNumAssignments ) ;
addChildComponent ( b ) ;
}
void paint ( Graphics & g ) override
{
g . setFont ( getHeight ( ) * 0.7f ) ;
g . setColour ( owner . findColour ( KeyMappingEditorComponent : : textColourId ) ) ;
g . drawFittedText ( TRANS ( owner . getCommandManager ( ) . getNameOfCommand ( commandID ) ) ,
4 , 0 , jmax ( 40 , getChildComponent ( 0 ) - > getX ( ) - 5 ) , getHeight ( ) ,
Justification : : centredLeft , true ) ;
}
void resized ( ) override
{
int x = getWidth ( ) - 4 ;
for ( int i = keyChangeButtons . size ( ) ; - - i > = 0 ; )
{
ChangeKeyButton * const b = keyChangeButtons . getUnchecked ( i ) ;
b - > fitToContent ( getHeight ( ) - 2 ) ;
b - > setTopRightPosition ( x , 1 ) ;
x = b - > getX ( ) - 5 ;
}
}
private :
KeyMappingEditorComponent & owner ;
OwnedArray < ChangeKeyButton > keyChangeButtons ;
const CommandID commandID ;
enum { maxNumAssignments = 3 } ;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR ( ItemComponent )
} ;
//==============================================================================
class KeyMappingEditorComponent : : MappingItem : public TreeViewItem
{
public :
MappingItem ( KeyMappingEditorComponent & kec , const CommandID command )
: owner ( kec ) , commandID ( command )
{ }
String getUniqueName ( ) const override { return String ( ( int ) commandID ) + " _id " ; }
bool mightContainSubItems ( ) override { return false ; }
int getItemHeight ( ) const override { return 20 ; }
Component * createItemComponent ( ) override { return new ItemComponent ( owner , commandID ) ; }
private :
KeyMappingEditorComponent & owner ;
const CommandID commandID ;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR ( MappingItem )
} ;
//==============================================================================
class KeyMappingEditorComponent : : CategoryItem : public TreeViewItem
{
public :
CategoryItem ( KeyMappingEditorComponent & kec , const String & name )
: owner ( kec ) , categoryName ( name )
{ }
String getUniqueName ( ) const override { return categoryName + " _cat " ; }
bool mightContainSubItems ( ) override { return true ; }
int getItemHeight ( ) const override { return 22 ; }
void paintItem ( Graphics & g , int width , int height ) override
{
g . setFont ( Font ( height * 0.7f , Font : : bold ) ) ;
g . setColour ( owner . findColour ( KeyMappingEditorComponent : : textColourId ) ) ;
g . drawText ( TRANS ( categoryName ) , 2 , 0 , width - 2 , height , Justification : : centredLeft , true ) ;
}
void itemOpennessChanged ( bool isNowOpen ) override
{
if ( isNowOpen )
{
if ( getNumSubItems ( ) = = 0 )
{
const Array < CommandID > commands ( owner . getCommandManager ( ) . getCommandsInCategory ( categoryName ) ) ;
for ( int i = 0 ; i < commands . size ( ) ; + + i )
if ( owner . shouldCommandBeIncluded ( commands . getUnchecked ( i ) ) )
addSubItem ( new MappingItem ( owner , commands . getUnchecked ( i ) ) ) ;
}
}
else
{
clearSubItems ( ) ;
}
}
private :
KeyMappingEditorComponent & owner ;
String categoryName ;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR ( CategoryItem )
} ;
//==============================================================================
class KeyMappingEditorComponent : : TopLevelItem : public TreeViewItem ,
public ButtonListener ,
private ChangeListener
{
public :
TopLevelItem ( KeyMappingEditorComponent & kec ) : owner ( kec )
{
setLinesDrawnForSubItems ( false ) ;
owner . getMappings ( ) . addChangeListener ( this ) ;
}
~ TopLevelItem ( )
{
owner . getMappings ( ) . removeChangeListener ( this ) ;
}
bool mightContainSubItems ( ) override { return true ; }
String getUniqueName ( ) const override { return " keys " ; }
void changeListenerCallback ( ChangeBroadcaster * ) override
{
const OpennessRestorer opennessRestorer ( * this ) ;
clearSubItems ( ) ;
const StringArray categories ( owner . getCommandManager ( ) . getCommandCategories ( ) ) ;
for ( int i = 0 ; i < categories . size ( ) ; + + i )
{
const Array < CommandID > commands ( owner . getCommandManager ( ) . getCommandsInCategory ( categories [ i ] ) ) ;
int count = 0 ;
for ( int j = 0 ; j < commands . size ( ) ; + + j )
if ( owner . shouldCommandBeIncluded ( commands . getUnchecked ( j ) ) )
+ + count ;
if ( count > 0 )
addSubItem ( new CategoryItem ( owner , categories [ i ] ) ) ;
}
}
static void resetToDefaultsCallback ( int result , KeyMappingEditorComponent * owner )
{
if ( result ! = 0 & & owner ! = nullptr )
owner - > getMappings ( ) . resetToDefaultMappings ( ) ;
}
void buttonClicked ( Button * ) override
{
AlertWindow : : showOkCancelBox ( AlertWindow : : QuestionIcon ,
TRANS ( " Reset to defaults " ) ,
TRANS ( " Are you sure you want to reset all the key-mappings to their default state? " ) ,
TRANS ( " Reset " ) ,
String : : empty ,
& owner ,
ModalCallbackFunction : : forComponent ( resetToDefaultsCallback , & owner ) ) ;
}
private :
KeyMappingEditorComponent & owner ;
} ;
//==============================================================================
KeyMappingEditorComponent : : KeyMappingEditorComponent ( KeyPressMappingSet & mappingManager ,
const bool showResetToDefaultButton )
: mappings ( mappingManager ) ,
resetButton ( TRANS ( " reset to defaults " ) )
{
treeItem = new TopLevelItem ( * this ) ;
if ( showResetToDefaultButton )
{
addAndMakeVisible ( resetButton ) ;
resetButton . addListener ( treeItem ) ;
}
addAndMakeVisible ( tree ) ;
tree . setColour ( TreeView : : backgroundColourId , findColour ( backgroundColourId ) ) ;
tree . setRootItemVisible ( false ) ;
tree . setDefaultOpenness ( true ) ;
tree . setRootItem ( treeItem ) ;
tree . setIndentSize ( 12 ) ;
}
KeyMappingEditorComponent : : ~ KeyMappingEditorComponent ( )
{
tree . setRootItem ( nullptr ) ;
}
//==============================================================================
void KeyMappingEditorComponent : : setColours ( Colour mainBackground ,
Colour textColour )
{
setColour ( backgroundColourId , mainBackground ) ;
setColour ( textColourId , textColour ) ;
tree . setColour ( TreeView : : backgroundColourId , mainBackground ) ;
}
void KeyMappingEditorComponent : : parentHierarchyChanged ( )
{
treeItem - > changeListenerCallback ( nullptr ) ;
}
void KeyMappingEditorComponent : : resized ( )
{
int h = getHeight ( ) ;
if ( resetButton . isVisible ( ) )
{
const int buttonHeight = 20 ;
h - = buttonHeight + 8 ;
int x = getWidth ( ) - 8 ;
resetButton . changeWidthToFitText ( buttonHeight ) ;
resetButton . setTopRightPosition ( x , h + 6 ) ;
}
tree . setBounds ( 0 , 0 , getWidth ( ) , h ) ;
}
//==============================================================================
bool KeyMappingEditorComponent : : shouldCommandBeIncluded ( const CommandID commandID )
{
const ApplicationCommandInfo * const ci = mappings . getCommandManager ( ) . getCommandForID ( commandID ) ;
return ci ! = nullptr & & ( ci - > flags & ApplicationCommandInfo : : hiddenFromKeyEditor ) = = 0 ;
}
bool KeyMappingEditorComponent : : isCommandReadOnly ( const CommandID commandID )
{
const ApplicationCommandInfo * const ci = mappings . getCommandManager ( ) . getCommandForID ( commandID ) ;
return ci ! = nullptr & & ( ci - > flags & ApplicationCommandInfo : : readOnlyInKeyEditor ) ! = 0 ;
}
String KeyMappingEditorComponent : : getDescriptionForKeyPress ( const KeyPress & key )
{
return key . getTextDescription ( ) ;
}