# keyEventsDemo.py
#
# keyPressed, keyReleased with ctrl and shift
#
# User timer-delayed keyRelease for Mac+3.6 bug

from tkinter import *
import time

keyPressedTimes = dict()
skippedKeyReleases = dict()
keyReleaseDelay1 = 500
keyReleaseDelay2 = 100

def eventInfo(eventName, char, keysym, ctrl, shift):
    # helper function to create a string with the event's info
    # also, prints the string for debug info
    msg = eventName + ": "
    msg += "(ctrl=" + str(ctrl) + ")"
    msg += "(shift=" + str(shift) + ")"
    msg += "(char=" + char + ")"
    msg += "(keysym=" + keysym + ")"
    print(msg)
    return msg

def ignoreKey(event):
    # Helper function to return the key from the given event
    ignoreSyms = [ "Shift_L", "Shift_R", "Control_L", "Control_R", "Caps_Lock" ]
    return (event.keysym in ignoreSyms)

def keyPressed(event, data):
    ctrl  = ((event.state & 0x0004) != 0)
    shift = ((event.state & 0x0001) != 0)
    if (ignoreKey(event) == False):
        data.info = eventInfo("keyPressed", event.char, event.keysym, ctrl, shift)
    if ((len(event.keysym) == 1) and (event.keysym.isalpha())):
        # it's an alphabetic (A-Za-z)
        if (event.keysym not in data.pressedLetters):
            data.pressedLetters.append(event.keysym)

def keyReleased(event, data):
    ctrl  = ((event.state & 0x0004) != 0)
    shift = ((event.state & 0x0001) != 0)
    if (ignoreKey(event) == False):
        data.info = eventInfo("keyReleased", event.char, event.keysym, ctrl, shift)
    if ((len(event.keysym) == 1) and (event.keysym.isalpha())):
        # it's an alphabetic (A-Za-z)
        if (event.keysym in data.pressedLetters):
            data.pressedLetters.remove(event.keysym)

def redrawAll(canvas, data):
    canvas.delete(ALL)
    # Draw the pressedLetters
    font = ("Arial", 16, "bold")
    msg = "Pressed Letters: " + str(data.pressedLetters)
    canvas.create_text(400, 125, text=msg, font=font)
    # Draw the event info message
    font = ("Arial", 16, "bold")
    info = data.info
    canvas.create_text(400, 50, text=info, font=font)

def init(data):
    data.info = "Key Events Demo"
    data.pressedLetters = [ ]

def timerFired(data): pass
def mousePressed(event, data): pass
def mouseReleased(event, data): pass

########### copy-paste below here ###########

####################################
# use the run function as-is
####################################

def run(width=300, height=300):
    def redrawAllWrapper(canvas, data):
        canvas.delete(ALL)
        canvas.create_rectangle(0, 0, data.width, data.height,
                                fill='white', width=0)
        redrawAll(canvas, data)
        canvas.update()

    def mousePressedWrapper(event, canvas, data):
        mousePressed(event, data)
        redrawAllWrapper(canvas, data)

    def keyPressedWrapper(event, canvas, data):
        if (event.keysym not in skippedKeyReleases):
            skippedKeyReleases[event.keysym] = 0
        keyPressedTimes[event.keysym] = time.time()
        keyPressed(event, data)
        redrawAllWrapper(canvas, data)

    def keyReleasedWrapper(event, canvas, data):
        if (skippedKeyReleases[event.keysym] == 0): delay = keyReleaseDelay1
        else: delay = keyReleaseDelay2
        canvas.after(delay, keyReleasePostDelayWrapper,
                     time.time(),
                     event, canvas, data)

    def keyReleasePostDelayWrapper(eventTime, event, canvas, data):
        if (keyPressedTimes[event.keysym] > eventTime):
            skippedKeyReleases[event.keysym] += 1
        else:
            skippedKeyReleases[event.keysym] = 0
            keyReleased(event, data)
            redrawAllWrapper(canvas, data)

    def timerFiredWrapper(canvas, data):
        timerFired(data)
        redrawAllWrapper(canvas, data)
        # pause, then call timerFired again
        canvas.after(data.timerDelay, timerFiredWrapper, canvas, data)
    # Set up data and call init
    class Struct(object): pass
    data = Struct()
    data.width = width
    data.height = height
    data.timerDelay = 100 # milliseconds
    init(data)
    # create the root and the canvas
    root = Tk()
    canvas = Canvas(root, width=data.width, height=data.height)
    canvas.pack()
    # set up events
    root.bind("<Button-1>", lambda event:
                            mousePressedWrapper(event, canvas, data))
    root.bind("<KeyPress>", lambda event:
                            keyPressedWrapper(event, canvas, data))
    root.bind("<KeyRelease>", lambda event:
                            keyReleasedWrapper(event, canvas, data))
    timerFiredWrapper(canvas, data)
    # and launch the app
    root.mainloop()  # blocks until window is closed
    print("bye!")

run(800, 300)