#!/usr/bin/python

# tz_wx_keyboard.py
#       --copyright--                   Copyright 2013 (C) Tranzoa, Co. All rights reserved.    Warranty: You're free and on your own here. This code is not necessarily up-to-date or of public quality.
#       --url--                         http://www.tranzoa.net/tzpython/
#       --email--                       pycode is the name to send to. tranzoa.com is the place to send to.
#       --bodstamps--
#       December 15, 2013       bar
#       December 16, 2013       bar     put images in source
#       December 17, 2013       bar     more interaction with app
#       January 18, 2014        bar     outside control of whether we can be live
#       January 19, 2014        bar     able to turn off cursor
#       February 3, 2014        bar     switch the two slashes
#       February 19, 2014       bar     use stretch_image and new keycap
#       February 22, 2014       bar     speed-ups
#       March 16, 2014          bar     allow external shifting
#       March 20, 2014          bar     don't crash under windows when our space, and therefore, scale is too small
#       --eodstamps--
##      \file
#       \namespace              tzpython.tz_wx_keyboard
#
#
#       A software keyboard.
#
#


import  cStringIO
import  os
import  re
import  sys

import  wx

GRADIENT_TOP        = wx.Colour(0xff, 0xff, 0xff)
GRADIENT_BACKGROUND = wx.Colour(0xc3 / 2, 0xc3 / 2, 0xbb / 2)
GRADIENT_BACKGROUND = wx.Colour(0xfd, 0xf5, 0xe6)

GRADIENT_TOP        = wx.Colour(0xc0, 0xc0, 0xc0)
GRADIENT_BACKGROUND = wx.Colour(0x40, 0x40, 0x40)

FONT_SIZE           = 32
FONT_COLOR          = 'blue'

MARGIN              = 10


#   BEGIN_IMAGES


def  backspace_image() :
  return '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x002\x00\x00\x00\'\x08\x06\x00\x00\x00M$\x1b\x99\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\x06bKGD\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\x00\x00\tpHYs\x00\x00\x0e\xc4\x00\x00\x0e\xc4\x01\x95+\x0e\x1b\x00\x00\x00\x07tIME\x07\xdd\x0c\x10\x07\x0e+\xc0\xed\xdd\x84\x00\x00\x00\x19tEXtComment\x00Created with GIMPW\x81\x0e\x17\x00\x00\x02.IDATX\xc3\xed\xd8\xcf\xabLq\x18\x06\xf0\xcf\x9c\x99{\xaf\xcc]\x88\x10\x8a\x8d\x1fe\xa1.I\xd1\xa5\xec\xc9\xc6Vv\x16\xfe\x06\xac\xd8X\xc8F,dc!Q\x166R\xcaR6\x147\xbf\xcb\x02\xf7*\xc2\xfd\x81\xdc1\x16\xdeS\xd34s\xcf\xcc\xd4\x9d9\xc3<\xf5\xd6\x993\xa7s\xbe\xcfy\xdf\xe7}\x9f\xf3-h\x1d#X\x8f\xcdq\x9c\xe2\x17\xde\xe2\x15\xe6\xe4\x1c#\xd8\x86[\xa86\x88G\x18G9\xef$\xb6\xe0n,\xfaw\x1d\x89\xf4\xf7c\x8c\xa1\x98W"\xab\xf1\xb0\t\x89z2\x870\xd4\x8bE&\x19\xff\x97\xb15\xa2\x8aB\x93\xeb\xd2\xf3C\x0b\\\xb3\xa8(e\x90\x18\xc3\x15,\xcd\xbb\x88\x93\x05H\xec\xc4\x05l\x88l\xf4\x1d\xca\xd8\x83\xa7\x19\xbah\xa4\x91\x03\xbd\xd2H\xb3L\xbc\xeb\x80\xc45ljAw\x8b\xae\x91\xd1\x10\xf5\x1d,\xcb\x10w\xbd\xd0\xef\xe1<\xa6\xba\x9c\x91\n\xe6k\x89\x8cb;\xae\x06\tmv\x9f2vDF\xba\xa5\xa7\xf9p\x13\x13\x98M[\xe6x\x9b\x9ahTZ\xbd\x88\t\xecO\x1d\xc5\n\x9c\xcc\xc1\xa2\xaa\x1d\xbe\xc0\xe7QMv\xe3{\x1f\x11hD\xe6`\x82\x95X\xd2\xa7\xb3"\xd5q)\xa9i\x97\x05}\x8c\xc4?\x82\x04?\xe3\xb8\xda\xefD^\xc4T\xee\xc7\xd2J_\xfel\x82\x0fx\xd0\xa7\x89(\xe0\x0c\x9e\x94\xf0\x03\xd7\xb1\x11\xc7;\xbc\xe1\x1c\xbeD;\xec\xa6\xbd\xba\x81\x8b\x98,\x85_\xf9\x18\xccJ8\xd6\x86\xcfJq\x1f\xa7\xe3>\x95.Z\x94o\x98\xae\x7ff\x11\xebp\xb6\x83)\xff\x19G\xb0</Z+\xc6\x96\xcf\x89\x0el\xfc<\xf6f|u.\xea\xc2\xeb\xbb\xc0\x0c\xde\x84\xa3\xdcWs>\xeb{=\xc1m\xbc\xecby5%RK\xe6Y8\xe3]m\x94\xcb\xcd0q\x95<\xb5\xb6"\xd6\xe0R\xec&\xb6\xa2\x95\xc3\x18\xce\x9bEI\xbb\xd9\xb9\xa8\xfb\\O\xfe,\xafU\xc1$\x8eFyU3&\xect\x97gI\xdb\xd3sU\xb4\xd7\x85\xca\xea\x14\xd6\xf6\xaa\xfd\xb6\xbaO;\x87\xf7x\xed\xeff\xf6\x14>E|\xc5\xe5\xd8\x03\x9b\xeaUF\nm^;\x1a1\\7ag"*\x06\x18`\x80\x01\x06\xf8\x9f\xf0\x07&<F\xe0\x07\xa6j\xb5\x00\x00\x00\x00IEND\xaeB`\x82'

def  shift_image() :
  return '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00 \x00\x00\x002\x08\x06\x00\x00\x00=d\xd9d\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\x06bKGD\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\x00\x00\tpHYs\x00\x00\x0e\xc4\x00\x00\x0e\xc4\x01\x95+\x0e\x1b\x00\x00\x00\x07tIME\x07\xdd\x0c\x10\x07+\ndw\x1d=\x00\x00\x00\x19tEXtComment\x00Created with GIMPW\x81\x0e\x17\x00\x00\x01\xf1IDATX\xc3\xed\xd8\xbdk\x14A\x18\xc7\xf1\xcf\xde\x06\x13\xd4@@\xf4|\xef|)\xc4NA\xa2\xa2\xb6\xfe\x0f\xa6\xb4\xb1\xf0\x1fP\x0b\xb1\xb2\x13\x0b\xfd\x0b\xb4\x10\xb1\xb3\x10\xd2\x8bUD\x91\x88\x82\x8a"\xa8 \x04\xcf\x17\xee\xbc\xb3\x99\x95\xcd\xb2w\xd9\xcbm\xce\x08\xf3\xc0\xb0\xc3\xce\xce\xfc\xbe3\xfb\xcc3\xfblb8K1\x85\x1d\xd8\x8f\xc9p\xbf\x8b\xcfX\xc4\x12\xda\xd6\xc0&\xb0\x13\x17q\x07\xbdB\xf9\x81\xcb8\x81\xcdu\x8b\xa7A\xfcfN\xb0\xdb\xa7\xfe\x1c\'\xeb\x84H\xb1\x0b\xb7Jf],\x19\xc8;\x1c\xc5\xa6:\xc4\xf7\xe2z\x05\xf1"\xc4W\x1c\x19\x05"\x13\xbfT\xb2\xccU\xcb\x07\xcc\xae\x06"[\xf6\xab#\x88ws>qj\x18\x884l\xb3\x1b#\x88\x17!\x9e\xe2\xf8J\x10\tfp\x10\xb7G\x10\xed\x07\xf1\x06\xa7\xd1\xcc\xc5\x8fe\xe2\xdb0\x17\x1en\xd70\xfb2\x88\x16\x9e\xe0@\x1e"\xc1V\x9c\xabYt\x10\xc8#\x1c\xce \xa6q~\x0c\xe2E\x88\x07\xd8\'8\xdc\xdb1\x08\x97A\x9cm`\x03~\x1a\xaf%\xe1:\xd9\xf0\x8fm]\x00t\xc2Q\xdb\xcfzC\x8e\xd9\xab\xd07\xbb\xdfn\xe0\x1b\xee\x0f\xe8\x90\x84\x83\xe5!\xbeW|\xbf\x8f1\x1f\xea\xbd\x12\xf1\x04\x0b\xc1\xf9\xff\xc6\xfd+}<\xb6\x13b\xc4l8\\\xaax\xf9\x05\x1c\xc3\xdd>\xed\x0b\xe1\xc3ec\x92\x8b\xff\xcd\x10\x1c\xa6s\xb4\xbf\xf1\x05\xcf\xb0%\xccjw\x85U\x98\x0b\xfb\xbc\x19B\xfbT\xae\xad\x1df\xfe\x12\xad\x89\x9c\xd0G|*q\xccnh\x9f\x19\xd2\x0f~\xe1u8\x03\x92B[6\xe62\xe7\xcb\x96\xbbN\xeb\x86\xb2\xbe\xb7a\x04\x88\x00\x11 \x02D\x80\x08\x10\x01"@\x04\x88\x00\x11\xe0\xbf\x01X)\x8b\xce\'\xb7\x9d\xb5\x00X\xc2\xbd\x01Yt\x96\xf5\xbe\xc0\xab,\xf5\xaa\xd3R\xec\xc1\xb5\x01Y\xf1"\xce\x18\xe2\xafh\xb2\n\x88\xed8T"\xd2\xc1\xfb\x00\xd1\xaa:\xe0\x1fI\x01B\x10\xa2\xd8\x18\xaf\x00\x00\x00\x00IEND\xaeB`\x82'

