/Lib/idlelib/keybindingDialog.py

http://unladen-swallow.googlecode.com/ · Python · 268 lines · 236 code · 9 blank · 23 comment · 9 complexity · e5a62031d59e57e010b0cdec4f654a43 MD5 · raw file

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