/*
  ==============================================================================

   This file is part of the JUCE library - "Jules' Utility Class Extensions"
   Copyright 2004-6 by Raw Material Software ltd.

  ------------------------------------------------------------------------------

   JUCE can be redistributed and/or modified under the terms of the
   GNU General Public License, as published by the Free Software Foundation;
   either version 2 of the License, or (at your option) any later version.

   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.

   You should have received a copy of the GNU General Public License
   along with JUCE; if not, visit www.gnu.org/licenses or write to the
   Free Software Foundation, Inc., 59 Temple Place, Suite 330, 
   Boston, MA 02111-1307 USA

  ------------------------------------------------------------------------------

   If you'd like to release a closed-source product which uses JUCE, commercial
   licenses are also available: visit www.rawmaterialsoftware.com/juce for
   more information.

  ==============================================================================
*/

#ifndef __JUCE_KEYPRESSMAPPINGSET_JUCEHEADER__
#define __JUCE_KEYPRESSMAPPINGSET_JUCEHEADER__

#include "juce_KeyListener.h"
#include "../../../events/juce_ChangeBroadcaster.h"
#include "../../../../juce_core/text/juce_StringArray.h"
#include "../../../../juce_core/text/juce_XmlElement.h"
#include "../../../../juce_core/containers/juce_OwnedArray.h"
#include "../../../../juce_core/containers/juce_BitArray.h"


//==============================================================================
/** Callback information used as a parameter to a KeyPressCallbackFunction.

    @see KeyPressCallbackFunction, KeyPressMappingSet
*/
struct JUCE_API  KeyPressCallbackFunctionInfo
{
    /** The unique ID number of this command.

        @see KeyPressMappingSet::addCommand()
    */
    int commandUID;

    /** The description of the command that is being invoked */
    String description;

    /** The keypress that was used to invoke it.

        Note that this may not be a valid keypress if the command was invoked
        directly using KeyPressMappingSet::invokeCommand().
    */
    KeyPress keyPress;

    /** The component that the keypress occurred in.

        Note that this may be null if the command was invoked directly.
    */
    Component* componentWhereKeyWasPressed;