def  shift_shift_image() :
  return '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00@\x00\x00\x002\x08\x06\x00\x00\x00\xec\x83\xdd\x14\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\x06bKGD\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\x00\x00\tpHYs\x00\x00\x0e\xc4\x00\x00\x0e\xc4\x01\x95+\x0e\x1b\x00\x00\x00\x07tIME\x07\xdd\x0c\x10\t\x13*B\x18\xac\x04\x00\x00\x00\x19tEXtComment\x00Created with GIMPW\x81\x0e\x17\x00\x00\x02DIDATh\xde\xed\xda\xbbk\x14A\x00\xc7\xf1\xcf\xe6\x82\tj  z\xc6W\xe7\xa3\x10;\x05\x89\x8a\xda\xfa?\x98\xd2\xc6\xc2\x7f@-\xc4\xcaN,\xf4/\xd0B\xc4\xceB\xb0\x17\xab\x88"\x11\x05\x15EPA\x08\xc6\x07w\xde\xd9\xcc\xe9\xba\xec\xc5\xbd\xdcc\xa2\xce\xc0p\xcb\xce\xed~\x7f|\xef\xe6\xb17\x97\xe9\xad\xd40\x89\x19\xec\xc2D8\xdf\xc2{,`\x11\r\xc3)Q\xf9\xe3\xd8\x823\xb8\x8ev\xa1~\xc19\x1c\xc6\xfa\x7f\x8d_\x0b\xf0+9`\xab\xcb\xf1c\x1c\x19p\x88\xa8\xfc\x1a\xb6\xe2j\x89\xf5b\xed\x04y\x85\x03X\xf7\xb7\xf3k\xd8\x81K\x15\xe0\xc5\x10\x1f\xb1\xbf\xcf\x10Q\xf9\x1d\xf8\xd9\x92\xafY\xd5\xfa\x06\xb3+\x0c\x11\x95\xdf\xf9\xda]\xe8\x03\xde\xca\xf5\xc9\xa3=\x86\x88\xca\xaf\x85i\xe6r\x1f\xf0b\x88\x878T1D4~\x86i\xec\xc1\xb5>\xa0\xddB\xbc\xc01\xd4s\xf3\xf7\xaa\xe1g\xd8\x84\xb9\xf0\xe6\xc6\x00\xec\x97\x85X\xc2\x03\xec.\x84\x88\xca\xcf\xb0\x11\'\x07\x0c].\xc8]\xec\x0b!b\xf3M\xe1\xd4\x08\xe0\xc5\x10\xb7\xb1s\x15\xf0\xcd\xe0\xe5\x08\xc0e!N\xc4\xe6\x8fa\r\xbe\x1am\xc9\xc2\xebDl\xfe\x98\xff\xbc$\x01h\x86G\xcdn\xa5\xdd\xe3=\xdb\x15\xae\xed\x9co\xc4\xe6\x8f\xe1\x13n-sA\x16\x1e,\xee\xe0s\xc5\xfeu\x1f\xf7\xc2q\xbb\x04\x9ea>\x0c~\xb1\xf9?\xd7\xdd\xe7\xbb\x8c\x98\xcd0G\xcf\x86\x87\x8b*\xa3\xeci\x1c\xc4\x8d.\xed\xf3\xe1\x87\x8b\xb5\xb1\xf9Yn\xfd]\x0f\x8b\x83\xa9\x9c\xad\xef\xf8\x80G\xd8\x10\xacn\xab\xf0)\xcc\x85y\xb6\x1e\x96\xb6\x93\xb9\xb6F0\xff4\xac\xcc\xa2\xf2\xc7s\xa0\xb7xW20\xb6B\xfbt\x8f\xfd\xf0\x1b\x9e\x875xVh\xeb\xdcSl\xfex\xa1\xa19\xe0A\xb6\x15j\xd5\xd0#\xe7\xa7i0\tH\x02\x92\x80$ \tH\x02\x92\x80$ \tH\x02\x92\x80$ \tH\x02\x92\x80$ \tH\x02\x92\x80$ \tH\x02\x92\x80\xb2\xf2\xa7]\\~mD6\x87\x90u(\xfc^\x04,\xe2f\x01T\x84gx\x82g~\xdf\xfa\x1aD\x89\xcdW\xc3v\\\xd4}Wv\x01\xc7\r\xe6O\xd2#\xe1g+\x08\xb1\x19{K M\xbc\x0e!\x96\x86\xd4e\x07\xce\xff\x01*\xc2\x83\xd6\x86\tp\xbc\x00\x00\x00\x00IEND\xaeB`\x82'

def  key_cap_image() :
  return '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00`\x00\x00\x00`\x08\x06\x00\x00\x00\xe2\x98w8\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\x06bKGD\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\x00\x00\tpHYs\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x00\x07tIME\x07\xde\x02\x14\x03\x01 ah\xeb\x87\x00\x00\x00\x19tEXtComment\x00Created with GIMPW\x81\x0e\x17\x00\x00\x05\x18IDATx\xda\xed\x9d\xdb\x8e"G\x10DO45\xc0,\xbb\xde\xb5\xbcZ\xff\xbb\xff\xc0\xff\xe3\xcf\xb0\xe5\x99\x9d\x0b4tw\xf8\xa1\xaa\x81\x19\xcd\x85\x95\xfd\xe0\x87H\xa9Q\t\x04H\x91\x95\'\xb3\xea%\xf4\xdb\xef\x7f,@\x0b\xa0<{\x16\xed\x91\x01\x81H\xfcH\xb8\xe9f`\x04\x8f\xa0\x01\x18\xb0\x07\xa4\x01<\x96&\xfe\x15\xb0\x04\xd6\xc0\nX\x19\x96\x82b\xd3It\x80l#%\x0fo\xab~\xd4\xc8\xc2\x13h\xc2\x1e\x80=\xa2\xc7\xf4H\xbbyO\x17\xdbEb\t\xfa\x00l\x80O\x86\x0f2\xd7hN\x82;\x81R\x03\x17\x84\xda\xde\x17\x06M\xc0\x80\xb4\xc7l\x81G\xe3{\xacN26.\x92\n\xb0\xb6\xbd\x91\xf8\x19\xf4E\xf0\xd9\xf8\x93\xd0\x1aq%T\x00\x05C\x97\xe8\xaf&>n\xa89\x00;\xcbw\x82[\xa1\x82<\'g,\xe0\x02ZI\xfa\x08|\x01\xbe\x01_U\xd7\x1b\xcc\nQj/p\x972\xb8(&\xc0\x95\xf3\xf4\xb6\x1f$n@+\xd7]\xdc\x90\xe4\xbe`\x8a\xe5\x95\xd0\xc6\xf0Y\xf0\x0b\xf0\xab\xc5W\xc1\'\xc4\xf5\xdc\x94\x05]\xb4}\xaf\t\x00b\xaa\x8d\x97\x01\xd8\n\xee\x0c+U6\xf5F\x8f\x82{P)H\x0b\xc1\x12|-\x8eU\xf0U\xe8[[_\xb7\x06\xddU\n\xd5\xa9(\xf12\xfe\x1b \xa6\xf6\xec\x81-\xe2\xba\x91\xa3\x17\xdcanZ\x7f]\xcc\xe3f\x01-\xa9\x8d\xf7\xa3\xed\x9f$>\x83\xbe\x186\xb2\x97H\x8b\xa7\xff\x91x{\x18\xf2\x88\xb4\x17<Tz\xb3C\xdc\x00k\xe4%\xb5\xaf.J\xd3\xb3\x03\n\xe2\nXIZ\x1b>\x086\xe0\r\xd2\xb2a\x08\xb72H\xbcF\x1f* \xa4Qv\xdf\xd4\xea\xc1k\xd0\n\xfb\x8a:\xf8t\x80\nF\xd49\xbf;U\x83\x8b\xd0\x12X\xaaV\xc6\n\xd5C\xd9|*K\xbc\x80\xa0Y\x1bc\x89\xb1MC\x07\xf0\x12\xe9\xaam\xf2\xc5\xf1leTNz\x1a\xac\xae&\xe3\x98\xa1\xc5\xb3G\x91\xff\x82B\xa8\xdd\x966\xb8,\xb0:\xe3\x85\xa4\xce\xd0\xe9\xecn\xa1\xcc<\xb1\x91\xb0N\x849\xae5\xafmK9\n\xbf\xd3\x85\x8f\xab\xa9\xd1E\x86Y6\xe1\xb6\x89[W.\xf3\x17\x9e\xe8j\xcb\xa7w\xe6\xf3\xd7\xb9\xf8I\xc2\xabm\xc0\x02\xd5q_\xe7\x9b\x99\'o\xcd5\xf0\xf2\\\xff\xf6&\x8f\xf8oj\xa3\x8bd\x9b\xd3\xd0=M\xde\xd9\x85R\xc4\xfc\x8f\xe7\xd2\x93\xb6\xf6)\r\xe5\xa5\xe4I\xe2\xc9\xb0\x93l\xfcgu\xf1\xbc\x85^v\xb5\x90\xa3\xef\xbf? \xbc\xf2A\x10\x14\x04\x05AAP\x10\x14\x04\x05AAP\x10\x14\x04\x05AAP\x10\x14\x04\x05AAP\x10\x14\x04e\xd3\x07AAP\x10\x14\x04\x05AAP\x10\x14\x04%\x82\xa0 (\x11\x04\x05A\x89 (\x08J\x04AAP"\x08\n\x82\x12AP\x10\x94\x08\x82\x82\xa0D\x10\x14\x04%\x82\xa0 (\x11\x04\x05A\x89 (\x08J\x04AAP"\x08\n\x82\x12AP\x10\x94\x08\x82\x82\xa0D\x10\x14\x04%\x82\xa0 (\x08\n\x82\x82\xa0 (\x08\n\x82\x82\xa0 (\x08\n\x82\x82\xa0 (\x08\n\x82\x82\xa0 (\x08\n\x82\x82\xa0 \xe8\xff\x82 \xbf\x85 \x9f\xffH\xd2\xf1\xc3R\xebLf\x9fKMi\x8eV\xae\xeeW\xc721\xd8\xf5#\xb9\xd9Z\xb9\xbdHI\xc2\xab\xea\xeb\xa4\x93\x05n\x82y\xd6\xef)\x81\xe4r\xcc\x8e\x85\xab\xf5\x9b\xcf\xc0\xe5\xf6\xcb\xaekKV:\xf4k\xa8?\xf3\x85<\x99zbc7\xe3<?\xb7\xe2,\xb3\t\xbd\x84\xb1&\xec\ti\x04\x8f\xa0fJ\xecE\xb3\x91\x9c\xe2,\xffv\xb3m;\xdd6#\xf2 4bM\xc6\xa3\x9a\xcf\xb0\xb1+e\xa0\xb4\xc5\x84\xa9\x06\xf4U\xf0\x03\xd6\x1e\xb1\x07\xaeZ\r\x15e&\xba\xb8\x0fH\x8c\xa0=\xb0\x97\xd8\x83\x0e\xc0\x80=H\x9a\x9a\xe6.\xed\x0b\x13b\x00\x0e\xa0\x1e\xd8\xb9Zq?\xb82\xec\xa0\x93\x9bj\xe2\xfdF`\xaa\xa5\xf9\x1ex\x00\xb6\xd8;\xa4^\xe2\xd06\xfa\x04\xb8`\x8f\x88\xc1f/\xb4E\xdcc\x7f\x97\xb4\xae\x99t\x8f\xb5l~\xc2\x80ci\xfe\x16\x85j\x97\x9d\\\x89\xb2\x97\xd8\x1a\xdfH\xfaN\xdd\xd0;\xe4\xbd`@\x1a\x0b\xaa\x99\x92\xb4\x05\xee\x81\x1b\xa4u\x1b=\xb7H\xd7\xf5\xc0\xe6\xc5l\xfe\x996\xf0n\x19L\rA\x03\xf6Vp\x87\xf8\x13\xf8[\xd2\x1d\xb0\xad\xd5\xe1\xb1\x80\x06\xa0o\xa5r\x0b\xac\xda\x08\xd4#6\xcdM\xbb\xb4\x01\xaa\x0b\x85.\xb8s\xa8\x8c7u\x97\xf7\xd8\x0f\xb6o$\xfde\xfb\x16\xe9Q\xd0\x1b\r\x05<4\xee\xdf\x03W\x8da=\xf6w\x8b\xb5f+\xee:P)\x8e\xf2\xef2\xe8x\xde\xaa\t\xf0\x01\xd8\xd5\x9d\xef[\xe0Fp\x07\xf4\xd8\xc3\\\x01;\xa0\xab#)\x83\xcd\xa3\xa4\x1b`\x89]\xaa\r\xb7"\xfe\x0f\xe5\xc1\x06MFC\x9d\x82\xd8\x02\x8f\x92\xee\r\x0f\xaaI\x19J\xeb\xc8\xb3e\xf6\xdc8\xee\x81\xa5\xa0\x80\xaa\x01}Mk\x1c\xcd\xdf%\x90\xdbMBm\xc6m\xf6\x1f\xc0{Po\xdc\x0b\xed\xda\x844\x14\xccx:@x\xc4\xf4\xae\xb7\xa4E\xd0\xd5)T2\x8e\xf6\x17\x1e\xc6\x8c\xa97\x06\x9e\x0fe\x93\xd0\x00\x1e\x90\x86\xe3y\xcb\x8c\xff\x00\xaay\x91\xe2\x90\xa9\xd7\xce\x00\x00\x00\x00IEND\xaeB`\x82'

