Dialog for building Tkinter accelerator key bindings
class GetKeysDialog(Toplevel
):
def __init__(self
,parent
,title
,action
,currentKeySequences
):
action - string, the name of the virtual event these keys will be
currentKeys - list, a list of all key sequence lists currently mapped
to virtual events, for overlap checking
Toplevel
.__init
__(self
, parent
)
self
.configure(borderwidth
=5)
self
.resizable(height
=FALSE
,width
=FALSE
)
self
.protocol("WM_DELETE_WINDOW", self
.Cancel
)
self
.currentKeySequences
=currentKeySequences
self
.keyString
=StringVar(self
)
self
.SetModifiersForPlatform()
for modifier
in self
.modifiers
:
variable
= StringVar(self
)
self
.modifier_vars
.append(variable
)
self
.withdraw() #hide while setting geometry
((parent
.winfo_rootx()+((parent
.winfo_width()/2)
-(self
.winfo_reqwidth()/2)),
parent
.winfo_rooty()+((parent
.winfo_height()/2)
-(self
.winfo_reqheight()/2)) )) ) #centre dialog over parent
self
.deiconify() #geometry set, unhide
frameMain
= Frame(self
,borderwidth
=2,relief
=SUNKEN
)
frameMain
.pack(side
=TOP
,expand
=TRUE
,fill
=BOTH
)
frameButtons
.pack(side
=BOTTOM
,fill
=X
)
self
.buttonOK
= Button(frameButtons
,text
='OK',
self
.buttonOK
.grid(row
=0,column
=0,padx
=5,pady
=5)
self
.buttonCancel
= Button(frameButtons
,text
='Cancel',
width
=8,command
=self
.Cancel
)
self
.buttonCancel
.grid(row
=0,column
=1,padx
=5,pady
=5)
self
.frameKeySeqBasic
= Frame(frameMain
)
self
.frameKeySeqAdvanced
= Frame(frameMain
)
self
.frameControlsBasic
= Frame(frameMain
)
self
.frameHelpAdvanced
= Frame(frameMain
)
self
.frameKeySeqAdvanced
.grid(row
=0,column
=0,sticky
=NSEW
,padx
=5,pady
=5)
self
.frameKeySeqBasic
.grid(row
=0,column
=0,sticky
=NSEW
,padx
=5,pady
=5)
self
.frameKeySeqBasic
.lift()
self
.frameHelpAdvanced
.grid(row
=1,column
=0,sticky
=NSEW
,padx
=5)
self
.frameControlsBasic
.grid(row
=1,column
=0,sticky
=NSEW
,padx
=5)
self
.frameControlsBasic
.lift()
self
.buttonLevel
= Button(frameMain
,command
=self
.ToggleLevel
,
text
='Advanced Key Binding Entry >>')
self
.buttonLevel
.grid(row
=2,column
=0,stick
=EW
,padx
=5,pady
=5)
labelTitleBasic
= Label(self
.frameKeySeqBasic
,
text
="New keys for '"+self
.action
+"' :")
labelTitleBasic
.pack(anchor
=W
)
labelKeysBasic
= Label(self
.frameKeySeqBasic
,justify
=LEFT
,
textvariable
=self
.keyString
,relief
=GROOVE
,borderwidth
=2)
labelKeysBasic
.pack(ipadx
=5,ipady
=5,fill
=X
)
self
.modifier_checkbuttons
= {}
for modifier
, variable
in zip(self
.modifiers
, self
.modifier_vars
):
label
= self
.modifier_label
.get(modifier
, modifier
)
check
=Checkbutton(self
.frameControlsBasic
,
command
=self
.BuildKeyString
,
text
=label
,variable
=variable
,onvalue
=modifier
,offvalue
='')
check
.grid(row
=0,column
=column
,padx
=2,sticky
=W
)
self
.modifier_checkbuttons
[modifier
] = check
labelFnAdvice
=Label(self
.frameControlsBasic
,justify
=LEFT
,
"Select the desired modifier keys\n"+
"above, and the final key from the\n"+
"list on the right.\n\n" +
"Use upper case Symbols when using\n" +
"the Shift modifier. (Letters will be\n" +
"converted automatically.)")
labelFnAdvice
.grid(row
=1,column
=0,columnspan
=4,padx
=2,sticky
=W
)
self
.listKeysFinal
=Listbox(self
.frameControlsBasic
,width
=15,height
=10,
self
.listKeysFinal
.bind('<ButtonRelease-1>',self
.FinalKeySelected
)
self
.listKeysFinal
.grid(row
=0,column
=4,rowspan
=4,sticky
=NS
)
scrollKeysFinal
=Scrollbar(self
.frameControlsBasic
,orient
=VERTICAL
,
command
=self
.listKeysFinal
.yview
)
self
.listKeysFinal
.config(yscrollcommand
=scrollKeysFinal
.set)
scrollKeysFinal
.grid(row
=0,column
=5,rowspan
=4,sticky
=NS
)
self
.buttonClear
=Button(self
.frameControlsBasic
,
text
='Clear Keys',command
=self
.ClearKeySeq
)
self
.buttonClear
.grid(row
=2,column
=0,columnspan
=4)
labelTitleAdvanced
= Label(self
.frameKeySeqAdvanced
,justify
=LEFT
,
text
="Enter new binding(s) for '"+self
.action
+"' :\n"+
"(These bindings will not be checked for validity!)")
labelTitleAdvanced
.pack(anchor
=W
)
self
.entryKeysAdvanced
=Entry(self
.frameKeySeqAdvanced
,
textvariable
=self
.keyString
)
self
.entryKeysAdvanced
.pack(fill
=X
)
labelHelpAdvanced
=Label(self
.frameHelpAdvanced
,justify
=LEFT
,
text
="Key bindings are specified using Tkinter keysyms as\n"+
"in these samples: <Control-f>, <Shift-F2>, <F12>,\n"
"<Control-space>, <Meta-less>, <Control-Alt-Shift-X>.\n"
"Upper case is used when the Shift modifier is present!\n\n" +
"'Emacs style' multi-keystroke bindings are specified as\n" +
"follows: <Control-x><Control-y>, where the first key\n" +
"is the 'do-nothing' keybinding.\n\n" +
"Multiple separate bindings for one action should be\n"+
"separated by a space, eg., <Alt-v> <Meta-v>." )
labelHelpAdvanced
.grid(row
=0,column
=0,sticky
=NSEW
)
def SetModifiersForPlatform(self
):
"""Determine list of names of key modifiers for this platform.
The names are used to build Tk bindings -- it doesn't matter if the
keyboard has these keys, it matters if Tk understands them. The
order is also important: key binding equality depends on it, so
config-keys.def must use the same ordering.
if sys
.platform
== 'darwin' and sys
.executable
.count('.app'):
self
.modifiers
= ['Shift', 'Control', 'Option', 'Command']
self
.modifiers
= ['Control', 'Alt', 'Shift']
self
.modifier_label
= {'Control': 'Ctrl'}
if self
.buttonLevel
.cget('text')[:8]=='Advanced':
self
.buttonLevel
.config(text
='<< Basic Key Binding Entry')
self
.frameKeySeqAdvanced
.lift()
self
.frameHelpAdvanced
.lift()
self
.entryKeysAdvanced
.focus_set()
self
.buttonLevel
.config(text
='Advanced Key Binding Entry >>')
self
.frameKeySeqBasic
.lift()
self
.frameControlsBasic
.lift()
def FinalKeySelected(self
,event
):
def BuildKeyString(self
):
keyList
= modifiers
= self
.GetModifiers()
finalKey
= self
.listKeysFinal
.get(ANCHOR
)
finalKey
= self
.TranslateKey(finalKey
, modifiers
)
self
.keyString
.set('<' + string
.join(keyList
,'-') + '>')
modList
= [variable
.get() for variable
in self
.modifier_vars
]
return filter(None, modList
)
self
.listKeysFinal
.select_clear(0,END
)
self
.listKeysFinal
.yview(MOVETO
, '0.0')
for variable
in self
.modifier_vars
:
def LoadFinalKeyList(self
):
#these tuples are also available for use in validity checks
self
.functionKeys
=('F1','F2','F2','F4','F5','F6','F7','F8','F9',
self
.alphanumKeys
=tuple(string
.ascii_lowercase
+string
.digits
)
self
.punctuationKeys
=tuple('~!@#%^&*()_-+={}[]|;:,.<>/?')
self
.whitespaceKeys
=('Tab','Space','Return')
self
.editKeys
=('BackSpace','Delete','Insert')
self
.moveKeys
=('Home','End','Page Up','Page Down','Left Arrow',
'Right Arrow','Up Arrow','Down Arrow')
#make a tuple of most of the useful common 'final' keys
keys
=(self
.alphanumKeys
+self
.punctuationKeys
+self
.functionKeys
+
self
.whitespaceKeys
+self
.editKeys
+self
.moveKeys
)
self
.listKeysFinal
.insert(END
, *keys
)
def TranslateKey(self
, key
, modifiers
):
"Translate from keycap symbol to the Tkinter keysym"
translateDict
= {'Space':'space',
'~':'asciitilde','!':'exclam','@':'at','#':'numbersign',
'%':'percent','^':'asciicircum','&':'ampersand','*':'asterisk',
'(':'parenleft',')':'parenright','_':'underscore','-':'minus',
'+':'plus','=':'equal','{':'braceleft','}':'braceright',
'[':'bracketleft',']':'bracketright','|':'bar',';':'semicolon',
':':'colon',',':'comma','.':'period','<':'less','>':'greater',
'/':'slash','?':'question','Page Up':'Prior','Page Down':'Next',
'Left Arrow':'Left','Right Arrow':'Right','Up Arrow':'Up',
'Down Arrow': 'Down', 'Tab':'tab'}
if key
in translateDict
.keys():
if 'Shift' in modifiers
and key
in string
.ascii_lowercase
:
def OK(self
, event
=None):
self
.result
=self
.keyString
.get()
def Cancel(self
, event
=None):
"Validity check on user's keybinding selection"
keys
= self
.keyString
.get()
finalKey
= self
.listKeysFinal
.get(ANCHOR
)
modifiers
= self
.GetModifiers()
# create a key sequence list for overlap check:
keySequence
= keys
.split()
title
= 'Key Sequence Error'
tkMessageBox
.showerror(title
=title
, parent
=self
,
message
='No keys specified.')
elif not keys
.endswith('>'):
tkMessageBox
.showerror(title
=title
, parent
=self
,
message
='Missing the final Key')
elif not modifiers
and finalKey
not in self
.functionKeys
:
tkMessageBox
.showerror(title
=title
, parent
=self
,
message
='No modifier key(s) specified.')
elif (modifiers
== ['Shift']) \
self
.functionKeys
+ ('Tab', 'Space')):
msg
= 'The shift modifier by itself may not be used with' \
' this key symbol; only with F1-F12, Tab, or Space'
tkMessageBox
.showerror(title
=title
, parent
=self
,
elif keySequence
in self
.currentKeySequences
:
msg
= 'This key combination is already in use.'
tkMessageBox
.showerror(title
=title
, parent
=self
,
if __name__
== '__main__':
dlg
=GetKeysDialog(root
,'Get Keys','find-again',[])
Button(root
,text
='Dialog',command
=run
).pack()