    /** 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;
};


//==============================================================================
/** Callback function used to invoke a command by the KeyPressMappingSet.

    @see KeyPressMappingSet::addCommand, KeyPressCallbackFunctionInfo
*/
typedef void (KeyPressCallbackFunction) (const KeyPressCallbackFunctionInfo& keyPressInfo);


//==============================================================================
/**
    Manages a list of named, categorised commands which are mapped onto keypresses.

    This object lets you create a list of named commands, with associated callback
    functions, and these can each be mapped onto one or more key presses.

    The commands can be invoked directly by calling invokeCommandForKeyPress() or
    invokeCommand(). They can also be invoked automatically by using the manager's
    KeyListener interface to listen for keypress events on one or more components.

    The object also 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
*/
class JUCE_API  KeyPressMappingSet  : public KeyListener,
                                      public ChangeBroadcaster,
                                      private MessageListener
{
public:
    //==============================================================================
    /** Creates an empty KeyPressMappingSet. */
    KeyPressMappingSet();

    /** Creates an copy of a KeyPressMappingSet. */
    KeyPressMappingSet (const KeyPressMappingSet& other);

    /** Destructor. */
    ~KeyPressMappingSet();

    //==============================================================================
    /** Adds a command to the list.

        @param commandUID           a unique identifier to represent this command - this must be
                                    unique amongst the commands that are registered. This value
                                    must not be zero
        @param commandDescription   the name of the new command - this should also be unique
        @param categoryName         a name for the category to which this command belongs - e.g.
                                    "editing functions", "file functions", etc. This is used
                                    to help key-mapping editors display lists of commands clearly.
        @param callbackFunction     the function to call when this command is invoked. It's ok to
                                    have multiple commands calling a single function
        @param wantsKeyUpDownCallbacks  if true, the callback function will be called both
                                        when its key is pressed and when it is released. The
                                        KeyPressCallbackFunctionInfo structure that is passed
                                        to the callback function contains information about
                                        which event is happening
        @param shouldBeVisibleInKeymapEditor    if false, this command is flagged as being internal,
                                        and won't be displayed in a KeyMappingEditorComponent. See also
                                        shouldCommandBeVisibleInEditor() and KeyMappingEditorComponent::shouldCommandBeIncluded()
        @param shouldCommandBeReadOnlyInEditor  if true, the command won't be editable when shown
                                        in a KeyMappingEditorComponent. See also
                                        shouldCommandBeVisibleInEditor() and KeyMappingEditorComponent::isCommandReadOnly()
    */
    void addCommand (const int commandUID,
                     const String& commandDescription,
                     const String& categoryName,
                     KeyPressCallbackFunction* callbackFunction,
                     const bool wantsKeyUpDownCallbacks = false,
                     const bool shouldBeVisibleInKeymapEditor = true,
                     const bool shouldCommandBeReadOnlyInEditor = false);

    /** Removes a command from the list. */
    void removeCommand (const int commandUID);

    /** Clears the entire command list. */
    void removeAllCommands();

    //==============================================================================
    /** Returns the description for a command.

        @see addCommand()
    */
    const String getDescriptionOfCommand (const int commandUID) const throw();

    /** Returns the list of categories.

        @see getCommandsInCategory()
    */
    const StringArray getCommandCategories() const throw();

    /** Returns a list of all the commands in a particular category.

        @see getCommandCategories()
    */
    const Array <int> getCommandsInCategory (const String& categoryName) const throw();

    /** Returns a list of the UIDs of all the commands that are registered. */
    const Array <int> getAllCommands() const throw();

    /** When a command is added, it can be flagged as not visible in an editor.

        @see addCommand
    */
    bool shouldCommandBeVisibleInEditor (const int commandUID) const;

    /** When a command is added, it can be flagged as read-only in an editor.

        @see addCommand
    */
    bool shouldCommandBeReadOnlyInEditor (const int commandUID) const;

    //==============================================================================
    /** Returns a list of keypresses that are assigned to a particular command.

        @param commandUID       the command's ID
        @param keyPresses       on return, this array will have had zero or more
                                KeyPress objects added to it, representing the
                                assigned keypresses. (This array will not be cleared
                                before these items are added)
        @returns the number of keypresses that were found
    */
    int getKeyPressesAssignedToCommand (const int commandUID,
                                        OwnedArray<KeyPress>& keyPresses) const throw();

    /** 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 commandUID   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 (const int commandUID,
                      const KeyPress& newKeyPress,
                      int insertIndex = -1);

    /** Removes all keypresses that are assigned to any commands. */
    void clearAllKeyPresses();

    /** Removes all keypresses that are assigned to a particular command. */
    void clearAllKeyPresses (const int commandUID);

    /** 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 (const int commandUID,
                         const 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 (const int commandUID,
                          const KeyPress& keyPress) const;

    //==============================================================================
    /** Tries to invoke the callback function for a command ID.

        @param commandUID                   the command to perform
        @param invokeLaterOnEventThread     if false, the command will be called
                                            synchronously before this method returns;
                                            if true, a message will be posted to invoke it
                                            asynchronously on the message thread, and the
                                            method will return immediately
        @returns true if a suitable command was found and invoked
        @see isSafeToInvokeCallbacks, invokeCommandForKeyPress
    */
    bool invokeCommand (const int commandUID,
                        const bool invokeLaterOnEventThread = false) const;

    /** Tries to invoke the callback function for a given keypress.

        @returns true if a suitable command was found and invoked
        @see isSafeToInvokeCallbacks, invokeCommand
    */
    bool invokeCommandForKeyPress (const KeyPress& keyPress) const;

    /** Looks for a command that corresponds to a keypress.

        @returns the UID of the command or 0 if none was found
    */
    int findCommandForKeyPress (const KeyPress& keyPress) const;

    //==============================================================================
    /** Tries to recreate the mappings from a previously stored state.

        The XML passed in must have been created by the createMemento() method.

        If the stored state makes any reference to commands that aren't
        currently available, these will be ignored.

        The KeyPressMappingSet's current state will not be cleared before
        adding the new mappings, so if you're loading a user's keymaps, you
        can set up the default key mappings before calling this function, and
        only those mappings that are explicitly set in the XML will be changed.

        @returns true if it manages to load the XML correctly
        @see createMemento
    */
    bool restoreFromMemento (const XmlElement& xmlVersion);

    /** Creates an XML representation of the current mappings.

        This will produce a lump of XML that can be later reloaded using
        restoreFromMemento() to recreate the current mapping state.

        The object that is returned must be deleted by the caller.

        @param defaultSet   if not 0, then any keypresses in this default set
                            are not stored in the memento. This allows you to
                            store just the difference between a default set of
                            mappings, and the user's set. When reloading, you
                            can restore your default set, then restoreFromMemento()
                            over the top, so that if you add new mappings to your
                            default set (in a new version of your app, for example),
                            then these will be merged into the active set, as long as
                            the stored memento doesn't overridde those keys.

        @see flagCurrentStateAsDefault, restoreFromMemento
    */
    XmlElement* createMemento (const KeyPressMappingSet* defaultSet) const;


    //==============================================================================
    /** @internal */
    void keyPressed (const KeyPress& key, Component* originatingComponent);
    /** @internal */
    void keyStateChanged (Component* originatingComponent);


    //==============================================================================
    juce_UseDebuggingNewOperator

protected:
    //==============================================================================
    /** Allows a subclass to decide whether it's a good time to allow a particular
        command to be invoked.

        Whenever a command is to be invoked, this method is checked first, and if
        it returns false, the callback will not be made.

        By default this just returns true, but a subclass may want to stop callbacks
        happening at certain times, e.g. when there's a modal window up or some kind
        of task is busy running.
    */
    virtual bool isSafeToInvokeCommand (const int commandUID) const;


private:
    //==============================================================================
    struct CommandInfo
    {
        CommandInfo();
        CommandInfo (const CommandInfo& other);

        int commandUID;
        String description, category;
        KeyPressCallbackFunction* callbackFunction;
        bool wantsKeyUpDownCallbacks : 1;
        bool shouldBeVisibleInKeymapEditor : 1;
        bool shouldCommandBeReadOnlyInEditor : 1;

        OwnedArray<KeyPress> keyPresses;

    private:
        const CommandInfo& operator= (const CommandInfo&);
    };

    OwnedArray<CommandInfo> commands;

    struct KeyPressTime
    {
        KeyPress key;
        uint32 timeWhenPressed;
    };

    OwnedArray<KeyPressTime> keysDown;

    CommandInfo* findCommandForID (const int commandUID) const throw();
    CommandInfo* findCommandForKeyPressInt (const KeyPress& p) const throw();

    void handleMessage (const Message& message);

    const KeyPressMappingSet& operator= (const KeyPressMappingSet&);
};


#endif   // __JUCE_KEYPRESSMAPPINGSET_JUCEHEADER__