def  key_cap_2_image() :
  return '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\xc0\x00\x00\x00`\x08\x06\x00\x00\x00K\xc0}\xe9\x00\x00\x05\xbaIDATx\x9c\xed\xda\xefn\x1b7\x1a\xc5\xe1\xdf\x19\xd1\x92\x1d7\x9b\x14\r\xda{\xef\x1d\xf4~z\x19\xbbX\'\xfe#\x8d<3\xa7\x1fH\xc9\xb2\xd1f\xfbe\xd11x\x1e`\x8c\x89dE\t\xf0\x92<|9\xfa\xf5\xb7\xdf7\xa0\rP\xde\\\x9bv\xc9\x80@D\xbc\x1fnuk`\x06\xcf\xa0\t\x98\xb0\'\xa4\t<\x97V\xfcW\xc0\x16\xb8\x06v\xc0\xce\xb0\x15\x14\x9bAb\x00d\x1b)\xe3 \xd6\xeb\xa2F-\xbc\x80\x16\xec\t8"F\xcc\x88t\xa0\xcd\xe9\xc5v\x91\xd8\x82>\x00\xb7\xc0G\xc3\x07\x99\x1bt\x1a\x04\x1e\x04\xca\x1a\x10\xab\'\xea\xdc/\x0cZ\x80\t\xe9\x88\xd9\x03O\xc6\x0fX\x83dl\\$\x15\xe0\xda\xf6\xad\xc4\x8f\xa0\xcf\x82O\xc6\x1f\x85\xae\x11WB\x05PbP\xac\x9d\xea\xc4n\xc0-\xea<\x03\x07\xcb\xf7\x82\xafB\x05\xf948\xe6\x02.\xa0\x9d\xa4\x1f\x80\xcf\xc0\xcf\xc0\x17\xd5\xfb[\xcc\x0eQ\x00\x19\x0fY\x06\xe2\x1dX\x00\xd7\x9c\xcfh\xfbQ\xe2\x0e\xb4s\x9d\xc5[$\xf2X0\xc5\xf2N\xe8\xd6\xf0I\xf0\x13\xf0\x8b\xc5\x17\xc1G\xc4\rmS,\x18\xfe\xc1\xffT\xc4\xffVw\xbe\x0b0S\x0b}/\xb87\xecT\xb3\xd1h\xf4$x\x00\x95\x82\xb4\x11l\xc17\xe2\xbc\n|\x11\xfa\xb9\xdd\xdfP7\xc8CMA\xb5+\x14\xb16:\xff`i\xd7\x11\xd8#nZr\x19\x05\xf7\x98\xbb\xb6\xbf\xdd\x9c\xda\x9d\x05\xb4\xa5n|\x7f\xb0\xfd/\x89O\xa0\xcf\x86[\xd9[\xa4\xcd\xeb\xef\x88X-\xdb\x9e\x91\x8e\x82\xc7\x9a\xde9 \xee\x80k\xe4-u_\xbb)\xd4z\x1e\x80\x82\xb8\x02v\x92\xae\r\x1f\x04\xb7\xe0[\xa4-5\x06\xe1\xb6\x0cD\xac\xcdE\xdf\x1f\xa4Y\xf6\xd8\xaau\x04_\x83v\xd8W\xd4\xc6\xcf\x00\xa8`D\xed\xf3\x0f\x9cW\x03\x17\xa1-\xb0U]\x19v\xa8\x1e\x8a\x9dN\xc5"\xd6\xe6\\\x9b\xc6\x12s\xeb\x06=\x83\xb7HW\xd4I~s>\xdb2*/\xf5l\xb0\x86:\x18\xce#d\xf3\xe6\x12)\xffX7Sw\xbbP\x1b7\x1b\xac\xc1x#i0\x0c\xbax\xb6\xa1\x9c\xf2\x8c\x8d\x84\xf5\x92p\xce\xf7:\xdd\xdb\x96r\x14\x1ck\xa5WwKK72\x9c\xcaV\xb8M\xe2mW\\N\x1fxU\xd7\xb6\xfc\xf2\xca\xe9\xfc\xeb\xb2\xf83\x08b\x8d\xdc&\xeb\xda\xee\xd7\xe5d\xce\xab\x97Nk\xc0\x9f\xf7\xf5\xbf?\xc9\xa7\xf8c\xad\xbe\xf3\xb0\x82\xfe\xe4O\xbe<\xd8\xf2i\xff\\\x1f(\xfa?\xfc\xeb"\xfe)\xbe\xa8m\x9f\x0f\xb2t\x8e@\\\x0e\x1eI\xbcj\xf6d4\xc4{v\xb1.\xbc\xdd\xc2\xfe\xbdG\x1br\xf4\x1b\xef\xd9_\xd6o"Pt"\x11(\xfa\x95\x08\x14]K\x04\x8a\xde%\x02E\xbf\x12\x81\xa2k\x89@\xd1\xbbD\xa0\xe8W"Pt-\x11(z\x97\x08\x14\xfdJ\x04\x8a\xae%\x02E\xef\x12\x81\xa2_\x89@\xd1\xb5D\xa0\xe8]"P\xf4+\x11(\xba\x96\x08\x14\xbdK\x04\x8a~%\x02E\xd7\x12\x81\xa2w\x89@\xd1\xafD\xa0\xe8Z"P\xf4.\x11(\xfa\x95\x08\x14]K\x04\x8a\xde%\x02E\xbf\x12\x81\xa2k\x89@\xd1\xbbD\xa0\xe8W"Pt-\x11(z\x97\x08\x14\xfdJ\x04\x8a\xae%\x02E\xef\x12\x81\xa2_\x89@\xd1\xb5D\xa0\xe8]"P\xf4+\x11(\xba\x96\x08\x14\xbdK\x04\x8a~%\x02E\xd7\x12\x81\xa2w\x89@\xd1\xafD\xa0\xe8Z"P\xf4.\x11(\xfa\x95\x08\x14]K\x04\x8a\xde%\x02E\xbf\x12\x81\xa2k\x89@\xd1\xbbD\xa0\xe8W"Pt-\x11(z\x97\x08\x14\xfdJ\x04\x8a\xae%\x02E\xef\x12\x81\xa2_\x89@\xd1\xb5D\xa0\xe8]"P\xf4+\x11(\xba\xf6\xb7"\x90\xbf\x17\x81|\xf9\x97d8\xc4Z\xfdEm\xbeT\xf3)\x02\x9d~\xb1\xd4[\xd9\x02\xb5W%\x19\xec\xfa\x96L]6\xdc~H\x19\x04\xb1B-\xb6\x9f\xea\xd4\x02\xb7\x82u{\x9b\xd7\tH.\xe7\xd1aa\x8c^\x86\x01\xa8\x15z}\xc3`\xc9\xca\x0e9VI/\x1bW\x9f\'iac\xab\xbe\xe1W{[\xa0\xd8FR\x1d%\xd6\x82\xbd \xcd\xe0\x194\x03\x13xS?\xa6\x05\xa5\xfcc\xa5Z\xf1S\x93\xce\x8c<\t\xcdX\x8b\xf1,X\x80\xc5\xd85\xe5@i7\x0bf\x01\xa6z\xf9\x19\xeb\x888\x02Wm\r)\xba\xfc\x9a\x88u:E\x9d\x19t\x04\x8e\x12G\xd030aO\x92\x16j\xcd\xbb\xb4\x0f,\x88\tx\x06\x8d\xc0\xc1\xb0\x17<\xbaf\xa8g\xc1\x86\x14\x7f\xac]\xcd8\x06f\xe0\x08<\x02{\xec\x03\xd2(Q\x07B]\r\\\xb0g\xc4ds\x14\xda#\x1e\xb0\xbfI\xba\x06\x90<bm\x11\x9b\xf6\r\xca\x0e8\xd6\xaa\xf5\xf9\x17\xd7Ds\x94\xd8\x1b\xdfI\xfaF\x9d\xd0\x0f\xc8G\xc1\x844\x17TG\x8a\xa4=\xf0\x00\xdc!]\xb7\xd6\xe7\x1e\xe9\xa6\x1e\x98y\x03\x1a\x80l\x03b\xe5\xbc\xb4\x084a\xef\x05\xf7\x88\x7f\x03\xff\x95t\x0f\xec\x81#x.\xa0\t\x18\xa9K\xc5W`Gm\x01\x8d\x88[\xcc\xae\x0e\x00\t\x18\x92\x82b\xd5jf_\xa8ahB\x1a\xb1\x1fm\xdfI\xfa\x8f\xed\xafHO\x82\xd1h*\xe0\xa9\xe5\xfe\x07\xe0\xaae\xa8\x11\xfb\x9b\xc5\xb5\xa4+\xa0P\x1bJz\xd3H\x8dX\x97vf\xd5N\xb1&\xe4g\xe0Pg~\x7f\x05\xee\x04\xf7\xc0\x88=\x9dV\x80\x030\xd4\x96(\x93\xcd\x93\xa4;`\x8b]\x0c\x83Z\xaf4\xe2=\x90l\xd0b4\xd5.\x10{\xe0I\xd2\x83\xe1QuPL\x85\xba#V\xfd\x90N\x1b\x87\x07`+(\xa0A\xaa\x8fL\xd8\xd6\xdb\x87\x89"\xd6\xa4\x9dk\xb9\x95\xf4\xd2z\xff\x13\xf8\x08\x1a\x8dG\xa1\x03\xb5C4\x15\xcc\xfcr\x80\xe0\x193\xba>%Z\x04C\xed\x82J\xc6\xa9\xfdX\xbf\xf6\xfcC}b\xc1\xa7C\xb1Eh\x02OH\xed\xac\x8b\t3\xff\x01\x7f\xaa\x91\xe2\xa8L`\xee\x00\x00\x00\x00IEND\xaeB`\x82'

