# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is the Python Computer Graphics Kit.
#
# The Initial Developer of the Original Code is Matthias Baas.
# Portions created by the Initial Developer are Copyright (C) 2004
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****

## \file keys.py
## Contains the Keys class.

import wx
import string

# Exceptions:
class InvalidKeyDescription(Exception):
    """Exception class."""
    pass

class KeyNotBound(Exception):
    """Exception class."""
    pass

# Keys
class Keys(object):
    """This class manages key strokes.

    Key bindings correspond to special key attributes. The attribute
    name is the name of the key and the value is a callable object
    that takes no arguments.

    Example:

    \code
    >>> keys = Keys()

    # Bind functions to keys...
    >>> keys.ctrl_c = onExit
    keys.shift_ctrl_tab = onFoo

    # Remove a key binding
    >>> del keys.ctrl_c
    
    \endcode
    """

    def __init__(self):

        # Command dictionary. The keys are the tuples that contain the
        # modifier flags and the key code. The value is a callable object.
        self._commands = {}
        
        # A dictionary that translates wx key codes into a string description
        # The dictionary is built from the WXK_xyz constants
        self._wxkeystrs = {}
        wxkeys = filter(lambda x: x[0:4]=="WXK_", dir(wx))
        for k in wxkeys:
            code = getattr(wx, k)
            self._wxkeystrs[code] = k[4:].capitalize()

        # The Window object that uses this key manager
        self._window = None


    def attach(self, window):
        self._window = window
        wx.EVT_KEY_DOWN(window, self._onKeyDown)
#        wx.EVT_KEY_UP(window, self._onKeyUp)
#        wx.EVT_CHAR(window, self._onChar)

    def detach(self):
        pass

    def findCommandKeys(self, func):
        """Search the key table for a command and return all associated keys.

        \param func (\c callable) The bound function
        \return A list of readable key strings.
        """

        res = []
        # Compare all commands...
        for key in self._commands.iterkeys():
            cmd = self._commands[key]
            if func==cmd:
                res.append(self._cmdkey2text(key))

        return res

    ######################################################################
    ## protected:

#    def __setitem__(self, name, value):
#        print "setitem"
#        key = self._str2cmdkey(name)
#        print "Setting",key,"to",value
#        self._commands[key] = value        

    def _onKeyDown(self, event):
        """Handle a KeyDown event.

        Note: This event handler is \b not called if the corresponding
        description text is present in the menu. In that case, the menu
        handles the events itself (a wx feature). But it seems this only
        works with English modifier names.
        """
        print "KeyDown",
        print "KeyCode:",event.GetKeyCode(),"RawKeyCode:",event.GetRawKeyCode()

        # Create the key tuple
        key = self._event2cmdkey(event)
        # Check if the key was bound
        if key in self._commands:
            # Call the bound function
            func = self._commands[key]
            func()
        else:
            event.Skip()
    
    def _onKeyUp(self, event):
#        print "KeyUp",
#        print "KeyCode:",event.GetKeyCode(),"RawKeyCode:",event.GetRawKeyCode()
        event.Skip()

    def __setattr__(self, name, value):
        if name=="":
            return
        # If the name starts with an underscore then it's not a key description
        if name[0]=="_":
            object.__setattr__(self, name, value)
            return

        # Bind key...

        # Create the key tuple
        key = self._str2cmdkey(name)
        # Remove an existing binding
        if self._commands.has_key(key):
            self.__delattr__(name)
        # Store the key binding
        self._commands[key] = value

        # Update menu items
        self._updateMenu(value)

    def __delattr__(self, name):
        # If the name starts with an underscore then it's not a key description
        if name[0]=="_":
            object.__delattr__(self, name)
            return

        # Create the key tuple
        key = self._str2cmdkey(name)
        # Check if a key binding exists
        if self._commands.has_key(key):
            func = self._commands[key]
            del self._commands[key]
            # Update menu items
            self._updateMenu(func)
        else:
            raise KeyNotBound, "Key '%s' is not bound to a function."%name


    def _updateMenu(self, func):
        """Update all menu items that are bound to func."""
        # Check if any menu items have to be updated
        menu = getattr(self._window, "menu", None)
        if menu==None:
            return
        nodes = menu.findCommandNodes(func)
        for n in nodes:
            n.update()        
        

    def _event2cmdkey(self, event):
        """Convert a key event into a tuple which can be used as key."""
        return (bool(event.ShiftDown()), bool(event.ControlDown()),
                bool(event.AltDown()), bool(event.MetaDown()),
                event.GetKeyCode())

    def _str2cmdkey(self, s):
        """Convert a key description string into the key tuple."""

        f = s.upper().split("_")

        # Check if the modifiers are correct
        for m in f[:-1]:
            if m not in ["SHIFT", "CTRL", "ALT", "META"]:
                raise InvalidKeyDescription, "Key '%s' contains invalid modifiers."%s

        # Convert the last argument into a key code
        skey = f[-1]
        if len(skey)==1:
            key = ord(skey)
        else:
            key = getattr(wx, "WXK_%s"%skey, None)
            if key==None:
                raise InvalidKeyDescription, "Key '%s' does not exist."%s
        
        return ("SHIFT" in f, "CTRL" in f, "ALT" in f, "META" in f, key)

    def _cmdkey2text(self, key):
        shift, ctrl, alt, meta, key = key
        mods = []
        if shift:
            mods.append("Shift")
#            mods.append("Umschalt")
        if ctrl:
            mods.append("Ctrl")
#            mods.append("Strg")
        if alt:
            mods.append("Alt")
        if meta:
            mods.append("Meta")

        ks = self._wxkeystrs.get(key, None)
        if ks==None:
            ks=chr(key)
        mods.append(ks)
            
        return string.join(mods, "+")
        
            
        
    
        
