configDialog.py 64 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450
  1. """IDLE Configuration Dialog: support user customization of IDLE by GUI
  2. Customize font faces, sizes, and colorization attributes. Set indentation
  3. defaults. Customize keybindings. Colorization and keybindings can be
  4. saved as user defined sets. Select startup options including shell/editor
  5. and default window size. Define additional help sources.
  6. Note that tab width in IDLE is currently fixed at eight due to Tk issues.
  7. Refer to comments in EditorWindow autoindent code for details.
  8. """
  9. from Tkinter import *
  10. import tkMessageBox, tkColorChooser, tkFont
  11. from idlelib.configHandler import idleConf
  12. from idlelib.dynOptionMenuWidget import DynOptionMenu
  13. from idlelib.keybindingDialog import GetKeysDialog
  14. from idlelib.configSectionNameDialog import GetCfgSectionNameDialog
  15. from idlelib.configHelpSourceEdit import GetHelpSourceDialog
  16. from idlelib.tabbedpages import TabbedPageSet
  17. from idlelib.textView import view_text
  18. from idlelib import macosxSupport
  19. class ConfigDialog(Toplevel):
  20. def __init__(self, parent, title='', _htest=False, _utest=False):
  21. """
  22. _htest - bool, change box location when running htest
  23. _utest - bool, don't wait_window when running unittest
  24. """
  25. Toplevel.__init__(self, parent)
  26. self.parent = parent
  27. if _htest:
  28. parent.instance_dict = {}
  29. self.wm_withdraw()
  30. self.configure(borderwidth=5)
  31. self.title(title or 'IDLE Preferences')
  32. self.geometry(
  33. "+%d+%d" % (parent.winfo_rootx() + 20,
  34. parent.winfo_rooty() + (30 if not _htest else 150)))
  35. #Theme Elements. Each theme element key is its display name.
  36. #The first value of the tuple is the sample area tag name.
  37. #The second value is the display name list sort index.
  38. self.themeElements={
  39. 'Normal Text': ('normal', '00'),
  40. 'Python Keywords': ('keyword', '01'),
  41. 'Python Definitions': ('definition', '02'),
  42. 'Python Builtins': ('builtin', '03'),
  43. 'Python Comments': ('comment', '04'),
  44. 'Python Strings': ('string', '05'),
  45. 'Selected Text': ('hilite', '06'),
  46. 'Found Text': ('hit', '07'),
  47. 'Cursor': ('cursor', '08'),
  48. 'Editor Breakpoint': ('break', '09'),
  49. 'Shell Normal Text': ('console', '10'),
  50. 'Shell Error Text': ('error', '11'),
  51. 'Shell Stdout Text': ('stdout', '12'),
  52. 'Shell Stderr Text': ('stderr', '13'),
  53. }
  54. self.ResetChangedItems() #load initial values in changed items dict
  55. self.CreateWidgets()
  56. self.resizable(height=FALSE, width=FALSE)
  57. self.transient(parent)
  58. self.grab_set()
  59. self.protocol("WM_DELETE_WINDOW", self.Cancel)
  60. self.tabPages.focus_set()
  61. #key bindings for this dialog
  62. #self.bind('<Escape>', self.Cancel) #dismiss dialog, no save
  63. #self.bind('<Alt-a>', self.Apply) #apply changes, save
  64. #self.bind('<F1>', self.Help) #context help
  65. self.LoadConfigs()
  66. self.AttachVarCallbacks() #avoid callbacks during LoadConfigs
  67. if not _utest:
  68. self.wm_deiconify()
  69. self.wait_window()
  70. def CreateWidgets(self):
  71. self.tabPages = TabbedPageSet(self,
  72. page_names=['Fonts/Tabs', 'Highlighting', 'Keys', 'General',
  73. 'Extensions'])
  74. self.tabPages.pack(side=TOP, expand=TRUE, fill=BOTH)
  75. self.CreatePageFontTab()
  76. self.CreatePageHighlight()
  77. self.CreatePageKeys()
  78. self.CreatePageGeneral()
  79. self.CreatePageExtensions()
  80. self.create_action_buttons().pack(side=BOTTOM)
  81. def create_action_buttons(self):
  82. if macosxSupport.isAquaTk():
  83. # Changing the default padding on OSX results in unreadable
  84. # text in the buttons
  85. paddingArgs = {}
  86. else:
  87. paddingArgs = {'padx':6, 'pady':3}
  88. outer = Frame(self, pady=2)
  89. buttons = Frame(outer, pady=2)
  90. for txt, cmd in (
  91. ('Ok', self.Ok),
  92. ('Apply', self.Apply),
  93. ('Cancel', self.Cancel),
  94. ('Help', self.Help)):
  95. Button(buttons, text=txt, command=cmd, takefocus=FALSE,
  96. **paddingArgs).pack(side=LEFT, padx=5)
  97. # add space above buttons
  98. Frame(outer, height=2, borderwidth=0).pack(side=TOP)
  99. buttons.pack(side=BOTTOM)
  100. return outer
  101. def CreatePageFontTab(self):
  102. parent = self.parent
  103. self.fontSize = StringVar(parent)
  104. self.fontBold = BooleanVar(parent)
  105. self.fontName = StringVar(parent)
  106. self.spaceNum = IntVar(parent)
  107. self.editFont = tkFont.Font(parent, ('courier', 10, 'normal'))
  108. ##widget creation
  109. #body frame
  110. frame = self.tabPages.pages['Fonts/Tabs'].frame
  111. #body section frames
  112. frameFont = LabelFrame(
  113. frame, borderwidth=2, relief=GROOVE, text=' Base Editor Font ')
  114. frameIndent = LabelFrame(
  115. frame, borderwidth=2, relief=GROOVE, text=' Indentation Width ')
  116. #frameFont
  117. frameFontName = Frame(frameFont)
  118. frameFontParam = Frame(frameFont)
  119. labelFontNameTitle = Label(
  120. frameFontName, justify=LEFT, text='Font Face :')
  121. self.listFontName = Listbox(
  122. frameFontName, height=5, takefocus=FALSE, exportselection=FALSE)
  123. self.listFontName.bind(
  124. '<ButtonRelease-1>', self.OnListFontButtonRelease)
  125. scrollFont = Scrollbar(frameFontName)
  126. scrollFont.config(command=self.listFontName.yview)
  127. self.listFontName.config(yscrollcommand=scrollFont.set)
  128. labelFontSizeTitle = Label(frameFontParam, text='Size :')
  129. self.optMenuFontSize = DynOptionMenu(
  130. frameFontParam, self.fontSize, None, command=self.SetFontSample)
  131. checkFontBold = Checkbutton(
  132. frameFontParam, variable=self.fontBold, onvalue=1,
  133. offvalue=0, text='Bold', command=self.SetFontSample)
  134. frameFontSample = Frame(frameFont, relief=SOLID, borderwidth=1)
  135. self.labelFontSample = Label(
  136. frameFontSample, justify=LEFT, font=self.editFont,
  137. text='AaBbCcDdEe\nFfGgHhIiJjK\n1234567890\n#:+=(){}[]')
  138. #frameIndent
  139. frameIndentSize = Frame(frameIndent)
  140. labelSpaceNumTitle = Label(
  141. frameIndentSize, justify=LEFT,
  142. text='Python Standard: 4 Spaces!')
  143. self.scaleSpaceNum = Scale(
  144. frameIndentSize, variable=self.spaceNum,
  145. orient='horizontal', tickinterval=2, from_=2, to=16)
  146. #widget packing
  147. #body
  148. frameFont.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
  149. frameIndent.pack(side=LEFT, padx=5, pady=5, fill=Y)
  150. #frameFont
  151. frameFontName.pack(side=TOP, padx=5, pady=5, fill=X)
  152. frameFontParam.pack(side=TOP, padx=5, pady=5, fill=X)
  153. labelFontNameTitle.pack(side=TOP, anchor=W)
  154. self.listFontName.pack(side=LEFT, expand=TRUE, fill=X)
  155. scrollFont.pack(side=LEFT, fill=Y)
  156. labelFontSizeTitle.pack(side=LEFT, anchor=W)
  157. self.optMenuFontSize.pack(side=LEFT, anchor=W)
  158. checkFontBold.pack(side=LEFT, anchor=W, padx=20)
  159. frameFontSample.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
  160. self.labelFontSample.pack(expand=TRUE, fill=BOTH)
  161. #frameIndent
  162. frameIndentSize.pack(side=TOP, fill=X)
  163. labelSpaceNumTitle.pack(side=TOP, anchor=W, padx=5)
  164. self.scaleSpaceNum.pack(side=TOP, padx=5, fill=X)
  165. return frame
  166. def CreatePageHighlight(self):
  167. parent = self.parent
  168. self.builtinTheme = StringVar(parent)
  169. self.customTheme = StringVar(parent)
  170. self.fgHilite = BooleanVar(parent)
  171. self.colour = StringVar(parent)
  172. self.fontName = StringVar(parent)
  173. self.themeIsBuiltin = BooleanVar(parent)
  174. self.highlightTarget = StringVar(parent)
  175. ##widget creation
  176. #body frame
  177. frame = self.tabPages.pages['Highlighting'].frame
  178. #body section frames
  179. frameCustom = LabelFrame(frame, borderwidth=2, relief=GROOVE,
  180. text=' Custom Highlighting ')
  181. frameTheme = LabelFrame(frame, borderwidth=2, relief=GROOVE,
  182. text=' Highlighting Theme ')
  183. #frameCustom
  184. self.textHighlightSample=Text(
  185. frameCustom, relief=SOLID, borderwidth=1,
  186. font=('courier', 12, ''), cursor='hand2', width=21, height=11,
  187. takefocus=FALSE, highlightthickness=0, wrap=NONE)
  188. text=self.textHighlightSample
  189. text.bind('<Double-Button-1>', lambda e: 'break')
  190. text.bind('<B1-Motion>', lambda e: 'break')
  191. textAndTags=(
  192. ('#you can click here', 'comment'), ('\n', 'normal'),
  193. ('#to choose items', 'comment'), ('\n', 'normal'),
  194. ('def', 'keyword'), (' ', 'normal'),
  195. ('func', 'definition'), ('(param):\n ', 'normal'),
  196. ('"""string"""', 'string'), ('\n var0 = ', 'normal'),
  197. ("'string'", 'string'), ('\n var1 = ', 'normal'),
  198. ("'selected'", 'hilite'), ('\n var2 = ', 'normal'),
  199. ("'found'", 'hit'), ('\n var3 = ', 'normal'),
  200. ('list', 'builtin'), ('(', 'normal'),
  201. ('None', 'builtin'), (')\n', 'normal'),
  202. (' breakpoint("line")', 'break'), ('\n\n', 'normal'),
  203. (' error ', 'error'), (' ', 'normal'),
  204. ('cursor |', 'cursor'), ('\n ', 'normal'),
  205. ('shell', 'console'), (' ', 'normal'),
  206. ('stdout', 'stdout'), (' ', 'normal'),
  207. ('stderr', 'stderr'), ('\n', 'normal'))
  208. for txTa in textAndTags:
  209. text.insert(END, txTa[0], txTa[1])
  210. for element in self.themeElements:
  211. def tem(event, elem=element):
  212. event.widget.winfo_toplevel().highlightTarget.set(elem)
  213. text.tag_bind(
  214. self.themeElements[element][0], '<ButtonPress-1>', tem)
  215. text.config(state=DISABLED)
  216. self.frameColourSet = Frame(frameCustom, relief=SOLID, borderwidth=1)
  217. frameFgBg = Frame(frameCustom)
  218. buttonSetColour = Button(
  219. self.frameColourSet, text='Choose Colour for :',
  220. command=self.GetColour, highlightthickness=0)
  221. self.optMenuHighlightTarget = DynOptionMenu(
  222. self.frameColourSet, self.highlightTarget, None,
  223. highlightthickness=0) #, command=self.SetHighlightTargetBinding
  224. self.radioFg = Radiobutton(
  225. frameFgBg, variable=self.fgHilite, value=1,
  226. text='Foreground', command=self.SetColourSampleBinding)
  227. self.radioBg=Radiobutton(
  228. frameFgBg, variable=self.fgHilite, value=0,
  229. text='Background', command=self.SetColourSampleBinding)
  230. self.fgHilite.set(1)
  231. buttonSaveCustomTheme = Button(
  232. frameCustom, text='Save as New Custom Theme',
  233. command=self.SaveAsNewTheme)
  234. #frameTheme
  235. labelTypeTitle = Label(frameTheme, text='Select : ')
  236. self.radioThemeBuiltin = Radiobutton(
  237. frameTheme, variable=self.themeIsBuiltin, value=1,
  238. command=self.SetThemeType, text='a Built-in Theme')
  239. self.radioThemeCustom = Radiobutton(
  240. frameTheme, variable=self.themeIsBuiltin, value=0,
  241. command=self.SetThemeType, text='a Custom Theme')
  242. self.optMenuThemeBuiltin = DynOptionMenu(
  243. frameTheme, self.builtinTheme, None, command=None)
  244. self.optMenuThemeCustom=DynOptionMenu(
  245. frameTheme, self.customTheme, None, command=None)
  246. self.buttonDeleteCustomTheme=Button(
  247. frameTheme, text='Delete Custom Theme',
  248. command=self.DeleteCustomTheme)
  249. self.new_custom_theme = Label(frameTheme, bd=2)
  250. ##widget packing
  251. #body
  252. frameCustom.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
  253. frameTheme.pack(side=LEFT, padx=5, pady=5, fill=Y)
  254. #frameCustom
  255. self.frameColourSet.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=X)
  256. frameFgBg.pack(side=TOP, padx=5, pady=0)
  257. self.textHighlightSample.pack(
  258. side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
  259. buttonSetColour.pack(side=TOP, expand=TRUE, fill=X, padx=8, pady=4)
  260. self.optMenuHighlightTarget.pack(
  261. side=TOP, expand=TRUE, fill=X, padx=8, pady=3)
  262. self.radioFg.pack(side=LEFT, anchor=E)
  263. self.radioBg.pack(side=RIGHT, anchor=W)
  264. buttonSaveCustomTheme.pack(side=BOTTOM, fill=X, padx=5, pady=5)
  265. #frameTheme
  266. labelTypeTitle.pack(side=TOP, anchor=W, padx=5, pady=5)
  267. self.radioThemeBuiltin.pack(side=TOP, anchor=W, padx=5)
  268. self.radioThemeCustom.pack(side=TOP, anchor=W, padx=5, pady=2)
  269. self.optMenuThemeBuiltin.pack(side=TOP, fill=X, padx=5, pady=5)
  270. self.optMenuThemeCustom.pack(side=TOP, fill=X, anchor=W, padx=5, pady=5)
  271. self.buttonDeleteCustomTheme.pack(side=TOP, fill=X, padx=5, pady=5)
  272. self.new_custom_theme.pack(side=TOP, fill=X, pady=5)
  273. return frame
  274. def CreatePageKeys(self):
  275. parent = self.parent
  276. self.bindingTarget = StringVar(parent)
  277. self.builtinKeys = StringVar(parent)
  278. self.customKeys = StringVar(parent)
  279. self.keysAreBuiltin = BooleanVar(parent)
  280. self.keyBinding = StringVar(parent)
  281. ##widget creation
  282. #body frame
  283. frame = self.tabPages.pages['Keys'].frame
  284. #body section frames
  285. frameCustom = LabelFrame(
  286. frame, borderwidth=2, relief=GROOVE,
  287. text=' Custom Key Bindings ')
  288. frameKeySets = LabelFrame(
  289. frame, borderwidth=2, relief=GROOVE, text=' Key Set ')
  290. #frameCustom
  291. frameTarget = Frame(frameCustom)
  292. labelTargetTitle = Label(frameTarget, text='Action - Key(s)')
  293. scrollTargetY = Scrollbar(frameTarget)
  294. scrollTargetX = Scrollbar(frameTarget, orient=HORIZONTAL)
  295. self.listBindings = Listbox(
  296. frameTarget, takefocus=FALSE, exportselection=FALSE)
  297. self.listBindings.bind('<ButtonRelease-1>', self.KeyBindingSelected)
  298. scrollTargetY.config(command=self.listBindings.yview)
  299. scrollTargetX.config(command=self.listBindings.xview)
  300. self.listBindings.config(yscrollcommand=scrollTargetY.set)
  301. self.listBindings.config(xscrollcommand=scrollTargetX.set)
  302. self.buttonNewKeys = Button(
  303. frameCustom, text='Get New Keys for Selection',
  304. command=self.GetNewKeys, state=DISABLED)
  305. #frameKeySets
  306. frames = [Frame(frameKeySets, padx=2, pady=2, borderwidth=0)
  307. for i in range(2)]
  308. self.radioKeysBuiltin = Radiobutton(
  309. frames[0], variable=self.keysAreBuiltin, value=1,
  310. command=self.SetKeysType, text='Use a Built-in Key Set')
  311. self.radioKeysCustom = Radiobutton(
  312. frames[0], variable=self.keysAreBuiltin, value=0,
  313. command=self.SetKeysType, text='Use a Custom Key Set')
  314. self.optMenuKeysBuiltin = DynOptionMenu(
  315. frames[0], self.builtinKeys, None, command=None)
  316. self.optMenuKeysCustom = DynOptionMenu(
  317. frames[0], self.customKeys, None, command=None)
  318. self.buttonDeleteCustomKeys = Button(
  319. frames[1], text='Delete Custom Key Set',
  320. command=self.DeleteCustomKeys)
  321. buttonSaveCustomKeys = Button(
  322. frames[1], text='Save as New Custom Key Set',
  323. command=self.SaveAsNewKeySet)
  324. ##widget packing
  325. #body
  326. frameCustom.pack(side=BOTTOM, padx=5, pady=5, expand=TRUE, fill=BOTH)
  327. frameKeySets.pack(side=BOTTOM, padx=5, pady=5, fill=BOTH)
  328. #frameCustom
  329. self.buttonNewKeys.pack(side=BOTTOM, fill=X, padx=5, pady=5)
  330. frameTarget.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
  331. #frame target
  332. frameTarget.columnconfigure(0, weight=1)
  333. frameTarget.rowconfigure(1, weight=1)
  334. labelTargetTitle.grid(row=0, column=0, columnspan=2, sticky=W)
  335. self.listBindings.grid(row=1, column=0, sticky=NSEW)
  336. scrollTargetY.grid(row=1, column=1, sticky=NS)
  337. scrollTargetX.grid(row=2, column=0, sticky=EW)
  338. #frameKeySets
  339. self.radioKeysBuiltin.grid(row=0, column=0, sticky=W+NS)
  340. self.radioKeysCustom.grid(row=1, column=0, sticky=W+NS)
  341. self.optMenuKeysBuiltin.grid(row=0, column=1, sticky=NSEW)
  342. self.optMenuKeysCustom.grid(row=1, column=1, sticky=NSEW)
  343. self.buttonDeleteCustomKeys.pack(side=LEFT, fill=X, expand=True, padx=2)
  344. buttonSaveCustomKeys.pack(side=LEFT, fill=X, expand=True, padx=2)
  345. frames[0].pack(side=TOP, fill=BOTH, expand=True)
  346. frames[1].pack(side=TOP, fill=X, expand=True, pady=2)
  347. return frame
  348. def CreatePageGeneral(self):
  349. parent = self.parent
  350. self.winWidth = StringVar(parent)
  351. self.winHeight = StringVar(parent)
  352. self.startupEdit = IntVar(parent)
  353. self.autoSave = IntVar(parent)
  354. self.encoding = StringVar(parent)
  355. self.userHelpBrowser = BooleanVar(parent)
  356. self.helpBrowser = StringVar(parent)
  357. #widget creation
  358. #body
  359. frame = self.tabPages.pages['General'].frame
  360. #body section frames
  361. frameRun = LabelFrame(frame, borderwidth=2, relief=GROOVE,
  362. text=' Startup Preferences ')
  363. frameSave = LabelFrame(frame, borderwidth=2, relief=GROOVE,
  364. text=' Autosave Preferences ')
  365. frameWinSize = Frame(frame, borderwidth=2, relief=GROOVE)
  366. frameEncoding = Frame(frame, borderwidth=2, relief=GROOVE)
  367. frameHelp = LabelFrame(frame, borderwidth=2, relief=GROOVE,
  368. text=' Additional Help Sources ')
  369. #frameRun
  370. labelRunChoiceTitle = Label(frameRun, text='At Startup')
  371. radioStartupEdit = Radiobutton(
  372. frameRun, variable=self.startupEdit, value=1,
  373. command=self.SetKeysType, text="Open Edit Window")
  374. radioStartupShell = Radiobutton(
  375. frameRun, variable=self.startupEdit, value=0,
  376. command=self.SetKeysType, text='Open Shell Window')
  377. #frameSave
  378. labelRunSaveTitle = Label(frameSave, text='At Start of Run (F5) ')
  379. radioSaveAsk = Radiobutton(
  380. frameSave, variable=self.autoSave, value=0,
  381. command=self.SetKeysType, text="Prompt to Save")
  382. radioSaveAuto = Radiobutton(
  383. frameSave, variable=self.autoSave, value=1,
  384. command=self.SetKeysType, text='No Prompt')
  385. #frameWinSize
  386. labelWinSizeTitle = Label(
  387. frameWinSize, text='Initial Window Size (in characters)')
  388. labelWinWidthTitle = Label(frameWinSize, text='Width')
  389. entryWinWidth = Entry(
  390. frameWinSize, textvariable=self.winWidth, width=3)
  391. labelWinHeightTitle = Label(frameWinSize, text='Height')
  392. entryWinHeight = Entry(
  393. frameWinSize, textvariable=self.winHeight, width=3)
  394. #frameEncoding
  395. labelEncodingTitle = Label(
  396. frameEncoding, text="Default Source Encoding")
  397. radioEncLocale = Radiobutton(
  398. frameEncoding, variable=self.encoding,
  399. value="locale", text="Locale-defined")
  400. radioEncUTF8 = Radiobutton(
  401. frameEncoding, variable=self.encoding,
  402. value="utf-8", text="UTF-8")
  403. radioEncNone = Radiobutton(
  404. frameEncoding, variable=self.encoding,
  405. value="none", text="None")
  406. #frameHelp
  407. frameHelpList = Frame(frameHelp)
  408. frameHelpListButtons = Frame(frameHelpList)
  409. scrollHelpList = Scrollbar(frameHelpList)
  410. self.listHelp = Listbox(
  411. frameHelpList, height=5, takefocus=FALSE,
  412. exportselection=FALSE)
  413. scrollHelpList.config(command=self.listHelp.yview)
  414. self.listHelp.config(yscrollcommand=scrollHelpList.set)
  415. self.listHelp.bind('<ButtonRelease-1>', self.HelpSourceSelected)
  416. self.buttonHelpListEdit = Button(
  417. frameHelpListButtons, text='Edit', state=DISABLED,
  418. width=8, command=self.HelpListItemEdit)
  419. self.buttonHelpListAdd = Button(
  420. frameHelpListButtons, text='Add',
  421. width=8, command=self.HelpListItemAdd)
  422. self.buttonHelpListRemove = Button(
  423. frameHelpListButtons, text='Remove', state=DISABLED,
  424. width=8, command=self.HelpListItemRemove)
  425. #widget packing
  426. #body
  427. frameRun.pack(side=TOP, padx=5, pady=5, fill=X)
  428. frameSave.pack(side=TOP, padx=5, pady=5, fill=X)
  429. frameWinSize.pack(side=TOP, padx=5, pady=5, fill=X)
  430. frameEncoding.pack(side=TOP, padx=5, pady=5, fill=X)
  431. frameHelp.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
  432. #frameRun
  433. labelRunChoiceTitle.pack(side=LEFT, anchor=W, padx=5, pady=5)
  434. radioStartupShell.pack(side=RIGHT, anchor=W, padx=5, pady=5)
  435. radioStartupEdit.pack(side=RIGHT, anchor=W, padx=5, pady=5)
  436. #frameSave
  437. labelRunSaveTitle.pack(side=LEFT, anchor=W, padx=5, pady=5)
  438. radioSaveAuto.pack(side=RIGHT, anchor=W, padx=5, pady=5)
  439. radioSaveAsk.pack(side=RIGHT, anchor=W, padx=5, pady=5)
  440. #frameWinSize
  441. labelWinSizeTitle.pack(side=LEFT, anchor=W, padx=5, pady=5)
  442. entryWinHeight.pack(side=RIGHT, anchor=E, padx=10, pady=5)
  443. labelWinHeightTitle.pack(side=RIGHT, anchor=E, pady=5)
  444. entryWinWidth.pack(side=RIGHT, anchor=E, padx=10, pady=5)
  445. labelWinWidthTitle.pack(side=RIGHT, anchor=E, pady=5)
  446. #frameEncoding
  447. labelEncodingTitle.pack(side=LEFT, anchor=W, padx=5, pady=5)
  448. radioEncNone.pack(side=RIGHT, anchor=E, pady=5)
  449. radioEncUTF8.pack(side=RIGHT, anchor=E, pady=5)
  450. radioEncLocale.pack(side=RIGHT, anchor=E, pady=5)
  451. #frameHelp
  452. frameHelpListButtons.pack(side=RIGHT, padx=5, pady=5, fill=Y)
  453. frameHelpList.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
  454. scrollHelpList.pack(side=RIGHT, anchor=W, fill=Y)
  455. self.listHelp.pack(side=LEFT, anchor=E, expand=TRUE, fill=BOTH)
  456. self.buttonHelpListEdit.pack(side=TOP, anchor=W, pady=5)
  457. self.buttonHelpListAdd.pack(side=TOP, anchor=W)
  458. self.buttonHelpListRemove.pack(side=TOP, anchor=W, pady=5)
  459. return frame
  460. def AttachVarCallbacks(self):
  461. self.fontSize.trace_variable('w', self.VarChanged_font)
  462. self.fontName.trace_variable('w', self.VarChanged_font)
  463. self.fontBold.trace_variable('w', self.VarChanged_font)
  464. self.spaceNum.trace_variable('w', self.VarChanged_spaceNum)
  465. self.colour.trace_variable('w', self.VarChanged_colour)
  466. self.builtinTheme.trace_variable('w', self.VarChanged_builtinTheme)
  467. self.customTheme.trace_variable('w', self.VarChanged_customTheme)
  468. self.themeIsBuiltin.trace_variable('w', self.VarChanged_themeIsBuiltin)
  469. self.highlightTarget.trace_variable('w', self.VarChanged_highlightTarget)
  470. self.keyBinding.trace_variable('w', self.VarChanged_keyBinding)
  471. self.builtinKeys.trace_variable('w', self.VarChanged_builtinKeys)
  472. self.customKeys.trace_variable('w', self.VarChanged_customKeys)
  473. self.keysAreBuiltin.trace_variable('w', self.VarChanged_keysAreBuiltin)
  474. self.winWidth.trace_variable('w', self.VarChanged_winWidth)
  475. self.winHeight.trace_variable('w', self.VarChanged_winHeight)
  476. self.startupEdit.trace_variable('w', self.VarChanged_startupEdit)
  477. self.autoSave.trace_variable('w', self.VarChanged_autoSave)
  478. self.encoding.trace_variable('w', self.VarChanged_encoding)
  479. def remove_var_callbacks(self):
  480. for var in (
  481. self.fontSize, self.fontName, self.fontBold,
  482. self.spaceNum, self.colour, self.builtinTheme,
  483. self.customTheme, self.themeIsBuiltin, self.highlightTarget,
  484. self.keyBinding, self.builtinKeys, self.customKeys,
  485. self.keysAreBuiltin, self.winWidth, self.winHeight,
  486. self.startupEdit, self.autoSave, self.encoding,):
  487. var.trace_vdelete('w', var.trace_vinfo()[0][1])
  488. def VarChanged_font(self, *params):
  489. '''When one font attribute changes, save them all, as they are
  490. not independent from each other. In particular, when we are
  491. overriding the default font, we need to write out everything.
  492. '''
  493. value = self.fontName.get()
  494. self.AddChangedItem('main', 'EditorWindow', 'font', value)
  495. value = self.fontSize.get()
  496. self.AddChangedItem('main', 'EditorWindow', 'font-size', value)
  497. value = self.fontBold.get()
  498. self.AddChangedItem('main', 'EditorWindow', 'font-bold', value)
  499. def VarChanged_spaceNum(self, *params):
  500. value = self.spaceNum.get()
  501. self.AddChangedItem('main', 'Indent', 'num-spaces', value)
  502. def VarChanged_colour(self, *params):
  503. self.OnNewColourSet()
  504. def VarChanged_builtinTheme(self, *params):
  505. value = self.builtinTheme.get()
  506. if value == 'IDLE Dark':
  507. if idleConf.GetOption('main', 'Theme', 'name') != 'IDLE New':
  508. self.AddChangedItem('main', 'Theme', 'name', 'IDLE Classic')
  509. self.AddChangedItem('main', 'Theme', 'name2', value)
  510. self.new_custom_theme.config(text='New theme, see Help',
  511. fg='#500000')
  512. else:
  513. self.AddChangedItem('main', 'Theme', 'name', value)
  514. self.AddChangedItem('main', 'Theme', 'name2', '')
  515. self.new_custom_theme.config(text='', fg='black')
  516. self.PaintThemeSample()
  517. def VarChanged_customTheme(self, *params):
  518. value = self.customTheme.get()
  519. if value != '- no custom themes -':
  520. self.AddChangedItem('main', 'Theme', 'name', value)
  521. self.PaintThemeSample()
  522. def VarChanged_themeIsBuiltin(self, *params):
  523. value = self.themeIsBuiltin.get()
  524. self.AddChangedItem('main', 'Theme', 'default', value)
  525. if value:
  526. self.VarChanged_builtinTheme()
  527. else:
  528. self.VarChanged_customTheme()
  529. def VarChanged_highlightTarget(self, *params):
  530. self.SetHighlightTarget()
  531. def VarChanged_keyBinding(self, *params):
  532. value = self.keyBinding.get()
  533. keySet = self.customKeys.get()
  534. event = self.listBindings.get(ANCHOR).split()[0]
  535. if idleConf.IsCoreBinding(event):
  536. #this is a core keybinding
  537. self.AddChangedItem('keys', keySet, event, value)
  538. else: #this is an extension key binding
  539. extName = idleConf.GetExtnNameForEvent(event)
  540. extKeybindSection = extName + '_cfgBindings'
  541. self.AddChangedItem('extensions', extKeybindSection, event, value)
  542. def VarChanged_builtinKeys(self, *params):
  543. value = self.builtinKeys.get()
  544. self.AddChangedItem('main', 'Keys', 'name', value)
  545. self.LoadKeysList(value)
  546. def VarChanged_customKeys(self, *params):
  547. value = self.customKeys.get()
  548. if value != '- no custom keys -':
  549. self.AddChangedItem('main', 'Keys', 'name', value)
  550. self.LoadKeysList(value)
  551. def VarChanged_keysAreBuiltin(self, *params):
  552. value = self.keysAreBuiltin.get()
  553. self.AddChangedItem('main', 'Keys', 'default', value)
  554. if value:
  555. self.VarChanged_builtinKeys()
  556. else:
  557. self.VarChanged_customKeys()
  558. def VarChanged_winWidth(self, *params):
  559. value = self.winWidth.get()
  560. self.AddChangedItem('main', 'EditorWindow', 'width', value)
  561. def VarChanged_winHeight(self, *params):
  562. value = self.winHeight.get()
  563. self.AddChangedItem('main', 'EditorWindow', 'height', value)
  564. def VarChanged_startupEdit(self, *params):
  565. value = self.startupEdit.get()
  566. self.AddChangedItem('main', 'General', 'editor-on-startup', value)
  567. def VarChanged_autoSave(self, *params):
  568. value = self.autoSave.get()
  569. self.AddChangedItem('main', 'General', 'autosave', value)
  570. def VarChanged_encoding(self, *params):
  571. value = self.encoding.get()
  572. self.AddChangedItem('main', 'EditorWindow', 'encoding', value)
  573. def ResetChangedItems(self):
  574. #When any config item is changed in this dialog, an entry
  575. #should be made in the relevant section (config type) of this
  576. #dictionary. The key should be the config file section name and the
  577. #value a dictionary, whose key:value pairs are item=value pairs for
  578. #that config file section.
  579. self.changedItems = {'main':{}, 'highlight':{}, 'keys':{},
  580. 'extensions':{}}
  581. def AddChangedItem(self, typ, section, item, value):
  582. value = str(value) #make sure we use a string
  583. if section not in self.changedItems[typ]:
  584. self.changedItems[typ][section] = {}
  585. self.changedItems[typ][section][item] = value
  586. def GetDefaultItems(self):
  587. dItems={'main':{}, 'highlight':{}, 'keys':{}, 'extensions':{}}
  588. for configType in dItems:
  589. sections = idleConf.GetSectionList('default', configType)
  590. for section in sections:
  591. dItems[configType][section] = {}
  592. options = idleConf.defaultCfg[configType].GetOptionList(section)
  593. for option in options:
  594. dItems[configType][section][option] = (
  595. idleConf.defaultCfg[configType].Get(section, option))
  596. return dItems
  597. def SetThemeType(self):
  598. if self.themeIsBuiltin.get():
  599. self.optMenuThemeBuiltin.config(state=NORMAL)
  600. self.optMenuThemeCustom.config(state=DISABLED)
  601. self.buttonDeleteCustomTheme.config(state=DISABLED)
  602. else:
  603. self.optMenuThemeBuiltin.config(state=DISABLED)
  604. self.radioThemeCustom.config(state=NORMAL)
  605. self.optMenuThemeCustom.config(state=NORMAL)
  606. self.buttonDeleteCustomTheme.config(state=NORMAL)
  607. def SetKeysType(self):
  608. if self.keysAreBuiltin.get():
  609. self.optMenuKeysBuiltin.config(state=NORMAL)
  610. self.optMenuKeysCustom.config(state=DISABLED)
  611. self.buttonDeleteCustomKeys.config(state=DISABLED)
  612. else:
  613. self.optMenuKeysBuiltin.config(state=DISABLED)
  614. self.radioKeysCustom.config(state=NORMAL)
  615. self.optMenuKeysCustom.config(state=NORMAL)
  616. self.buttonDeleteCustomKeys.config(state=NORMAL)
  617. def GetNewKeys(self):
  618. listIndex = self.listBindings.index(ANCHOR)
  619. binding = self.listBindings.get(listIndex)
  620. bindName = binding.split()[0] #first part, up to first space
  621. if self.keysAreBuiltin.get():
  622. currentKeySetName = self.builtinKeys.get()
  623. else:
  624. currentKeySetName = self.customKeys.get()
  625. currentBindings = idleConf.GetCurrentKeySet()
  626. if currentKeySetName in self.changedItems['keys']: #unsaved changes
  627. keySetChanges = self.changedItems['keys'][currentKeySetName]
  628. for event in keySetChanges:
  629. currentBindings[event] = keySetChanges[event].split()
  630. currentKeySequences = currentBindings.values()
  631. newKeys = GetKeysDialog(self, 'Get New Keys', bindName,
  632. currentKeySequences).result
  633. if newKeys: #new keys were specified
  634. if self.keysAreBuiltin.get(): #current key set is a built-in
  635. message = ('Your changes will be saved as a new Custom Key Set.'
  636. ' Enter a name for your new Custom Key Set below.')
  637. newKeySet = self.GetNewKeysName(message)
  638. if not newKeySet: #user cancelled custom key set creation
  639. self.listBindings.select_set(listIndex)
  640. self.listBindings.select_anchor(listIndex)
  641. return
  642. else: #create new custom key set based on previously active key set
  643. self.CreateNewKeySet(newKeySet)
  644. self.listBindings.delete(listIndex)
  645. self.listBindings.insert(listIndex, bindName+' - '+newKeys)
  646. self.listBindings.select_set(listIndex)
  647. self.listBindings.select_anchor(listIndex)
  648. self.keyBinding.set(newKeys)
  649. else:
  650. self.listBindings.select_set(listIndex)
  651. self.listBindings.select_anchor(listIndex)
  652. def GetNewKeysName(self, message):
  653. usedNames = (idleConf.GetSectionList('user', 'keys') +
  654. idleConf.GetSectionList('default', 'keys'))
  655. newKeySet = GetCfgSectionNameDialog(
  656. self, 'New Custom Key Set', message, usedNames).result
  657. return newKeySet
  658. def SaveAsNewKeySet(self):
  659. newKeysName = self.GetNewKeysName('New Key Set Name:')
  660. if newKeysName:
  661. self.CreateNewKeySet(newKeysName)
  662. def KeyBindingSelected(self, event):
  663. self.buttonNewKeys.config(state=NORMAL)
  664. def CreateNewKeySet(self, newKeySetName):
  665. #creates new custom key set based on the previously active key set,
  666. #and makes the new key set active
  667. if self.keysAreBuiltin.get():
  668. prevKeySetName = self.builtinKeys.get()
  669. else:
  670. prevKeySetName = self.customKeys.get()
  671. prevKeys = idleConf.GetCoreKeys(prevKeySetName)
  672. newKeys = {}
  673. for event in prevKeys: #add key set to changed items
  674. eventName = event[2:-2] #trim off the angle brackets
  675. binding = ' '.join(prevKeys[event])
  676. newKeys[eventName] = binding
  677. #handle any unsaved changes to prev key set
  678. if prevKeySetName in self.changedItems['keys']:
  679. keySetChanges = self.changedItems['keys'][prevKeySetName]
  680. for event in keySetChanges:
  681. newKeys[event] = keySetChanges[event]
  682. #save the new theme
  683. self.SaveNewKeySet(newKeySetName, newKeys)
  684. #change gui over to the new key set
  685. customKeyList = idleConf.GetSectionList('user', 'keys')
  686. customKeyList.sort()
  687. self.optMenuKeysCustom.SetMenu(customKeyList, newKeySetName)
  688. self.keysAreBuiltin.set(0)
  689. self.SetKeysType()
  690. def LoadKeysList(self, keySetName):
  691. reselect = 0
  692. newKeySet = 0
  693. if self.listBindings.curselection():
  694. reselect = 1
  695. listIndex = self.listBindings.index(ANCHOR)
  696. keySet = idleConf.GetKeySet(keySetName)
  697. bindNames = keySet.keys()
  698. bindNames.sort()
  699. self.listBindings.delete(0, END)
  700. for bindName in bindNames:
  701. key = ' '.join(keySet[bindName]) #make key(s) into a string
  702. bindName = bindName[2:-2] #trim off the angle brackets
  703. if keySetName in self.changedItems['keys']:
  704. #handle any unsaved changes to this key set
  705. if bindName in self.changedItems['keys'][keySetName]:
  706. key = self.changedItems['keys'][keySetName][bindName]
  707. self.listBindings.insert(END, bindName+' - '+key)
  708. if reselect:
  709. self.listBindings.see(listIndex)
  710. self.listBindings.select_set(listIndex)
  711. self.listBindings.select_anchor(listIndex)
  712. def DeleteCustomKeys(self):
  713. keySetName=self.customKeys.get()
  714. delmsg = 'Are you sure you wish to delete the key set %r ?'
  715. if not tkMessageBox.askyesno(
  716. 'Delete Key Set', delmsg % keySetName, parent=self):
  717. return
  718. #remove key set from config
  719. idleConf.userCfg['keys'].remove_section(keySetName)
  720. if keySetName in self.changedItems['keys']:
  721. del(self.changedItems['keys'][keySetName])
  722. #write changes
  723. idleConf.userCfg['keys'].Save()
  724. #reload user key set list
  725. itemList = idleConf.GetSectionList('user', 'keys')
  726. itemList.sort()
  727. if not itemList:
  728. self.radioKeysCustom.config(state=DISABLED)
  729. self.optMenuKeysCustom.SetMenu(itemList, '- no custom keys -')
  730. else:
  731. self.optMenuKeysCustom.SetMenu(itemList, itemList[0])
  732. #revert to default key set
  733. self.keysAreBuiltin.set(idleConf.defaultCfg['main'].Get('Keys', 'default'))
  734. self.builtinKeys.set(idleConf.defaultCfg['main'].Get('Keys', 'name'))
  735. #user can't back out of these changes, they must be applied now
  736. self.Apply()
  737. self.SetKeysType()
  738. def DeleteCustomTheme(self):
  739. themeName = self.customTheme.get()
  740. delmsg = 'Are you sure you wish to delete the theme %r ?'
  741. if not tkMessageBox.askyesno(
  742. 'Delete Theme', delmsg % themeName, parent=self):
  743. return
  744. #remove theme from config
  745. idleConf.userCfg['highlight'].remove_section(themeName)
  746. if themeName in self.changedItems['highlight']:
  747. del(self.changedItems['highlight'][themeName])
  748. #write changes
  749. idleConf.userCfg['highlight'].Save()
  750. #reload user theme list
  751. itemList = idleConf.GetSectionList('user', 'highlight')
  752. itemList.sort()
  753. if not itemList:
  754. self.radioThemeCustom.config(state=DISABLED)
  755. self.optMenuThemeCustom.SetMenu(itemList, '- no custom themes -')
  756. else:
  757. self.optMenuThemeCustom.SetMenu(itemList, itemList[0])
  758. #revert to default theme
  759. self.themeIsBuiltin.set(idleConf.defaultCfg['main'].Get('Theme', 'default'))
  760. self.builtinTheme.set(idleConf.defaultCfg['main'].Get('Theme', 'name'))
  761. #user can't back out of these changes, they must be applied now
  762. self.Apply()
  763. self.SetThemeType()
  764. def GetColour(self):
  765. target = self.highlightTarget.get()
  766. prevColour = self.frameColourSet.cget('bg')
  767. rgbTuplet, colourString = tkColorChooser.askcolor(
  768. parent=self, title='Pick new colour for : '+target,
  769. initialcolor=prevColour)
  770. if colourString and (colourString != prevColour):
  771. #user didn't cancel, and they chose a new colour
  772. if self.themeIsBuiltin.get(): #current theme is a built-in
  773. message = ('Your changes will be saved as a new Custom Theme. '
  774. 'Enter a name for your new Custom Theme below.')
  775. newTheme = self.GetNewThemeName(message)
  776. if not newTheme: #user cancelled custom theme creation
  777. return
  778. else: #create new custom theme based on previously active theme
  779. self.CreateNewTheme(newTheme)
  780. self.colour.set(colourString)
  781. else: #current theme is user defined
  782. self.colour.set(colourString)
  783. def OnNewColourSet(self):
  784. newColour=self.colour.get()
  785. self.frameColourSet.config(bg=newColour) #set sample
  786. plane ='foreground' if self.fgHilite.get() else 'background'
  787. sampleElement = self.themeElements[self.highlightTarget.get()][0]
  788. self.textHighlightSample.tag_config(sampleElement, **{plane:newColour})
  789. theme = self.customTheme.get()
  790. themeElement = sampleElement + '-' + plane
  791. self.AddChangedItem('highlight', theme, themeElement, newColour)
  792. def GetNewThemeName(self, message):
  793. usedNames = (idleConf.GetSectionList('user', 'highlight') +
  794. idleConf.GetSectionList('default', 'highlight'))
  795. newTheme = GetCfgSectionNameDialog(
  796. self, 'New Custom Theme', message, usedNames).result
  797. return newTheme
  798. def SaveAsNewTheme(self):
  799. newThemeName = self.GetNewThemeName('New Theme Name:')
  800. if newThemeName:
  801. self.CreateNewTheme(newThemeName)
  802. def CreateNewTheme(self, newThemeName):
  803. #creates new custom theme based on the previously active theme,
  804. #and makes the new theme active
  805. if self.themeIsBuiltin.get():
  806. themeType = 'default'
  807. themeName = self.builtinTheme.get()
  808. else:
  809. themeType = 'user'
  810. themeName = self.customTheme.get()
  811. newTheme = idleConf.GetThemeDict(themeType, themeName)
  812. #apply any of the old theme's unsaved changes to the new theme
  813. if themeName in self.changedItems['highlight']:
  814. themeChanges = self.changedItems['highlight'][themeName]
  815. for element in themeChanges:
  816. newTheme[element] = themeChanges[element]
  817. #save the new theme
  818. self.SaveNewTheme(newThemeName, newTheme)
  819. #change gui over to the new theme
  820. customThemeList = idleConf.GetSectionList('user', 'highlight')
  821. customThemeList.sort()
  822. self.optMenuThemeCustom.SetMenu(customThemeList, newThemeName)
  823. self.themeIsBuiltin.set(0)
  824. self.SetThemeType()
  825. def OnListFontButtonRelease(self, event):
  826. font = self.listFontName.get(ANCHOR)
  827. self.fontName.set(font.lower())
  828. self.SetFontSample()
  829. def SetFontSample(self, event=None):
  830. fontName = self.fontName.get()
  831. fontWeight = tkFont.BOLD if self.fontBold.get() else tkFont.NORMAL
  832. newFont = (fontName, self.fontSize.get(), fontWeight)
  833. self.labelFontSample.config(font=newFont)
  834. self.textHighlightSample.configure(font=newFont)
  835. def SetHighlightTarget(self):
  836. if self.highlightTarget.get() == 'Cursor': #bg not possible
  837. self.radioFg.config(state=DISABLED)
  838. self.radioBg.config(state=DISABLED)
  839. self.fgHilite.set(1)
  840. else: #both fg and bg can be set
  841. self.radioFg.config(state=NORMAL)
  842. self.radioBg.config(state=NORMAL)
  843. self.fgHilite.set(1)
  844. self.SetColourSample()
  845. def SetColourSampleBinding(self, *args):
  846. self.SetColourSample()
  847. def SetColourSample(self):
  848. #set the colour smaple area
  849. tag = self.themeElements[self.highlightTarget.get()][0]
  850. plane = 'foreground' if self.fgHilite.get() else 'background'
  851. colour = self.textHighlightSample.tag_cget(tag, plane)
  852. self.frameColourSet.config(bg=colour)
  853. def PaintThemeSample(self):
  854. if self.themeIsBuiltin.get(): #a default theme
  855. theme = self.builtinTheme.get()
  856. else: #a user theme
  857. theme = self.customTheme.get()
  858. for elementTitle in self.themeElements:
  859. element = self.themeElements[elementTitle][0]
  860. colours = idleConf.GetHighlight(theme, element)
  861. if element == 'cursor': #cursor sample needs special painting
  862. colours['background'] = idleConf.GetHighlight(
  863. theme, 'normal', fgBg='bg')
  864. #handle any unsaved changes to this theme
  865. if theme in self.changedItems['highlight']:
  866. themeDict = self.changedItems['highlight'][theme]
  867. if element + '-foreground' in themeDict:
  868. colours['foreground'] = themeDict[element + '-foreground']
  869. if element + '-background' in themeDict:
  870. colours['background'] = themeDict[element + '-background']
  871. self.textHighlightSample.tag_config(element, **colours)
  872. self.SetColourSample()
  873. def HelpSourceSelected(self, event):
  874. self.SetHelpListButtonStates()
  875. def SetHelpListButtonStates(self):
  876. if self.listHelp.size() < 1: #no entries in list
  877. self.buttonHelpListEdit.config(state=DISABLED)
  878. self.buttonHelpListRemove.config(state=DISABLED)
  879. else: #there are some entries
  880. if self.listHelp.curselection(): #there currently is a selection
  881. self.buttonHelpListEdit.config(state=NORMAL)
  882. self.buttonHelpListRemove.config(state=NORMAL)
  883. else: #there currently is not a selection
  884. self.buttonHelpListEdit.config(state=DISABLED)
  885. self.buttonHelpListRemove.config(state=DISABLED)
  886. def HelpListItemAdd(self):
  887. helpSource = GetHelpSourceDialog(self, 'New Help Source').result
  888. if helpSource:
  889. self.userHelpList.append((helpSource[0], helpSource[1]))
  890. self.listHelp.insert(END, helpSource[0])
  891. self.UpdateUserHelpChangedItems()
  892. self.SetHelpListButtonStates()
  893. def HelpListItemEdit(self):
  894. itemIndex = self.listHelp.index(ANCHOR)
  895. helpSource = self.userHelpList[itemIndex]
  896. newHelpSource = GetHelpSourceDialog(
  897. self, 'Edit Help Source', menuItem=helpSource[0],
  898. filePath=helpSource[1]).result
  899. if (not newHelpSource) or (newHelpSource == helpSource):
  900. return #no changes
  901. self.userHelpList[itemIndex] = newHelpSource
  902. self.listHelp.delete(itemIndex)
  903. self.listHelp.insert(itemIndex, newHelpSource[0])
  904. self.UpdateUserHelpChangedItems()
  905. self.SetHelpListButtonStates()
  906. def HelpListItemRemove(self):
  907. itemIndex = self.listHelp.index(ANCHOR)
  908. del(self.userHelpList[itemIndex])
  909. self.listHelp.delete(itemIndex)
  910. self.UpdateUserHelpChangedItems()
  911. self.SetHelpListButtonStates()
  912. def UpdateUserHelpChangedItems(self):
  913. "Clear and rebuild the HelpFiles section in self.changedItems"
  914. self.changedItems['main']['HelpFiles'] = {}
  915. for num in range(1, len(self.userHelpList) + 1):
  916. self.AddChangedItem(
  917. 'main', 'HelpFiles', str(num),
  918. ';'.join(self.userHelpList[num-1][:2]))
  919. def LoadFontCfg(self):
  920. ##base editor font selection list
  921. fonts = list(tkFont.families(self))
  922. fonts.sort()
  923. for font in fonts:
  924. self.listFontName.insert(END, font)
  925. configuredFont = idleConf.GetFont(self, 'main', 'EditorWindow')
  926. fontName = configuredFont[0].lower()
  927. fontSize = configuredFont[1]
  928. fontBold = configuredFont[2]=='bold'
  929. self.fontName.set(fontName)
  930. lc_fonts = [s.lower() for s in fonts]
  931. try:
  932. currentFontIndex = lc_fonts.index(fontName)
  933. self.listFontName.see(currentFontIndex)
  934. self.listFontName.select_set(currentFontIndex)
  935. self.listFontName.select_anchor(currentFontIndex)
  936. except ValueError:
  937. pass
  938. ##font size dropdown
  939. self.optMenuFontSize.SetMenu(('7', '8', '9', '10', '11', '12', '13',
  940. '14', '16', '18', '20', '22'), fontSize )
  941. ##fontWeight
  942. self.fontBold.set(fontBold)
  943. ##font sample
  944. self.SetFontSample()
  945. def LoadTabCfg(self):
  946. ##indent sizes
  947. spaceNum = idleConf.GetOption(
  948. 'main', 'Indent', 'num-spaces', default=4, type='int')
  949. self.spaceNum.set(spaceNum)
  950. def LoadThemeCfg(self):
  951. ##current theme type radiobutton
  952. self.themeIsBuiltin.set(idleConf.GetOption(
  953. 'main', 'Theme', 'default', type='bool', default=1))
  954. ##currently set theme
  955. currentOption = idleConf.CurrentTheme()
  956. ##load available theme option menus
  957. if self.themeIsBuiltin.get(): #default theme selected
  958. itemList = idleConf.GetSectionList('default', 'highlight')
  959. itemList.sort()
  960. self.optMenuThemeBuiltin.SetMenu(itemList, currentOption)
  961. itemList = idleConf.GetSectionList('user', 'highlight')
  962. itemList.sort()
  963. if not itemList:
  964. self.radioThemeCustom.config(state=DISABLED)
  965. self.customTheme.set('- no custom themes -')
  966. else:
  967. self.optMenuThemeCustom.SetMenu(itemList, itemList[0])
  968. else: #user theme selected
  969. itemList = idleConf.GetSectionList('user', 'highlight')
  970. itemList.sort()
  971. self.optMenuThemeCustom.SetMenu(itemList, currentOption)
  972. itemList = idleConf.GetSectionList('default', 'highlight')
  973. itemList.sort()
  974. self.optMenuThemeBuiltin.SetMenu(itemList, itemList[0])
  975. self.SetThemeType()
  976. ##load theme element option menu
  977. themeNames = self.themeElements.keys()
  978. themeNames.sort(key=lambda x: self.themeElements[x][1])
  979. self.optMenuHighlightTarget.SetMenu(themeNames, themeNames[0])
  980. self.PaintThemeSample()
  981. self.SetHighlightTarget()
  982. def LoadKeyCfg(self):
  983. ##current keys type radiobutton
  984. self.keysAreBuiltin.set(idleConf.GetOption(
  985. 'main', 'Keys', 'default', type='bool', default=1))
  986. ##currently set keys
  987. currentOption = idleConf.CurrentKeys()
  988. ##load available keyset option menus
  989. if self.keysAreBuiltin.get(): #default theme selected
  990. itemList = idleConf.GetSectionList('default', 'keys')
  991. itemList.sort()
  992. self.optMenuKeysBuiltin.SetMenu(itemList, currentOption)
  993. itemList = idleConf.GetSectionList('user', 'keys')
  994. itemList.sort()
  995. if not itemList:
  996. self.radioKeysCustom.config(state=DISABLED)
  997. self.customKeys.set('- no custom keys -')
  998. else:
  999. self.optMenuKeysCustom.SetMenu(itemList, itemList[0])
  1000. else: #user key set selected
  1001. itemList = idleConf.GetSectionList('user', 'keys')
  1002. itemList.sort()
  1003. self.optMenuKeysCustom.SetMenu(itemList, currentOption)
  1004. itemList = idleConf.GetSectionList('default', 'keys')
  1005. itemList.sort()
  1006. self.optMenuKeysBuiltin.SetMenu(itemList, itemList[0])
  1007. self.SetKeysType()
  1008. ##load keyset element list
  1009. keySetName = idleConf.CurrentKeys()
  1010. self.LoadKeysList(keySetName)
  1011. def LoadGeneralCfg(self):
  1012. #startup state
  1013. self.startupEdit.set(idleConf.GetOption(
  1014. 'main', 'General', 'editor-on-startup', default=1, type='bool'))
  1015. #autosave state
  1016. self.autoSave.set(idleConf.GetOption(
  1017. 'main', 'General', 'autosave', default=0, type='bool'))
  1018. #initial window size
  1019. self.winWidth.set(idleConf.GetOption(
  1020. 'main', 'EditorWindow', 'width', type='int'))
  1021. self.winHeight.set(idleConf.GetOption(
  1022. 'main', 'EditorWindow', 'height', type='int'))
  1023. # default source encoding
  1024. self.encoding.set(idleConf.GetOption(
  1025. 'main', 'EditorWindow', 'encoding', default='none'))
  1026. # additional help sources
  1027. self.userHelpList = idleConf.GetAllExtraHelpSourcesList()
  1028. for helpItem in self.userHelpList:
  1029. self.listHelp.insert(END, helpItem[0])
  1030. self.SetHelpListButtonStates()
  1031. def LoadConfigs(self):
  1032. """
  1033. load configuration from default and user config files and populate
  1034. the widgets on the config dialog pages.
  1035. """
  1036. ### fonts / tabs page
  1037. self.LoadFontCfg()
  1038. self.LoadTabCfg()
  1039. ### highlighting page
  1040. self.LoadThemeCfg()
  1041. ### keys page
  1042. self.LoadKeyCfg()
  1043. ### general page
  1044. self.LoadGeneralCfg()
  1045. # note: extension page handled separately
  1046. def SaveNewKeySet(self, keySetName, keySet):
  1047. """
  1048. save a newly created core key set.
  1049. keySetName - string, the name of the new key set
  1050. keySet - dictionary containing the new key set
  1051. """
  1052. if not idleConf.userCfg['keys'].has_section(keySetName):
  1053. idleConf.userCfg['keys'].add_section(keySetName)
  1054. for event in keySet:
  1055. value = keySet[event]
  1056. idleConf.userCfg['keys'].SetOption(keySetName, event, value)
  1057. def SaveNewTheme(self, themeName, theme):
  1058. """
  1059. save a newly created theme.
  1060. themeName - string, the name of the new theme
  1061. theme - dictionary containing the new theme
  1062. """
  1063. if not idleConf.userCfg['highlight'].has_section(themeName):
  1064. idleConf.userCfg['highlight'].add_section(themeName)
  1065. for element in theme:
  1066. value = theme[element]
  1067. idleConf.userCfg['highlight'].SetOption(themeName, element, value)
  1068. def SetUserValue(self, configType, section, item, value):
  1069. if idleConf.defaultCfg[configType].has_option(section, item):
  1070. if idleConf.defaultCfg[configType].Get(section, item) == value:
  1071. #the setting equals a default setting, remove it from user cfg
  1072. return idleConf.userCfg[configType].RemoveOption(section, item)
  1073. #if we got here set the option
  1074. return idleConf.userCfg[configType].SetOption(section, item, value)
  1075. def SaveAllChangedConfigs(self):
  1076. "Save configuration changes to the user config file."
  1077. idleConf.userCfg['main'].Save()
  1078. for configType in self.changedItems:
  1079. cfgTypeHasChanges = False
  1080. for section in self.changedItems[configType]:
  1081. if section == 'HelpFiles':
  1082. #this section gets completely replaced
  1083. idleConf.userCfg['main'].remove_section('HelpFiles')
  1084. cfgTypeHasChanges = True
  1085. for item in self.changedItems[configType][section]:
  1086. value = self.changedItems[configType][section][item]
  1087. if self.SetUserValue(configType, section, item, value):
  1088. cfgTypeHasChanges = True
  1089. if cfgTypeHasChanges:
  1090. idleConf.userCfg[configType].Save()
  1091. for configType in ['keys', 'highlight']:
  1092. # save these even if unchanged!
  1093. idleConf.userCfg[configType].Save()
  1094. self.ResetChangedItems() #clear the changed items dict
  1095. self.save_all_changed_extensions() # uses a different mechanism
  1096. def DeactivateCurrentConfig(self):
  1097. #Before a config is saved, some cleanup of current
  1098. #config must be done - remove the previous keybindings
  1099. winInstances = self.parent.instance_dict
  1100. for instance in winInstances:
  1101. instance.RemoveKeybindings()
  1102. def ActivateConfigChanges(self):
  1103. "Dynamically apply configuration changes"
  1104. winInstances = self.parent.instance_dict.keys()
  1105. for instance in winInstances:
  1106. instance.ResetColorizer()
  1107. instance.ResetFont()
  1108. instance.set_notabs_indentwidth()
  1109. instance.ApplyKeybindings()
  1110. instance.reset_help_menu_entries()
  1111. def Cancel(self):
  1112. self.destroy()
  1113. def Ok(self):
  1114. self.Apply()
  1115. self.destroy()
  1116. def Apply(self):
  1117. self.DeactivateCurrentConfig()
  1118. self.SaveAllChangedConfigs()
  1119. self.ActivateConfigChanges()
  1120. def Help(self):
  1121. page = self.tabPages._current_page
  1122. view_text(self, title='Help for IDLE preferences',
  1123. text=help_common+help_pages.get(page, ''))
  1124. def CreatePageExtensions(self):
  1125. """Part of the config dialog used for configuring IDLE extensions.
  1126. This code is generic - it works for any and all IDLE extensions.
  1127. IDLE extensions save their configuration options using idleConf.
  1128. This code reads the current configuration using idleConf, supplies a
  1129. GUI interface to change the configuration values, and saves the
  1130. changes using idleConf.
  1131. Not all changes take effect immediately - some may require restarting IDLE.
  1132. This depends on each extension's implementation.
  1133. All values are treated as text, and it is up to the user to supply
  1134. reasonable values. The only exception to this are the 'enable*' options,
  1135. which are boolean, and can be toggled with a True/False button.
  1136. """
  1137. parent = self.parent
  1138. frame = self.tabPages.pages['Extensions'].frame
  1139. self.ext_defaultCfg = idleConf.defaultCfg['extensions']
  1140. self.ext_userCfg = idleConf.userCfg['extensions']
  1141. self.is_int = self.register(is_int)
  1142. self.load_extensions()
  1143. # create widgets - a listbox shows all available extensions, with the
  1144. # controls for the extension selected in the listbox to the right
  1145. self.extension_names = StringVar(self)
  1146. frame.rowconfigure(0, weight=1)
  1147. frame.columnconfigure(2, weight=1)
  1148. self.extension_list = Listbox(frame, listvariable=self.extension_names,
  1149. selectmode='browse')
  1150. self.extension_list.bind('<<ListboxSelect>>', self.extension_selected)
  1151. scroll = Scrollbar(frame, command=self.extension_list.yview)
  1152. self.extension_list.yscrollcommand=scroll.set
  1153. self.details_frame = LabelFrame(frame, width=250, height=250)
  1154. self.extension_list.grid(column=0, row=0, sticky='nws')
  1155. scroll.grid(column=1, row=0, sticky='ns')
  1156. self.details_frame.grid(column=2, row=0, sticky='nsew', padx=[10, 0])
  1157. frame.configure(padx=10, pady=10)
  1158. self.config_frame = {}
  1159. self.current_extension = None
  1160. self.outerframe = self # TEMPORARY
  1161. self.tabbed_page_set = self.extension_list # TEMPORARY
  1162. # create the frame holding controls for each extension
  1163. ext_names = ''
  1164. for ext_name in sorted(self.extensions):
  1165. self.create_extension_frame(ext_name)
  1166. ext_names = ext_names + '{' + ext_name + '} '
  1167. self.extension_names.set(ext_names)
  1168. self.extension_list.selection_set(0)
  1169. self.extension_selected(None)
  1170. def load_extensions(self):
  1171. "Fill self.extensions with data from the default and user configs."
  1172. self.extensions = {}
  1173. for ext_name in idleConf.GetExtensions(active_only=False):
  1174. self.extensions[ext_name] = []
  1175. for ext_name in self.extensions:
  1176. opt_list = sorted(self.ext_defaultCfg.GetOptionList(ext_name))
  1177. # bring 'enable' options to the beginning of the list
  1178. enables = [opt_name for opt_name in opt_list
  1179. if opt_name.startswith('enable')]
  1180. for opt_name in enables:
  1181. opt_list.remove(opt_name)
  1182. opt_list = enables + opt_list
  1183. for opt_name in opt_list:
  1184. def_str = self.ext_defaultCfg.Get(
  1185. ext_name, opt_name, raw=True)
  1186. try:
  1187. def_obj = {'True':True, 'False':False}[def_str]
  1188. opt_type = 'bool'
  1189. except KeyError:
  1190. try:
  1191. def_obj = int(def_str)
  1192. opt_type = 'int'
  1193. except ValueError:
  1194. def_obj = def_str
  1195. opt_type = None
  1196. try:
  1197. value = self.ext_userCfg.Get(
  1198. ext_name, opt_name, type=opt_type, raw=True,
  1199. default=def_obj)
  1200. except ValueError: # Need this until .Get fixed
  1201. value = def_obj # bad values overwritten by entry
  1202. var = StringVar(self)
  1203. var.set(str(value))
  1204. self.extensions[ext_name].append({'name': opt_name,
  1205. 'type': opt_type,
  1206. 'default': def_str,
  1207. 'value': value,
  1208. 'var': var,
  1209. })
  1210. def extension_selected(self, event):
  1211. newsel = self.extension_list.curselection()
  1212. if newsel:
  1213. newsel = self.extension_list.get(newsel)
  1214. if newsel is None or newsel != self.current_extension:
  1215. if self.current_extension:
  1216. self.details_frame.config(text='')
  1217. self.config_frame[self.current_extension].grid_forget()
  1218. self.current_extension = None
  1219. if newsel:
  1220. self.details_frame.config(text=newsel)
  1221. self.config_frame[newsel].grid(column=0, row=0, sticky='nsew')
  1222. self.current_extension = newsel
  1223. def create_extension_frame(self, ext_name):
  1224. """Create a frame holding the widgets to configure one extension"""
  1225. f = VerticalScrolledFrame(self.details_frame, height=250, width=250)
  1226. self.config_frame[ext_name] = f
  1227. entry_area = f.interior
  1228. # create an entry for each configuration option
  1229. for row, opt in enumerate(self.extensions[ext_name]):
  1230. # create a row with a label and entry/checkbutton
  1231. label = Label(entry_area, text=opt['name'])
  1232. label.grid(row=row, column=0, sticky=NW)
  1233. var = opt['var']
  1234. if opt['type'] == 'bool':
  1235. Checkbutton(entry_area, textvariable=var, variable=var,
  1236. onvalue='True', offvalue='False',
  1237. indicatoron=FALSE, selectcolor='', width=8
  1238. ).grid(row=row, column=1, sticky=W, padx=7)
  1239. elif opt['type'] == 'int':
  1240. Entry(entry_area, textvariable=var, validate='key',
  1241. validatecommand=(self.is_int, '%P')
  1242. ).grid(row=row, column=1, sticky=NSEW, padx=7)
  1243. else:
  1244. Entry(entry_area, textvariable=var
  1245. ).grid(row=row, column=1, sticky=NSEW, padx=7)
  1246. return
  1247. def set_extension_value(self, section, opt):
  1248. name = opt['name']
  1249. default = opt['default']
  1250. value = opt['var'].get().strip() or default
  1251. opt['var'].set(value)
  1252. # if self.defaultCfg.has_section(section):
  1253. # Currently, always true; if not, indent to return
  1254. if (value == default):
  1255. return self.ext_userCfg.RemoveOption(section, name)
  1256. # set the option
  1257. return self.ext_userCfg.SetOption(section, name, value)
  1258. def save_all_changed_extensions(self):
  1259. """Save configuration changes to the user config file."""
  1260. has_changes = False
  1261. for ext_name in self.extensions:
  1262. options = self.extensions[ext_name]
  1263. for opt in options:
  1264. if self.set_extension_value(ext_name, opt):
  1265. has_changes = True
  1266. if has_changes:
  1267. self.ext_userCfg.Save()
  1268. help_common = '''\
  1269. When you click either the Apply or Ok buttons, settings in this
  1270. dialog that are different from IDLE's default are saved in
  1271. a .idlerc directory in your home directory. Except as noted,
  1272. these changes apply to all versions of IDLE installed on this
  1273. machine. Some do not take affect until IDLE is restarted.
  1274. [Cancel] only cancels changes made since the last save.
  1275. '''
  1276. help_pages = {
  1277. 'Highlighting':'''
  1278. Highlighting:
  1279. The IDLE Dark color theme is new in October 2015. It can only
  1280. be used with older IDLE releases if it is saved as a custom
  1281. theme, with a different name.
  1282. '''
  1283. }
  1284. def is_int(s):
  1285. "Return 's is blank or represents an int'"
  1286. if not s:
  1287. return True
  1288. try:
  1289. int(s)
  1290. return True
  1291. except ValueError:
  1292. return False
  1293. class VerticalScrolledFrame(Frame):
  1294. """A pure Tkinter vertically scrollable frame.
  1295. * Use the 'interior' attribute to place widgets inside the scrollable frame
  1296. * Construct and pack/place/grid normally
  1297. * This frame only allows vertical scrolling
  1298. """
  1299. def __init__(self, parent, *args, **kw):
  1300. Frame.__init__(self, parent, *args, **kw)
  1301. # create a canvas object and a vertical scrollbar for scrolling it
  1302. vscrollbar = Scrollbar(self, orient=VERTICAL)
  1303. vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE)
  1304. canvas = Canvas(self, bd=0, highlightthickness=0,
  1305. yscrollcommand=vscrollbar.set, width=240)
  1306. canvas.pack(side=LEFT, fill=BOTH, expand=TRUE)
  1307. vscrollbar.config(command=canvas.yview)
  1308. # reset the view
  1309. canvas.xview_moveto(0)
  1310. canvas.yview_moveto(0)
  1311. # create a frame inside the canvas which will be scrolled with it
  1312. self.interior = interior = Frame(canvas)
  1313. interior_id = canvas.create_window(0, 0, window=interior, anchor=NW)
  1314. # track changes to the canvas and frame width and sync them,
  1315. # also updating the scrollbar
  1316. def _configure_interior(event):
  1317. # update the scrollbars to match the size of the inner frame
  1318. size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
  1319. canvas.config(scrollregion="0 0 %s %s" % size)
  1320. interior.bind('<Configure>', _configure_interior)
  1321. def _configure_canvas(event):
  1322. if interior.winfo_reqwidth() != canvas.winfo_width():
  1323. # update the inner frame's width to fill the canvas
  1324. canvas.itemconfigure(interior_id, width=canvas.winfo_width())
  1325. canvas.bind('<Configure>', _configure_canvas)
  1326. return
  1327. if __name__ == '__main__':
  1328. import unittest
  1329. unittest.main('idlelib.idle_test.test_configdialog',
  1330. verbosity=2, exit=False)
  1331. from idlelib.idle_test.htest import run
  1332. run(ConfigDialog)