def  key_cap_3_image() :
  return "\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x01 \x00\x00\x00`\x08\x06\x00\x00\x00\xbd\xac\xafX\x00\x00\x06\x0cIDATx\x9c\xed\xdb\xdbn#\xd7\x11\x85\xe1\x7f\x91\xad\xc3Xv\xec \x86\xf3\xeey\x83\xbcO\x1e#A4\x9e\x83D\x0e\xc9\x95\x8b\xdd\xd4\xc9\x1a\xd90\x90l\x03\xf9?\xa0\x85\x1eR\x1c\xce\xdc\x14j\xd5\xae\xce\xdf\xfe\xfe\x8f-d\x0b,/\xae\xedz\xa5@ H\xd2o\xd3\xb5n\x148B\x8f\x90\x03p\xa0=\x90\x1c\xa0\xc7e->\x17\xc0%p\r\\\x01W\x85\xcb\xc0\xd2\xb2I\xd8\x00iKb\x1d\x92\xf4\xba'5\xa2\xa1'\xc8\x89\xf6\x00\xec\t;\xca\x8e\xe4\x9e\xb5\xa7Y\xda.\t\x97\x90o\x80\x1b\xe0\xbb\xc27)\xef\xc8\xb9\x08u\x13\x88=\x90\xa47\x85\xd1\xfb\x84BN\xc0\x81dO\xb9\x03>\x97~\xa4\xd9$\xa5\xa5K\x92\x05\xb8n{\x93\xf0g\xc8\x0f\x81\xefK\xbf\x0b\xb9&\\\x84,@\x8ca\x92\xde\x92\xd1\xd8\x14\xe8\x1a\xb5\xbe\x00\xf7M?\x04\xde\x87,\xa4\xe7\xe2t\\\xa0\x0b\xe4*\xc9\xb7\xc0\x0f\xc0O\xc0\x8f\x19\xf77\x94+\xc2\x02\xa4tc\x1b$\xe9W\x9c\x80\x8e9\x0f\xbb\xb6\x9f\x12n!W\x1d]\xcc\x1a\xc9\xba[(K\xd3\xab\x90\x9b\xc2\xf7\x81\xbf\x00\x7fm\xf81\xf0\x1d\xe1\x1d\xebP:\xb0\x99\xf8\x9f\x92\xf4G7&\xcf'\xe0\xc8(4w\x81\x0f\x85\xab\x8cl\xb6+\xf9\x1c\xf8\x08Y\x16\x92m\xe0\x12\xfa.<tA?\x86\xfc\xb4\xde\xbfc\x0c\xa87#\x85\x8dS1Iz*\x0f?8\xad\xd7\x1e\xb8#\xbc[\x93\xd3.\xf0\x81r\xbb\xce\x97\xb7\xe7\xe3\xf6\x05r\xc9\x18<\x7f\xdb\xf6O\t\xdfC~(\xdc\xa4\xbd$\xd9>\xff\x0eIzU\xdb\x1eI\xf6\x81Ocz\xc3=\xe1\x16\xb8&\xbdd\xcc\x95\xb7\x0b\xa3\x9el\x80\x85p\x01\\%\xb9.|\x13\xb8\x81\xde\x90\\2b\x18]\xdb Iz\xea\xc9\xde\x0f$\xc7\xb4\xbb\xb5Z\xec\xa0\xd7\x90+\xda\x0b\xc6\xc1\xd7\x06\xc8B\tc\xcfg\xc3C7\xd4%\xe4\x12\xb8\xcc\xe8\x8c\xae\xc8XJ<o%J\xd2S\x0f\xb5\xa14\xe1\xb8\x9e\x86}\x81^\x92\\0\x9a\x9c\xed\xc3na\xc9\xf2XO\n\xcdf\x14\xa3\x87\n\xb5}q\x05\xcb\x8f\xa4\xaf+c\xda\x0c\xe3\xe0jK\xb3)\xdd&\xd9\x146y\xf2l\xc5r\xceS-\t\xcdc\xc2z\xb8\xcf\xf9\xbem\xe2*\xb4\xa4\xd7\xe4\xd9\xddiMW)\x9c\xcbF\xe8\xda\xc4\xacS\xe9\xe5\xfc\x81gu\xa5M\x1f_9\xef\x1f>->\x16!I/umV\xc6\xbaO\x9e63<{\xe9\xdc\x03\xbd\xbe\xd7\xf3v\x93c\xf1\x91\xf4\x9a7\x1e\x96\xc8+\x7f\xea\xd3\xc5\xc2\x9e\xe7\xd7\xe3\x81\xb2\xff\xc2\xbfN\xd2\xff\xa7>\xa9-}X$\xccC\x04\xe3i\xf1J\xc2\xb3\xc3.\xab\x91\xa4\xdf\xebI_\xf4r\x84\xfc\xdb\x1e\xadp\xf5Y\xd2\xef\xf5\xd5\xfaa\x04\x93\xf4?`\x04\x934\x87\x11L\xd24F0I3\x19\xc1$\xcda\x04\x934\x8d\x11L\xd2LF0Is\x18\xc1$Mc\x04\x934\x93\x11L\xd2\x1cF0I\xd3\x18\xc1$\xcdd\x04\x934\x87\x11L\xd24F0I3\x19\xc1$\xcda\x04\x934\x8d\x11L\xd2LF0Is\x18\xc1$Mc\x04\x934\x93\x11L\xd2\x1cF0I\xd3\x18\xc1$\xcdd\x04\x934\x87\x11L\xd24F0I3\x19\xc1$\xcda\x04\x934\x8d\x11L\xd2LF0Is\x18\xc1$Mc\x04\x934\x93\x11L\xd2\x1cF0I\xd3\x18\xc1$\xcdd\x04\x934\x87\x11L\xd24F0I3\x19\xc1$\xcda\x04\x934\x8d\x11L\xd2LF0Is\x18\xc1$Mc\x04\x934\x93\x11L\xd2\x1cF0I\xd3\x18\xc1$\xcdd\x04\x934\x87\x11L\xd24F0I3\x19\xc1$\xcda\x04\x934\x8d\x11L\xd2LF0Is\x18\xc1$Mc\x04\x934\x93\x11L\xd2\x1cF0I\xd3\x18\xc1$\xcdd\x04\x934\x87\x11L\xd24F0I3\x19\xc1$\xcda\x04\x934\xcdo\x8a`}+\x82\xf5\xe9_b9\x92\xf4\x9a\xaf\xd4\x86\xc7jr\x8e`\xe7_\\\xc6m\xda@\xd6W\x93\x14\xda\xf1V\xcah\x9b\xba\xfeH,B\x92^X\xc76\xe7:\xd1@\xd7\x82\xd1\xf5m\x9e'\xb0ty\xa8N\r\xa5\xe4\xb1\x0cA\xd6B3\xde(4i\x9cPK\xfa\x85<\x0e\x8e\xfb\xd0\xa4\x84\x966\xe3\x8d>\x9b-\x03K[\x92\x8c*\xd5\x9chO$G\xe8\x11r\x04\x0e\xd0\xed\xf8XN\xc4\xf2#\xe9\x15k\xf1a$\xad#\xe9!\xe4Hs*=\x06N\xc0\xa9\xb4#e\xc1\xb2\xde\x9c('\xe00\xae~\xa1\xd9\x13\xf6\xc0\xc5\xdaC-y\xfa5\x92\xf4K\xe7\xa8u\x84\xec\x81}\xc2\x1e\xf2\x058\xd0\x1e\x92\x9c\x185\xa7\xcb\xfa\x81\x13\xe1\x00|\x81\xec\x80\xfb\xc2]\xe0SG\x86\xfb\x12\xd8b\xf1\x91\xf4\x96\x91\xb1\n\x1c\x81=\xf0\t\xb8\xa3\xbd'\xd9%\x8cB4\xba\xa1.\xb4G\xc2\xa1e\x1frG\xf8H\xfbs\x92k\x80\xa4;\x9aK\xc2v\xfd\x868\x81\x96\xf4\x9au\xcf\xe7\xd4\x91\xa8\xf6\tw\xa5\xb7I~f44\xf7\xa4\xfb\xc0\x81\xe4\xb8\x90Q\xa9\x92\xdc\x01\x1f\x81[\x92\xeb\xf5\xe8\xfd\x8e\xe4\xddXX\xec\x16\xb2\x01\x1c\x03IzCOk\x04;\xd0\xde\x05>\x10\xfe\t\xfc;\xc9\x07\xe0\x0e\xd8C\x8f\x0b\xe4\x00\xec\x18\xad\xd2{\xe0\x8aq\x04\xb6#\xdcP\xaeF\x01J\x80\x8d)L\xd2W\x8d\x99\xcd\x89\x11\xc6\x0e$;\xdaOmo\x93\xfc\xab\xed{\x92\xcf\x81]\xc9a\x81\x1e\xd6\xb9\xcfG\xe0b\xcdp;\xda\x9f\x1b\xae\x93\\\x00\x0b\xe3@-/\x0e\xf2%\xe9\xd1\xba3\xb8n\x11\x1eH\xbf\x00\xf7\xa3\xf3\xe9{\xe06\xf0\x01\xd8\xd1\x1e\xce\x1d\xd0=\xb0\x19G\xf2\x1cZ>'\xb9\x05.i\x97\xc2&\xebY\xbd$\xfd\x9a\xa4\x85\x9cJ\x0e\xe3\x14\x8c;\xe0s\x92\x8f\x85O\x19E\xe9\xb00&\xd2\x19\x1f\xcayp\xf4\x11\xb8\x0c,\x90M2\x1e\xd9h\x9b\x97\x0f\x93I\xd2\xd9\xbaW\xd8\xb5\xa4\x9c\xd6\xdd\x9f\x03t\x0f\xd9\x95\xeeB\xee\x19'd\x87\x85r|\\ \xea\x91\xb2\xebxJ~\tl\xc6)|Rj\xed\x91\xf4\xb6\xf5\xf9\x8b\xf1\xc4D\xcfK\x89\xa7\x90\x03\xf4@\xb2\xee\x1ar\xa0\x1c\xff\x03T\xdb\x91\xe23!H\x8f\x00\x00\x00\x00IEND\xaeB`\x82"

