2 Dialog for building Tkinter accelerator key bindings
8 class GetKeysDialog(Toplevel
):
9 def __init__(self
,parent
,title
,action
,currentKeySequences
):
11 action - string, the name of the virtual event these keys will be
13 currentKeys - list, a list of all key sequence lists currently mapped
14 to virtual events, for overlap checking
16 Toplevel
.__init
__(self
, parent
)
17 self
.configure(borderwidth
=5)
18 self
.resizable(height
=FALSE
,width
=FALSE
)
20 self
.transient(parent
)
22 self
.protocol("WM_DELETE_WINDOW", self
.Cancel
)
25 self
.currentKeySequences
=currentKeySequences
27 self
.keyString
=StringVar(self
)
28 self
.keyString
.set('')
29 self
.SetModifiersForPlatform() # set self.modifiers, self.modifier_label
30 self
.modifier_vars
= []
31 for modifier
in self
.modifiers
:
32 variable
= StringVar(self
)
34 self
.modifier_vars
.append(variable
)
37 self
.LoadFinalKeyList()
38 self
.withdraw() #hide while setting geometry
39 self
.update_idletasks()
40 self
.geometry("+%d+%d" %
41 ((parent
.winfo_rootx()+((parent
.winfo_width()/2)
42 -(self
.winfo_reqwidth()/2)),
43 parent
.winfo_rooty()+((parent
.winfo_height()/2)
44 -(self
.winfo_reqheight()/2)) )) ) #centre dialog over parent
45 self
.deiconify() #geometry set, unhide
48 def CreateWidgets(self
):
49 frameMain
= Frame(self
,borderwidth
=2,relief
=SUNKEN
)
50 frameMain
.pack(side
=TOP
,expand
=TRUE
,fill
=BOTH
)
51 frameButtons
=Frame(self
)
52 frameButtons
.pack(side
=BOTTOM
,fill
=X
)
53 self
.buttonOK
= Button(frameButtons
,text
='OK',
54 width
=8,command
=self
.OK
)
55 self
.buttonOK
.grid(row
=0,column
=0,padx
=5,pady
=5)
56 self
.buttonCancel
= Button(frameButtons
,text
='Cancel',
57 width
=8,command
=self
.Cancel
)
58 self
.buttonCancel
.grid(row
=0,column
=1,padx
=5,pady
=5)
59 self
.frameKeySeqBasic
= Frame(frameMain
)
60 self
.frameKeySeqAdvanced
= Frame(frameMain
)
61 self
.frameControlsBasic
= Frame(frameMain
)
62 self
.frameHelpAdvanced
= Frame(frameMain
)
63 self
.frameKeySeqAdvanced
.grid(row
=0,column
=0,sticky
=NSEW
,padx
=5,pady
=5)
64 self
.frameKeySeqBasic
.grid(row
=0,column
=0,sticky
=NSEW
,padx
=5,pady
=5)
65 self
.frameKeySeqBasic
.lift()
66 self
.frameHelpAdvanced
.grid(row
=1,column
=0,sticky
=NSEW
,padx
=5)
67 self
.frameControlsBasic
.grid(row
=1,column
=0,sticky
=NSEW
,padx
=5)
68 self
.frameControlsBasic
.lift()
69 self
.buttonLevel
= Button(frameMain
,command
=self
.ToggleLevel
,
70 text
='Advanced Key Binding Entry >>')
71 self
.buttonLevel
.grid(row
=2,column
=0,stick
=EW
,padx
=5,pady
=5)
72 labelTitleBasic
= Label(self
.frameKeySeqBasic
,
73 text
="New keys for '"+self
.action
+"' :")
74 labelTitleBasic
.pack(anchor
=W
)
75 labelKeysBasic
= Label(self
.frameKeySeqBasic
,justify
=LEFT
,
76 textvariable
=self
.keyString
,relief
=GROOVE
,borderwidth
=2)
77 labelKeysBasic
.pack(ipadx
=5,ipady
=5,fill
=X
)
78 self
.modifier_checkbuttons
= {}
80 for modifier
, variable
in zip(self
.modifiers
, self
.modifier_vars
):
81 label
= self
.modifier_label
.get(modifier
, modifier
)
82 check
=Checkbutton(self
.frameControlsBasic
,
83 command
=self
.BuildKeyString
,
84 text
=label
,variable
=variable
,onvalue
=modifier
,offvalue
='')
85 check
.grid(row
=0,column
=column
,padx
=2,sticky
=W
)
86 self
.modifier_checkbuttons
[modifier
] = check
88 labelFnAdvice
=Label(self
.frameControlsBasic
,justify
=LEFT
,
90 "Select the desired modifier keys\n"+
91 "above, and the final key from the\n"+
92 "list on the right.\n\n" +
93 "Use upper case Symbols when using\n" +
94 "the Shift modifier. (Letters will be\n" +
95 "converted automatically.)")
96 labelFnAdvice
.grid(row
=1,column
=0,columnspan
=4,padx
=2,sticky
=W
)
97 self
.listKeysFinal
=Listbox(self
.frameControlsBasic
,width
=15,height
=10,
99 self
.listKeysFinal
.bind('<ButtonRelease-1>',self
.FinalKeySelected
)
100 self
.listKeysFinal
.grid(row
=0,column
=4,rowspan
=4,sticky
=NS
)
101 scrollKeysFinal
=Scrollbar(self
.frameControlsBasic
,orient
=VERTICAL
,
102 command
=self
.listKeysFinal
.yview
)
103 self
.listKeysFinal
.config(yscrollcommand
=scrollKeysFinal
.set)
104 scrollKeysFinal
.grid(row
=0,column
=5,rowspan
=4,sticky
=NS
)
105 self
.buttonClear
=Button(self
.frameControlsBasic
,
106 text
='Clear Keys',command
=self
.ClearKeySeq
)
107 self
.buttonClear
.grid(row
=2,column
=0,columnspan
=4)
108 labelTitleAdvanced
= Label(self
.frameKeySeqAdvanced
,justify
=LEFT
,
109 text
="Enter new binding(s) for '"+self
.action
+"' :\n"+
110 "(These bindings will not be checked for validity!)")
111 labelTitleAdvanced
.pack(anchor
=W
)
112 self
.entryKeysAdvanced
=Entry(self
.frameKeySeqAdvanced
,
113 textvariable
=self
.keyString
)
114 self
.entryKeysAdvanced
.pack(fill
=X
)
115 labelHelpAdvanced
=Label(self
.frameHelpAdvanced
,justify
=LEFT
,
116 text
="Key bindings are specified using Tkinter keysyms as\n"+
117 "in these samples: <Control-f>, <Shift-F2>, <F12>,\n"
118 "<Control-space>, <Meta-less>, <Control-Alt-Shift-X>.\n"
119 "Upper case is used when the Shift modifier is present!\n\n" +
120 "'Emacs style' multi-keystroke bindings are specified as\n" +
121 "follows: <Control-x><Control-y>, where the first key\n" +
122 "is the 'do-nothing' keybinding.\n\n" +
123 "Multiple separate bindings for one action should be\n"+
124 "separated by a space, eg., <Alt-v> <Meta-v>." )
125 labelHelpAdvanced
.grid(row
=0,column
=0,sticky
=NSEW
)
127 def SetModifiersForPlatform(self
):
128 """Determine list of names of key modifiers for this platform.
130 The names are used to build Tk bindings -- it doesn't matter if the
131 keyboard has these keys, it matters if Tk understands them. The
132 order is also important: key binding equality depends on it, so
133 config-keys.def must use the same ordering.
136 if sys
.platform
== 'darwin' and sys
.argv
[0].count('.app'):
137 self
.modifiers
= ['Shift', 'Control', 'Option', 'Command']
139 self
.modifiers
= ['Control', 'Alt', 'Shift']
140 self
.modifier_label
= {'Control': 'Ctrl'} # short name
142 def ToggleLevel(self
):
143 if self
.buttonLevel
.cget('text')[:8]=='Advanced':
145 self
.buttonLevel
.config(text
='<< Basic Key Binding Entry')
146 self
.frameKeySeqAdvanced
.lift()
147 self
.frameHelpAdvanced
.lift()
148 self
.entryKeysAdvanced
.focus_set()
152 self
.buttonLevel
.config(text
='Advanced Key Binding Entry >>')
153 self
.frameKeySeqBasic
.lift()
154 self
.frameControlsBasic
.lift()
155 self
.advanced
= False
157 def FinalKeySelected(self
,event
):
158 self
.BuildKeyString()
160 def BuildKeyString(self
):
161 keyList
= modifiers
= self
.GetModifiers()
162 finalKey
= self
.listKeysFinal
.get(ANCHOR
)
164 finalKey
= self
.TranslateKey(finalKey
, modifiers
)
165 keyList
.append(finalKey
)
166 self
.keyString
.set('<' + string
.join(keyList
,'-') + '>')
168 def GetModifiers(self
):
169 modList
= [variable
.get() for variable
in self
.modifier_vars
]
170 return filter(None, modList
)
172 def ClearKeySeq(self
):
173 self
.listKeysFinal
.select_clear(0,END
)
174 self
.listKeysFinal
.yview(MOVETO
, '0.0')
175 for variable
in self
.modifier_vars
:
177 self
.keyString
.set('')
179 def LoadFinalKeyList(self
):
180 #these tuples are also available for use in validity checks
181 self
.functionKeys
=('F1','F2','F2','F4','F5','F6','F7','F8','F9',
183 self
.alphanumKeys
=tuple(string
.ascii_lowercase
+string
.digits
)
184 self
.punctuationKeys
=tuple('~!@#%^&*()_-+={}[]|;:,.<>/?')
185 self
.whitespaceKeys
=('Tab','Space','Return')
186 self
.editKeys
=('BackSpace','Delete','Insert')
187 self
.moveKeys
=('Home','End','Page Up','Page Down','Left Arrow',
188 'Right Arrow','Up Arrow','Down Arrow')
189 #make a tuple of most of the useful common 'final' keys
190 keys
=(self
.alphanumKeys
+self
.punctuationKeys
+self
.functionKeys
+
191 self
.whitespaceKeys
+self
.editKeys
+self
.moveKeys
)
192 self
.listKeysFinal
.insert(END
, *keys
)
194 def TranslateKey(self
, key
, modifiers
):
195 "Translate from keycap symbol to the Tkinter keysym"
196 translateDict
= {'Space':'space',
197 '~':'asciitilde','!':'exclam','@':'at','#':'numbersign',
198 '%':'percent','^':'asciicircum','&':'ampersand','*':'asterisk',
199 '(':'parenleft',')':'parenright','_':'underscore','-':'minus',
200 '+':'plus','=':'equal','{':'braceleft','}':'braceright',
201 '[':'bracketleft',']':'bracketright','|':'bar',';':'semicolon',
202 ':':'colon',',':'comma','.':'period','<':'less','>':'greater',
203 '/':'slash','?':'question','Page Up':'Prior','Page Down':'Next',
204 'Left Arrow':'Left','Right Arrow':'Right','Up Arrow':'Up',
205 'Down Arrow': 'Down', 'Tab':'Tab'}
206 if key
in translateDict
.keys():
207 key
= translateDict
[key
]
208 if 'Shift' in modifiers
and key
in string
.ascii_lowercase
:
213 def OK(self
, event
=None):
214 if self
.advanced
or self
.KeysOK(): # doesn't check advanced string yet
215 self
.result
=self
.keyString
.get()
218 def Cancel(self
, event
=None):
223 '''Validity check on user's 'basic' keybinding selection.
225 Doesn't check the string produced by the advanced dialog because
226 'modifiers' isn't set.
229 keys
= self
.keyString
.get()
231 finalKey
= self
.listKeysFinal
.get(ANCHOR
)
232 modifiers
= self
.GetModifiers()
233 # create a key sequence list for overlap check:
234 keySequence
= keys
.split()
236 title
= 'Key Sequence Error'
238 tkMessageBox
.showerror(title
=title
, parent
=self
,
239 message
='No keys specified.')
240 elif not keys
.endswith('>'):
241 tkMessageBox
.showerror(title
=title
, parent
=self
,
242 message
='Missing the final Key')
244 and finalKey
not in self
.functionKeys
+ self
.moveKeys
):
245 tkMessageBox
.showerror(title
=title
, parent
=self
,
246 message
='No modifier key(s) specified.')
247 elif (modifiers
== ['Shift']) \
249 self
.functionKeys
+ self
.moveKeys
+ ('Tab', 'Space')):
250 msg
= 'The shift modifier by itself may not be used with'\
252 tkMessageBox
.showerror(title
=title
, parent
=self
, message
=msg
)
253 elif keySequence
in self
.currentKeySequences
:
254 msg
= 'This key combination is already in use.'
255 tkMessageBox
.showerror(title
=title
, parent
=self
, message
=msg
)
260 if __name__
== '__main__':
265 dlg
=GetKeysDialog(root
,'Get Keys','find-again',[])
267 Button(root
,text
='Dialog',command
=run
).pack()