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