def  key_cap_4_image() :
  return "\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x01\x80\x00\x00\x00`\x08\x06\x00\x00\x00\x14\xf4\xa5\x89\x00\x00\x06WIDATx\x9c\xed\xdc\xdbr\x1b\xc7\x15\x85\xe1\x7f\x01\xc3\x83L;v*.\xe7\xdd\xf3\x06y\x9f<FR\xa1\xad\x03\th\x80\x95\x8b\x1eP\x94\xcaV\xe5&\t\x9c\xfe\xbf\xaaa\x8d\x00B\x90nvw\xaf\xde=\xf9\xcb_\xff\xb6\x87\xec\x81\xe5\x8bk\xbf])\x10\x08\x92\xa4\xdf\x83nu\xbb\xc0\tz\x82\xac\xc0J\xbb\x92\xac\xd0\xd3\xb2\x15\xff\x1b\xe0\x16\xb8\x07\xee\x80\xbb\xc2m`i\xd9%\xec\x80\xb4%q\x1c\x90\xa4k\xf4\xaaF7\xf4\x0c9\xd3\xae\xc0\x91p\xa0\x1cH\x9e\xd9\xe6\xf4K\xdb%\xe1\x16\xf2\r\xf0\x00|W\xf8&\xe5\r\xb9\x0c\x02\xdd\x05\xe2\x1a@\x92\xaeX\x18s\xffP\xc8\x19XI\x8e\x94'\xe0C\xe9;\x9a]RZ\xba$Y\x80\xfb\xb6\x0f\t\x7f\x84\xfc\x10\xf8\xbe\xf4\xbb\x90{\xc2M\xc8\x02\xc4\x18H\x92\xaeW\xc6\xc4\xbe@\xb7\xa8\xe7#\xf0\xdc\xf4m\xe0\xe7\x90\x85\xf428\x9c\x16\xe8\x02\xb9K\xf2-\xf0\x03\xf0\x13\xf0c\xc6\xfd\x03\xe5\x8e\xb0\x00)\xdd\xb9\x0c\x90\xa4\xabv\x06:r~\x0em\xdf'<B\xee:f\xf1[$\xd4\xc3BY\x9a\xde\x85<\x14\xbe\x0f\xfc\t\xf8s\xc3\x8f\x81\xef\x08o\xd86\x85\x03\xbb\xff\xe1\x7fJ\x92\xf45c\xe7\xf7\x0c\x9c\x18\x85\xfe)\xf0\xb6p\x97\x91\r\x1dJ>\x04\xdeA\x96\x85d\x1f\xb8\x85\xbe\t/\xab\x80\x1fC~\xda\xee\xdf06\x88w#\x05\x1a]A\x92\xa4\xeb\x91\x97\x1f\x9c\xb7\xeb\x08<\x11\xdel\xc9\xcd!\xf0\x96\xf2\xb8\xed\xef\xee/\xed\x9e\x0b\xe4\x96\xb1\xf1\xfbm\xdb?$|\x0f\xf9\xa1\xf0\x90\xf6\x96d\xff\xf9wH\x92\xaeP\xdb\x9eH\x8e\x81\xf7#\xbd\xe7\x99\xf0\x08\xdc\x93\xde2\xf6u\xf7\x0b\xa3\x9e\xef\x80\x85p\x03\xdc%\xb9/|\x13x\x80>\x90\xdc2b \xba-\x03$I\xd7\xe3U\xdf?$\xa7\xb4\x87\xadZ\x1f\xa0\xf7\x90;\xda\x1bF\xe3\xcf\x0e\xc8B\t\xa3\xcf\x7f\xc7\xcbj\xa0K\xc8-p\x9b\xb12\xb8#\xe3P\xd8\xe5T\x98$\xe9z\xbc\xd4\xe6\xd2\x84\xd3\xd6\r\xf4\x11zKr\xc3\x98\xe4\xef_\xcev\x95,\x9f\xeay\xa1\xd9\x8d\xc1\xe0e\x84\xd8\x7fq\x05\xcb\xbf$]\xab2v{a4\xee\xeciv\xa5\xfb$\xbb\xc2.\xaf\x9e\xed\xb0\\\xf2\x9c\x96\x84\xe6S\xc2\xf3r\x9f\xcb}\xdb\xc4\xa3\xc0\x92t}\xf2\xd9\xddyKwR\xb8\x94\xed\xd0m\x12\xbf\xed\n/\x97\x0f|V\xd7\xdb\xf4\xd3+\x97\xf3_\xaf\x8b\xbf\x83\x80$]\x97n\x93\xf5\xd1\xee\x9f\xd7\x93y>{\xe9\xb2\x06\xf8\xf5\xbe\xfe\xafO\xf2-\xfe\x92t}\xbe\xf2\xb0\x86\xfc\xca\x9f\xfa\xfa`W/\xfb\xc7\xe3\x81B\xff\x81\x7f\x9d$\xe9\xbf\xaf\xafj{_\x0er\xe5%\x02\xe2\xf5\xe0\x91\x84\xcf\x9a}\x1c\r$\xe9\xf7\xe9\xd5\xba\xe0\xcb-\xdc\x7f\xef\xd1\x0e\x1e\xfd\x95\xa4\xdf\xa7\xdf\xac\xdfF@\x92\xf4\x7f\xcf\x08H\x92fd\x04$I\x932\x02\x92\xa4y\x19\x01I\xd2\x8c\x8c\x80$iRF@\x924/# I\x9a\x91\x11\x90$M\xca\x08H\x92\xe6e\x04$I32\x02\x92\xa4I\x19\x01I\xd2\xbc\x8c\x80$iFF@\x924)# I\x9a\x97\x11\x90$\xcd\xc8\x08H\x92&e\x04$I\xf32\x02\x92\xa4\x19\x19\x01I\xd2\xa4\x8c\x80$i^F@\x924## I\x9a\x94\x11\x90$\xcd\xcb\x08H\x92fd\x04$I\x932\x02\x92\xa4y\x19\x01I\xd2\x8c\x8c\x80$iRF@\x924/# I\x9a\x91\x11\x90$M\xca\x08H\x92\xe6e\x04$I32\x02\x92\xa4I\x19\x01I\xd2\xbc\x8c\x80$iFF@\x924)# I\x9a\x97\x11\x90$\xcd\xc8\x08H\x92&e\x04$I\xf32\x02\x92\xa4\x19\x19\x01I\xd2\xa4\x8c\x80$i^F@\x924## I\x9a\x94\x11\x90$\xcd\xcb\x08H\x92fd\x04$I\x932\x02\x92\xa4y\x19\x01I\xd2\x8c\x8c\x80$iRF@\x924/# I\x9a\x91\x11\x90$M\xca\x08H\x92\xe6e\x04$I32\x02\x92\xa4I\x19\x01I\xd2\xbc\x8c\x80$iFF@\x924)# I\x9a\x97\x11\x90$\xcd\xc8\x08H\x92&\xf5oE@\xfdZ\x04\xd4\xd7\x7f\x89\xc3\x81$]\x9f\xdf\xa8\xcd\x9f\xaa\xf9%\x02\xba\xfc\xe22n\xd3\x06\xb2\xbd\x9a\xa4\xd0\x8e\xb7R\xc6\xb2\xa1\xdb\x8f\xc4A@\x92\xae\xca\x16\xdb_\xeat\x03\xdd\nv\xb7\xb7\xf9<\x01J\x97\x97\xd1\xa1\xa1\x94|\x1a\x06 [\xa1\x1fo\x14\x9a4\xee\x10K\xd2\x95\xc9\xa7\x8d\xdb\xbeL\xd2CK\x9b\xf1F?\xdb\xdb\x05\x96\xb6$\x19\xa3Ds\xa6=\x93\x9c\xa0'\xc8\tX\xa1\xfb\xf1\xb1\x9c\x89\xe5_\x92\xae\xceV\xfc\x19I\xcf\x89t\r9\xd1\x9cKO\x813p.\xedHy`\xd9n\xce\x943\xb0\x8e\xab\x1fi\x8e\x84#p\xb3\xad!\x96\xbc\xfe\x1aI\xd2\xb5\xb9D='\xc8\x118&\x1c!\x1f\x81\x95vMrf\xd4\xfc.\xdb\x07\xce\x84\x15\xf8\x089\x00\xcf\x85\xa7\xc0\xfb\x8e\x0c\xe9c`\x8f\xc5_\x92\xae\xd7\xc8x\n\x9c\x80#\xf0\x1ex\xa2}&9$\x8c\x81`\xac\x06\xba\xd0\x9e\x08k\xcb1\xe4\x89\xf0\x8e\xf6\x97$\xf7\x00I\x0f4\xb7\x84\xfd\xf6\rq\x07X\x92\xae\xcf\xd6\xe7\x7f\xeeHt\x8e\tO\xa5\x8fI~aL\xe8\x9fI\x8f\x81\x95\xe4\xb4\x901R$y\x02\xde\x01\x8f$\xf7[\xeb\xe7\x13\xc9\x9bq`\xac{\xc8\x0ep\x1b@\x92\xaeV\xcf[\x04\xb4\xd2>\x05\xde\x12\xfe\x0e\xfc3\xc9[\xe0\t8BO\x0bd\x05\x0e\x8c\xa5\xc2\xcf\xc0\x1d\xa3\x05\xe8@x\xa0\xdc\x8d\x01 \x01v\xa6@\x92t\xa5Ff\x7ff\x84A+\xc9\x81\xf6}\xdb\xc7$\xffh\xfb3\xc9\x87\xc0\xa1d]\xa0\xeb\x96\xfb\xbf\x03n\xb6\x0c\xe9@\xfbK\xc3}\x92\x1b`a4\x14\xe5\x8bFRI\xd2\xb5\xd8\xcelm\xa7\xb8V\xd2\x8f\xc0\xf3\x98\xf9\xf7g\xe01\xf0\x168\xd0\xae\x97\x15\xc03\xb0\x1b-\xa1\xac-\x1f\x92<\x02\xb7\xb4Ka\x97\xadWT\x92t\xdd\x92\x16r.YG\x17\x10O\xc0\x87$\xef\n\xef3\x06\x85ua\xec\x08g|(\x97\x8d\x83w\xc0m`\x81\xec\x92\xf1\xc8\x88\xb6\xf9\xf2aB\x92\xa4\xeb\xb0\x9d\xeb\xeaV\xd2\xcf[\xef\xff\n=B\x0e\xa5\x87\x90gF\x87\xd0\xbaPN\x9f\x0e\x10\xf4D9t<%t\t\xecF\x17hRj\xed\x97\xa4k\xb6=\xffa<\xb1\xa1\x97Ca\xe7\x90\x15\xba\x92lg\xbdX)\xa7\x7f\x01*\x0c\x91\xe2\xf5\x92\xdf\x19\x00\x00\x00\x00IEND\xaeB`\x82"

def  key_cap_5_image() :
  return '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x01\xe0\x00\x00\x00`\x08\x06\x00\x00\x00\xc5\x13\xa1\xf9\x00\x00\x06\x8cIDATx\x9c\xed\xdd\xddn\x1b\xe7\x15\x85\xe1w\x91\xa3\x1fGI\x9d\xa2Az\xef\xbd\x83\xdeO/\xa3E\xe5X\xb6Dj\xc8\xd5\x83o\xa8?\xc8ir\x10N\x81\xbe\x0f0\xc2\x984M\xfbhc\xaf\xd9\xdfv\xfe\xf6\xf7\x7fl![`zsm\x97+\x05\x02A\x92$\xfd7]\xeaf\x81\x03\xf4\x00\x99\x81\x99v&\x99\xa1\x87i)\xbe\x17\xc0%p\r\\\x01W\x85\xcb\xc0\xd4\xb2I\xd8\x00iKb\x1d\x96$\xe9\xad\x175\xb2\xa1G\xc8\x91v\x06\xf6\x84\x1deG\xf2\xc0\xd2\xd3Nm\xa7\x84K\xc8w\xc0\r\xf0C\xe1\xbb\x94\x0f\xe4T\x84\xbb\t\xc4\x1eX\x92\xa4o\x08\xa3\xf7\r\x85\x1c\x81\x99dO\xb9\x07\xbe\x96\xde\xd1l\x92\xd2\xd2)\xc9\x04\\\xb7\xbdI\xf83\xe4\xc7\xc0\xc7\xd2\x1fB\xae\t\x17!\x13\x10chI\x92\xde\x97\xd1\xd8\x16\xe8\x125?\x02\x0fM?\x07>\x85L\xa4\xa7\xe2|\x98\xa0\x13\xe4*\xc9\xf7\xc0\x8f\xc0\xcf\xc0O\x19\xf77\x94+\xc2\x04\xa4tc\x1b,I\xd27\x1d\x81\x8e\xe7\xbc\xec\xda~I\xb8\x85\\ut\xb1K$\xdd\xddD\x99\x9a^\x85\xdc\x14>\x06\xfe\x02\xfc\xb5\xe1\xa7\xc0\x0f\x84\x0f,CY\x81\xcd\x8a\xff(I\x92\xfew\x8d\xc9\xab#p`\x14\xda\xfb\xc0\xe7\xc2UF6\xbd+\xf9\x1a\xb8\x83L\x13\xc96p\t\xfd\x10\x9e\xba\xe0\x9fB~^\xee?0\x06\xb46#\x85\x1eS\xd1\x92$i\xc8\xd3\x0f\x8e\xcb\xb5\x07\xee\t\x1f\x96\xe4x\x17\xf8L\xb9]\xe6\xab\xb6\xa7\xe3F\x13\xe4\x921x\xf5}\xdb?%|\x84\xfcX\xb8I{I\xb2}\xfd\x1d\x92$\xe9\x8d\xb6=\x90\xec\x03_\xc6\xd3[\x1e\x08\xb7\xc05\xe9%c\xaej;1\xea\xe9\x06\x98\x08\x17\xc0U\x92\xeb\xc2w\x81\x1b\xe8\r\xc9%#\x86\xa6K\x1b,I\x92\x86\x17\xe7~!9\xa4\xdd-\xd5r\x07\xbd\x86\\\xd1^0\x06\x9f7@&J\x18\xe7|7<u\xc3\x9dB.\x81\xcb\x8c\xce\xf8\x8a\x8c\xa5\x1c\xa7\xad\x1c\x92$ix\xaa\x8d\xa5\t\x87e\x1a\xfa\x11zIr\xc1hr\xb7O\xbb5J\xa6\xe7zZh6\xa3\x18?U\xe8\xed\x9b+X~%IzO\x19\xd3V0\x06\x97\xb74\x9b\xd2m\x92Ma\x93\x17\xbb%\xa7S\x9e\xdc\x92\xd0<\'\xccO\xf79\xdd\xb7M\\\x85%I\xd2kyuw\\\xd2\xe5\x14Ne3tib\x97\xa9\xac\xe9\xf4\x81Wu\xb5M\x9f_9\xed\xdfxY|-\xc2\x92$=\xeb\xd2\xac\x8e\xe3\xbey\xd9\xcc\xf2\xea\xa5S\x0f\xfc\xfe\xb9\xde_or-\xbe\x92$\xbd\xf6+\xcb"\xf3\xce\xaf\xfar\xb1FO\xf3[c\xa1\xf4\x1f\xf0\xb7\x93$\xe9\xffM_\xd4\xd6>-\xd2\xc8S\x04\xcd\xcb\xe2\x9d\x84W\xc3\xceVcI\x92~\xbf\x17}\xf1\xdb\x11\xaa\xdf\xb6Z\xd2\xd5W\x92$\xfd~\xdf\xac\x9fF\xd0\x92$\xfd\xa1\x8c\xa0%I:7#hI\x92V`\x04-I\xd2:\x8c\xa0%I:7#hI\x92V`\x04-I\xd2:\x8c\xa0%I:7#hI\x92V`\x04-I\xd2:\x8c\xa0%I:7#hI\x92V`\x04-I\xd2:\x8c\xa0%I:7#hI\x92V`\x04-I\xd2:\x8c\xa0%I:7#hI\x92V`\x04-I\xd2:\x8c\xa0%I:7#hI\x92V`\x04-I\xd2:\x8c\xa0%I:7#hI\x92V`\x04-I\xd2:\x8c\xa0%I:7#hI\x92V`\x04-I\xd2:\x8c\xa0%I:7#hI\x92V`\x04-I\xd2:\x8c\xa0%I:7#hI\x92V`\x04-I\xd2:\x8c\xa0%I:7#hI\x92V`\x04-I\xd2:\x8c\xa0%I:7#hI\x92V`\x04-I\xd2:\x8c\xa0%I:7#hI\x92V`\x04-I\xd2:\x8c\xa0%I:7#hI\x92V`\x04-I\xd2:\x8c\xa0%I:7#hI\x92V`\x04-I\xd2:\x8c\xa0%I:7#hI\x92V`\x04-I\xd2:\x8c\xa0%I:7#hI\x92V`\x04-I\xd2:\x8c\xa0%I:7#hI\x92V`\x04-I\xd2:\x8c\xa0%I:7#hI\x92V`\x04-I\xd2:\x8c\xa0%I:7#hI\x92V`\x04-I\xd2:\x8c\xa0%I:7#hI\x92V\xf0\x9b"\xe8\xfeZ\x04\xdd\x97\x7f\x88\xe5X\x92\xa4\xd7\xbeQ\x1b\x9f\xab\xe9)\x82>\xfd\xc6i\xdc\xa6\rdy5I\xa1\x1do\xa5\x8c\xb6\xb9\xcb\x8f\xc4",I\xd2\x93\xe5\xb1\xed\xa9N6\xd0\xa5`vy\x9b\xd7\tt:=U\xe7\x86R\xf2\\\x86!K\xa1\x1do\x14\x9a4NhI\x92\xf4B\x9e\x07\xa7\xfa\xd4\xa4\x86\x966\xe3\x8d\xbe\x9a\xad\x02\xa6\xb6$\x19U\xba9\xd2\x1eI\x0e\xd0\x03\xe4\x00\xcc\xd0\xed\xf8X\x8e\xc4\xf2+I\xd2+K\xf1e$\xcd\x07\xd29\xe4@s,=\x04\x8e\xc0\xb1\xb4#e\x86i\xb99R\x8e\xc0<\xae>\xd2\xec\t{\xe0b\xe9\xa1\xa7\xbc\xfc\x1aI\x92\xf4\xd2)j>@\xf6\xc0>a\x0fy\x04f\xda9\xc9\x91Qs;-\x1f8\x12f\xe0\x11\xb2\x03\x1e\n\xf7\x81/\x1d\x19\xf6c`\x8b\xc5W\x92\xa4\xf7\x8d\x8c\xb9\xc0\x01\xd8\x03_\x80{\xda\x07\x92]\xc2(\xc4\xa3\x1b\xeeD{ \xcc-\xfb\x90{\xc2\x1d\xed/I\xae\x01\x92\xeeh.\t\xdb\xe5\x1b\xe2\x04\x96$I\xaf-\xe7|\x8f\x1d\x89\xf2>\xe1\xbe\xf46\xc9/\x8c\x86\xf6\x81t\x1f\x98I\x0e\x13\x19\x95:\xc9=p\x07\xdc\x92\\/G\x8f\xeeI>\x8c\x85\x1d\xddB6\x80\x8f\x81%IzW\x8fK\x04=\xd3\xde\x07>\x13\xfe\t\xfc;\xc9g\xe0\x1e\xd8C\x0f\x13d\x06v\x8cV\xf9\x13p\xc5\x18\x81\xde\x11n(W\xa3\x00\'\xc0\xc6\x14Z\x92\xa4w\x8cg\xb6GF\x18=\x93\xech\xbf\xb4\xbdM\xf2\xaf\xb6\x9fH\xbe\x06v%\xf3\x04\x9d\x97\xe7\xbew\xc0\xc5\x92a\xefh\x7fi\xb8Nr\x01L\x8c\x81\xea\xbc9\xc8$I\x92\xe0t\xd0\xb7\xcb\x16\x8d\x99\xf4\x11x\x18\x9do?\x01\xb7\x81\xcf\xc0\x8ev>u\xc0\x0f\xc0f\x1cIbn\xf9\x9a\xe4\x16\xb8\xa4\x9d\n\x9b,g\x95$I\xd2\xb7%-\xe4X2\x8f)h\xee\x81\xafI\xee\n_2\x8a\xf2<1&\xb22>\x94\xd3\x83\xe3;\xe020A6\xc9XY\xd96o\x97IK\x92\xa4e\x8ds\xd2\xa5\xa4\x1e\x97\xb3\xbf3t\x0f\xd9\x95\xeeB\x1e\x18\x13\xd2\xf3D9<\x1f \xee\x81\xb2\xeb\xf8_\x92\xa6\xc0f\x9cBJJ\xad\xbd\x92$}\xcb\xb2\x7frl\x8c\xeci)\xc71d\x86\xce$\xcb\xae\rf\xca\xe1?\xff.\x91\xe2\xff\x7f5\x07\x00\x00\x00\x00IEND\xaeB`\x82'


#   END_IMAGES


class   a_key(wx.BitmapButton) :
    """ Define a single key. """

    def __init__(me, parent, bitmap, rtn, txt_out, hover_text, down_text, *args, **kwargs) :
        """ Initialize a key. """
        kwargs['style'] = kwargs.get('style', 0) + wx.BORDER_NONE
        super(a_key, me).__init__(parent, wx.ID_ANY, bitmap, *args, **kwargs)

        me.img          = bitmap

        me.SetBitmapDisabled(bitmap)
        me.SetBitmapFocus(bitmap)
        me.SetBitmapHover(bitmap)
        me.SetBitmapSelected(bitmap)

        me.rtn          = rtn           or getattr(parent, 'on_text', None) or getattr(getattr(parent, 'parent', None), 'on_text', None)
        me.txt_out      = txt_out       or ""
        me.hover_text   = hover_text    or ""
        me.down_text    = down_text     or ""

        me.pushed_cnt   = 0

        me.Bind(wx.EVT_BUTTON, me.on_pushed)

        me.Bind(wx.EVT_SET_FOCUS, me.on_focus)


    def on_focus(me, evt) :
        """ Don't grab the focus. """
        # evt.Skip(True)
        pass


    def on_pushed(me, evt) :
        """ The key is pushed. """
        me.pushed_cnt  += 1
        if  me.rtn      :
            me.rtn(me.txt_out)
        evt.Skip(True)


    #   a_key



class   a_board(wx.Panel) :
    """ Define a single keyboard. """

    def __init__(me, parent, *args, **kwargs) :
        """ Initialize a single keyboard. """
        super(a_board, me).__init__(parent, *args, **kwargs)

        me.SetBackgroundColour(GRADIENT_BACKGROUND)

        me.parent       = parent
        me.keys         = []

        me.cursoring    = True

        me.margin       = MARGIN            # for GTK, at least

        me.Bind(wx.EVT_PAINT, me.on_paint)

        me.Hide()


    def on_paint(me, evt = None) :
        """ Paint ourself. """
        dc      = wx.PaintDC(me)
        w, h    = me.GetClientSize()
        dc.GradientFillLinear( ( 0, 0, w, h ), me.GetBackgroundColour(), GRADIENT_TOP, wx.NORTH)


    def layout(me) :
        """ Set the key positions and sizes. """
        me.SetClientSize(me.parent.Size)
        w, h    = me.GetClientSize()
        x       = 0
        y       = 0
        ph      = 0
        for k in me.keys :
            kw, kh  = k.GetBitmapLabel().Size
            kw += me.margin
            kh += me.margin

            xx  = k.x
            if  xx  < 0 :
                xx  = x
                if  xx and (kw + x >= w) :
                    xx  = 0
                    y  += ph
                pass
            else    :
                xx  = w * xx
            x       = xx + kw

            yy  = k.y
            if  yy  < 0 :
                yy  = y
            else    :
                yy  = h * yy
                y   = yy
            ph      = kh

            k.SetPosition(( xx, yy ))
            k.SetSize((kw, kh))
        me.Refresh()


    def append(me, k, x = None, y = None, layout = True) :
        """ Add a key to the board. """
        k.x = x
        k.y = y
        me.keys.append(k)
        if  layout :
            me.layout()
        pass


    def show_cursor(me, how = None) :
        """ Show or hide the cursor appropriately. """
        ov  = me.cursoring
        if  how != None     :
            me.cursoring    = how
            c               = ((not how) and wx.StockCursor(wx.CURSOR_BLANK)) or wx.NullCursor
            me.SetCursor(c)
            for w in me.keys :
                w.SetCursor(c)
            pass
        return(ov)


    #   a_board


bitmaps = {}                # some speedup, but only some
#                             painting probably doesn't take the time as the time is consistent no matter what the size if scaling is disabled
#                             scaling (up especially) the key images takes the most time
#                             making the buttons may take the too-much rest of the time
#                             anyway, create_boards() takes all the time

def load_bitmap(bmp) :
    if  isinstance(bmp, basestring) :
        if  bmp in bitmaps :
            bmp = bitmaps[bmp]
        else    :
            bname   = bmp
            try     :
                ifn = os.path.splitext(os.path.basename(bmp))[0] + '_image'
            except  :
                ifn = "---"
            if  ifn not in globals() :
                bmp = wx.Bitmap(bmp)
            else    :
                bmp = wx.BitmapFromImage(wx.ImageFromStream(cStringIO.StringIO(globals()[ifn]())))
            bitmaps[bname]  = bmp
        pass
    return(bmp)


def insure_image(bmp) :
    """ Return a wx image for the given bitmap or named image file. """
    bmp     = load_bitmap(bmp)
    if  isinstance(bmp, wx.Bitmap) :
        bmp = wx.ImageFromBitmap(bmp)
    return(bmp)


def insure_bitmap(bmp) :
    """ Return a wx image for the given bitmap or named image file. """
    bmp     = load_bitmap(bmp)
    if  isinstance(bmp, wx.Image) :
        bmp = wx.BitmapFromImage(bmp)
    return(bmp)


def scale_bmp(bmp, scale) :
    """ Scale the given bitmap. """
    bmp     = insure_image(bmp)
    w, h    = bmp.GetSize()
    bmp     = bmp.Scale(max(10, int(scale * w)), max(10, int(scale * h)), wx.IMAGE_QUALITY_HIGH)
    return(wx.BitmapFromImage(bmp))


def paint_text_on_bmp(bmp, text, scale) :
    """ Paint the given text in the image. """
    bmp     = insure_bitmap(bmp)
    w, h    = bmp.GetSize()
    dc      = wx.MemoryDC(bmp)
    dc.SetFont(wx.Font(FONT_SIZE * scale, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD))
    tw, th, d, ld   = dc.GetFullTextExtent(text)
    th      = sum([th, d, ld])                          # yeah, well, we do what works
    x       = max(0, (w - tw) / 2)
    y       = max(0, (h - th) / 2)
    dc.SetLogicalFunction(wx.COPY)
    dc.SetTextForeground(FONT_COLOR)
    dc.DrawText(text, x, y)
    return(bmp)


def paint_image_on_bmp(bmp, img) :
    """ Paint the given text in the image. """
    bmp     = insure_bitmap(bmp)
    img     = insure_bitmap(img)
    w,  h   = bmp.GetSize()
    wi, hi  = img.GetSize()
    dc      = wx.MemoryDC(bmp)
    x       = max(0, (w - wi) / 2)
    y       = max(0, (h - hi) / 2)
    dc.DrawBitmap(img, x, y, True)
    return(bmp)



def key_len(r) :
    """ Return the width of this string in keys. """
    return(len(r) + len(re.sub(r'[^\002\003 \b]', '', r)) + len(re.sub(r'[^ ]', '', r)) + len(re.sub(r'[^ ]', '', r)) + len(re.sub(r'[^ ]', '', r)))


class   a_keyboard(wx.Panel) :
    """ Define a keyboard - a set of boards. """

    MAGIC_TAKEUP    = 0.995

    def get_metrics(me) :
        """ Return information about the size of the keyboard in keys and 0..1 key width/hite. """
        lc  = [ '1234567890-=/\b', '\001qwertyuiop[]`', "\002asdfghjkl;'\002", 'zxcvb nm,.\\', ]
        uc  = [ '!@#$%^&*()_+|\b',  '\001QWERTYUIOP{}~', '\002ASDFGHJKL:"\002', 'ZXCVB NM<>?', ]
        cc  = [ '1234567890-=/\b', '\001QWERTYUIOP[]`', "\003ASDFGHJKL;'\003", 'ZXCVB NM,.\\', ]

        mxh = max(len(lc), len(uc))
        kh  = me.MAGIC_TAKEUP / mxh

        mxw = max([ key_len(r) for r in lc + uc + cc])
        kw  = me.MAGIC_TAKEUP / mxw

        return([ lc, uc, cc ], mxw, mxh, kw, kh)


    def get_key_size(me) :
        """ Return the size of a normal key at the current scale. """
        return(insure_bitmap('key_cap.png').Size)


    def create_boards(me, layout = True) :
        """ Create our sub-keyboards, one for each "shift" state. """
        for kb in me.boards :
            kb.Destroy()

        kbs, mxw, mxh, kw, kh   = me.get_metrics()

        me.boards   = []
        for bc in kbs :
            brd     = a_board(me)
            me.boards.append(brd)

            for ri, r in enumerate(bc) :
                wst = (kw * (mxw - key_len(r))) / 2.0
                ci  = 0
                for c in list(r) :
                    if  c  == '\b' :
                        bmp = paint_image_on_bmp(scale_bmp('key_cap_2.png',         me.scale), scale_bmp('backspace.png',   me.scale))
                        brd.append(a_key(brd, bmp, None, c, '', ''),                wst + (kw * ci), ri * kh, layout = False)
                    elif c == '\001' :
                        pass
                    elif c == '\002' :
                        bmp = paint_image_on_bmp(scale_bmp('key_cap_2.png',         me.scale), scale_bmp('shift.png',       me.scale))
                        brd.append(a_key(brd, bmp, me.do_next_shift, '', '', ''),   wst + (kw * ci), ri * kh, layout = False)
                    elif c == '\003' :
                        bmp = paint_image_on_bmp(scale_bmp('key_cap_2.png',         me.scale), scale_bmp('shift_shift.png', me.scale))
                        brd.append(a_key(brd, bmp, me.do_next_shift, '', '', ''),   wst + (kw * ci), ri * kh, layout = False)
                    elif c == ' ' :
                        bmp = paint_text_on_bmp( scale_bmp('key_cap_5.png',         me.scale), c, me.scale)
                        brd.append(a_key(brd, bmp, None, c, '', ''),                wst + (kw * ci), ri * kh, layout = False)
                    else    :
                        bmp = paint_text_on_bmp( scale_bmp('key_cap.png',           me.scale), c, me.scale)
                        brd.append(a_key(brd, bmp, None, c, '', ''),                wst + (kw * ci), ri * kh, layout = False)
                    ci     += key_len(c)
                pass
            if  layout :
                brd.layout()
            pass
        pass


    def __init__(me, parent, scale = None, *args, **kwargs) :
        """ Initialize a set of keyboards. """
        super(a_keyboard, me).__init__(parent, *args, **kwargs)

        me.scale        = scale or 1.0

        me.need_size    = True

        me.Bind(wx.EVT_SIZE, me.on_size)

        me.shifted      = 0

        me.text_ctrl    = None

        me.boards       = []
        me.create_boards()

        me.showing      = True              #: whether we are wanted to be showing (if the focus is in someplace we want to be showing for)
        me.cursoring    = True

        me.idle_rtn     = None              #: routine to call at end of our on_idle

        me.op_cnt       = 0                 #: count the times he hits a key, any key

        me.Hide()

        me.Bind(wx.EVT_IDLE, me.on_idle)



    def set_showing(me, how) :
        """ Set/get whether to show ourselves. """
        ov  = me.showing
        if  how != None :
            me.showing  = how
            if  not me.showing :
                me.Hide()
            pass
        return(ov)


    def set_idle(me, rtn = None) :
        """ Get/set the on-idle routine to call - or False to remove any. """
        ov  = me.idle_rtn
        if  rtn != None :
            me.idle_rtn = rtn
        return(ov)


    def on_idle(me, evt = None) :
        """ Try to find the focus window so we can feed it text. """
        win = wx.Window.FindFocus()
        if  me.showing and hasattr(win, 'SetInsertionPoint') :
            me.do_size()                        # this is so at least we usually only do one full rebuild when the user sizes us
            me.text_ctrl    = win
            me.boards[me.shifted].Show()
            me.Show()
        elif win and (not isinstance(win, a_key)) :
            me.Hide()
        elif me.IsShown()   :
            me.do_size()

        if  me.idle_rtn :
            me.idle_rtn(evt)
        pass


    def fix_focus(me) :
        """ Fix the focus to go back to the control who we feed. """
        i           = 0
        if  me.text_ctrl :
            f, t    = me.text_ctrl.GetSelection()
            i       = me.text_ctrl.GetInsertionPoint()

            win     = me.text_ctrl
            if  win :
                win.SetFocus()
            wa      = []
            while win :
                wa.append(win)
                win = win.GetParent()
            wa.reverse()
            for win in wa :
                win.Raise()                     # gets the frame to be the active window

            me.text_ctrl.SetInsertionPoint(i)
            me.text_ctrl.SetSelection(f, t)

        return(i)


    def shift(me, how = None) :
        """ Get/set the shift situation. 0=unshift 1=shift-for-one-key 2=caps-lock -1=next-shift-state """
        ov  = me.shifted
        if  how != None :
            if  how < 0 :
                me.shifted      = (me.shifted + 1) % len(me.boards)
            elif 0 <= how <= 2  :
                me.shifted      = how
            for b in xrange(len(me.boards)) :
                me.boards[b].Hide()
            me.boards[me.shifted].Show()
            me.layout()
            me.Refresh()
        return(ov)

    def do_next_shift(me, txt) :
        """ Do a shift key. """
        me.op_cnt  += 1
        me.shift(-1)
        me.fix_focus()


    def on_size(me, evt = None) :
        """ The size changed, maybe. """
        me.need_size    = True
        if  evt :
            evt.Skip()
        pass


    def layout(me) :
        """ Layout the boards. """
        w, h    = me.GetClientSize()
        for brd in me.boards :
            brd.SetPosition((0, 0))
            brd.SetSize(( w, h ))
            brd.layout()                                            # a gob of these after we setting down can take a bit of time, too. up to a half second on spring
        # print "@@@@", me.scale, kw, mxw, me.GetClientSize()
        pass


    def do_size(me) :
        """ Do the size change. """
        if  me.need_size            :
            me.need_size            = False

            w, h                    = me.GetClientSize()
            kbs, mxw, mxh, kw, kh   = me.get_metrics()
            kw, kh                  = me.get_key_size()
            scale                   = min((w * me.MAGIC_TAKEUP) / (kw * mxw), (h * me.MAGIC_TAKEUP) / (kh * mxh))
            if  abs(scale - me.scale) >= (0.01 * scale) :
                me.scale            = scale
                me.create_boards(layout = False)                        # this is what takes the time - can be over a second on spring - a normal window resizing can do a couple of these
            me.layout()
        pass


    def on_text(me, txt) :
        """ Put text to our current text entry TextCtrl. """
        me.op_cnt  += 1
        if  me.shifted == 1 :
            me.boards[1].Hide()
            me.boards[0].Show()
            me.shifted  = 0
        if  me.text_ctrl :
            i   = me.fix_focus()
            if  sys.platform.find('linux') >= 0 :
                evt = wx.KeyEvent()
                evt.m_keyCode   = ord(txt)
                if  txt.isupper() :
                    evt.m_shiftDown = True
                me.text_ctrl.EmulateKeyPress(evt)
            else :
                if  txt == '\b' :
                    if  i :
                        me.text_ctrl.Remove(i - 1, i)
                        me.text_ctrl.SetSelection(0, 0)
                        me.text_ctrl.SetInsertionPoint(i - 1)
                    pass
                else :
                    me.text_ctrl.SetInsertionPoint(i)
                    me.text_ctrl.WriteText(txt)
                    me.text_ctrl.SetInsertionPoint(i + 1)
                pass
            pass
        pass


    def pop_up(me) :
        """ Pop up the keyboard and input to the window with the focus. """
        ctrl                = wx.Window.FindFocus()
        if  hasattr(ctrl, 'SetInsertionPoint') :
            me.text_ctrl    = ctrl
        me.showing(True)
        me.Show()


    def pop_down(me) :
        """ Dismiss the keyboard. """
        me.showing(False)


    def show_cursor(me, how = None) :
        """ Show or hide the cursor appropriately. """
        ov  = me.cursoring
        if  how != None         :
            me.cursoring        = how
            for b in me.boards  :
                b.show_cursor(me.cursoring)
            pass
        return(ov)


    #   a_keyboard


#
#
#       Test
#
#
if __name__ == '__main__' :

    if  (len(sys.argv) == 2) and (sys.argv[1] == '--make_images') :
        from    PIL import  Image

        import  tzlib
        import  stretch_image

        ire = re.compile(r'\#\s*BEGIN_IMAGES.+?\#\s+END_IMAGES', re.DOTALL)

        s   = "\n"
        for fn_w in [
                    [ 'backspace.png',   0 ],
                    [ 'shift.png',       0 ],
                    [ 'shift_shift.png', 0 ],
                    [ 'key_cap.png',     0 ],
                    [ 'key_cap.png',     2 ],
                    [ 'key_cap.png',     3 ],
                    [ 'key_cap.png',     4 ],
                    [ 'key_cap.png',     5 ],
                  ] :
            fn, w   = fn_w
            if  w   :
                n,e = os.path.splitext(fn)
                img = Image.open(fn)
                nim = stretch_image.stretch_image(img, img.size[0] * w)
                img = cStringIO.StringIO()
                nim.save(img, e.replace('.', ''))
                img = img.getvalue()
                fn  = "%s_%u%s" % ( n, w, e, )
            else    :
                img = tzlib.read_whole_binary_file(fn)

            s      += ('def  %s_image() :\n  return ' % os.path.splitext(os.path.basename(fn))[0]) + repr(img) + "\n\n"

        sfn = 'tz_wx_keyboard.py'
        src = tzlib.read_whole_text_file(sfn)
        s1  = src
        src = re.sub(r'\r?\n', '\n', src)
        src = src.replace('\r', '')
        src = ire.sub('#   BEGIN_\x49MAGES\n\n' + '<\x49MAGES>' + '\n#   END_\x49MAGES', src)
        src = src.replace('<\x49MAGES>', s)
        if  src != s1 :
            tzlib.write_whole_binary_file(sfn, src)                     # update this source file with the latest in keyboard images
        else :
            print "Nothing to update."
        pass
    else    :
        import  tz_wx_widgets

        class   a_frame(wx.Frame) :
            def __init__(me, *args, **kwargs) :
                super(a_frame, me).__init__(*args, **kwargs)
                me.kbrd = None
                me.Bind(wx.EVT_SIZE, me.on_size)
                wx.EVT_CLOSE(me,     me.on_close_window)
            def set_kbrd(me, kbrd) :
                me.kbrd = kbrd
            def on_size(me, evt = None) :
                if  me.kbrd :
                    w, h    = me.GetClientSize()
                    me.kbrd.SetSize((w, max(30, h - 30)))
                    me.kbrd.SetPosition(( 0, 30 ))
                    me.kbrd.Show()
                    me.kbrd.boards[me.kbrd.shifted].Show()
                    me.kbrd.on_size()
                pass
            def on_close_window(me, evt = None) :
                me.dwin.Destroy()
                me.Destroy()
            #   a_frame


        app     = wx.App()
        frame   = a_frame(None, title = "tz_wx_keyboard")
        txt_in  = wx.TextCtrl(frame, wx.ID_ANY, size = ( 500, -1, ))
        me      = a_keyboard(frame, scale = 0.5, size = ( -1, -1), pos = ( 0, 30 ))
        frame.set_kbrd(me)
        frame.Show()
        frame.on_size()
        txt_in.SetFocus()
        frame.dwin  = tz_wx_widgets.a_text_entry_popup(None, wx.ID_ANY, "blah title", "prompt")
        frame.dwin.Show()
        me.show_cursor(True)
        app.MainLoop()

    pass

#
#
#
# eof
