configdialog.py 102 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380
  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. import re
  10. from tkinter import (Toplevel, Listbox, Text, Scale, Canvas,
  11. StringVar, BooleanVar, IntVar, TRUE, FALSE,
  12. TOP, BOTTOM, RIGHT, LEFT, SOLID, GROOVE,
  13. NONE, BOTH, X, Y, W, E, EW, NS, NSEW, NW,
  14. HORIZONTAL, VERTICAL, ANCHOR, ACTIVE, END)
  15. from tkinter.ttk import (Frame, LabelFrame, Button, Checkbutton, Entry, Label,
  16. OptionMenu, Notebook, Radiobutton, Scrollbar, Style)
  17. import tkinter.colorchooser as tkColorChooser
  18. import tkinter.font as tkFont
  19. from tkinter import messagebox
  20. from idlelib.config import idleConf, ConfigChanges
  21. from idlelib.config_key import GetKeysDialog
  22. from idlelib.dynoption import DynOptionMenu
  23. from idlelib import macosx
  24. from idlelib.query import SectionName, HelpSource
  25. from idlelib.textview import view_text
  26. from idlelib.autocomplete import AutoComplete
  27. from idlelib.codecontext import CodeContext
  28. from idlelib.parenmatch import ParenMatch
  29. from idlelib.format import FormatParagraph
  30. from idlelib.squeezer import Squeezer
  31. from idlelib.textview import ScrollableTextFrame
  32. changes = ConfigChanges()
  33. # Reload changed options in the following classes.
  34. reloadables = (AutoComplete, CodeContext, ParenMatch, FormatParagraph,
  35. Squeezer)
  36. class ConfigDialog(Toplevel):
  37. """Config dialog for IDLE.
  38. """
  39. def __init__(self, parent, title='', *, _htest=False, _utest=False):
  40. """Show the tabbed dialog for user configuration.
  41. Args:
  42. parent - parent of this dialog
  43. title - string which is the title of this popup dialog
  44. _htest - bool, change box location when running htest
  45. _utest - bool, don't wait_window when running unittest
  46. Note: Focus set on font page fontlist.
  47. Methods:
  48. create_widgets
  49. cancel: Bound to DELETE_WINDOW protocol.
  50. """
  51. Toplevel.__init__(self, parent)
  52. self.parent = parent
  53. if _htest:
  54. parent.instance_dict = {}
  55. if not _utest:
  56. self.withdraw()
  57. self.configure(borderwidth=5)
  58. self.title(title or 'IDLE Preferences')
  59. x = parent.winfo_rootx() + 20
  60. y = parent.winfo_rooty() + (30 if not _htest else 150)
  61. self.geometry(f'+{x}+{y}')
  62. # Each theme element key is its display name.
  63. # The first value of the tuple is the sample area tag name.
  64. # The second value is the display name list sort index.
  65. self.create_widgets()
  66. self.resizable(height=FALSE, width=FALSE)
  67. self.transient(parent)
  68. self.protocol("WM_DELETE_WINDOW", self.cancel)
  69. self.fontpage.fontlist.focus_set()
  70. # XXX Decide whether to keep or delete these key bindings.
  71. # Key bindings for this dialog.
  72. # self.bind('<Escape>', self.Cancel) #dismiss dialog, no save
  73. # self.bind('<Alt-a>', self.Apply) #apply changes, save
  74. # self.bind('<F1>', self.Help) #context help
  75. # Attach callbacks after loading config to avoid calling them.
  76. tracers.attach()
  77. if not _utest:
  78. self.grab_set()
  79. self.wm_deiconify()
  80. self.wait_window()
  81. def create_widgets(self):
  82. """Create and place widgets for tabbed dialog.
  83. Widgets Bound to self:
  84. note: Notebook
  85. highpage: HighPage
  86. fontpage: FontPage
  87. keyspage: KeysPage
  88. genpage: GenPage
  89. extpage: self.create_page_extensions
  90. Methods:
  91. create_action_buttons
  92. load_configs: Load pages except for extensions.
  93. activate_config_changes: Tell editors to reload.
  94. """
  95. self.note = note = Notebook(self)
  96. self.highpage = HighPage(note)
  97. self.fontpage = FontPage(note, self.highpage)
  98. self.keyspage = KeysPage(note)
  99. self.genpage = GenPage(note)
  100. self.extpage = self.create_page_extensions()
  101. note.add(self.fontpage, text='Fonts/Tabs')
  102. note.add(self.highpage, text='Highlights')
  103. note.add(self.keyspage, text=' Keys ')
  104. note.add(self.genpage, text=' General ')
  105. note.add(self.extpage, text='Extensions')
  106. note.enable_traversal()
  107. note.pack(side=TOP, expand=TRUE, fill=BOTH)
  108. self.create_action_buttons().pack(side=BOTTOM)
  109. def create_action_buttons(self):
  110. """Return frame of action buttons for dialog.
  111. Methods:
  112. ok
  113. apply
  114. cancel
  115. help
  116. Widget Structure:
  117. outer: Frame
  118. buttons: Frame
  119. (no assignment): Button (ok)
  120. (no assignment): Button (apply)
  121. (no assignment): Button (cancel)
  122. (no assignment): Button (help)
  123. (no assignment): Frame
  124. """
  125. if macosx.isAquaTk():
  126. # Changing the default padding on OSX results in unreadable
  127. # text in the buttons.
  128. padding_args = {}
  129. else:
  130. padding_args = {'padding': (6, 3)}
  131. outer = Frame(self, padding=2)
  132. buttons_frame = Frame(outer, padding=2)
  133. self.buttons = {}
  134. for txt, cmd in (
  135. ('Ok', self.ok),
  136. ('Apply', self.apply),
  137. ('Cancel', self.cancel),
  138. ('Help', self.help)):
  139. self.buttons[txt] = Button(buttons_frame, text=txt, command=cmd,
  140. takefocus=FALSE, **padding_args)
  141. self.buttons[txt].pack(side=LEFT, padx=5)
  142. # Add space above buttons.
  143. Frame(outer, height=2, borderwidth=0).pack(side=TOP)
  144. buttons_frame.pack(side=BOTTOM)
  145. return outer
  146. def ok(self):
  147. """Apply config changes, then dismiss dialog.
  148. Methods:
  149. apply
  150. destroy: inherited
  151. """
  152. self.apply()
  153. self.destroy()
  154. def apply(self):
  155. """Apply config changes and leave dialog open.
  156. Methods:
  157. deactivate_current_config
  158. save_all_changed_extensions
  159. activate_config_changes
  160. """
  161. self.deactivate_current_config()
  162. changes.save_all()
  163. self.save_all_changed_extensions()
  164. self.activate_config_changes()
  165. def cancel(self):
  166. """Dismiss config dialog.
  167. Methods:
  168. destroy: inherited
  169. """
  170. changes.clear()
  171. self.destroy()
  172. def destroy(self):
  173. global font_sample_text
  174. font_sample_text = self.fontpage.font_sample.get('1.0', 'end')
  175. self.grab_release()
  176. super().destroy()
  177. def help(self):
  178. """Create textview for config dialog help.
  179. Attributes accessed:
  180. note
  181. Methods:
  182. view_text: Method from textview module.
  183. """
  184. page = self.note.tab(self.note.select(), option='text').strip()
  185. view_text(self, title='Help for IDLE preferences',
  186. contents=help_common+help_pages.get(page, ''))
  187. def deactivate_current_config(self):
  188. """Remove current key bindings.
  189. Iterate over window instances defined in parent and remove
  190. the keybindings.
  191. """
  192. # Before a config is saved, some cleanup of current
  193. # config must be done - remove the previous keybindings.
  194. win_instances = self.parent.instance_dict.keys()
  195. for instance in win_instances:
  196. instance.RemoveKeybindings()
  197. def activate_config_changes(self):
  198. """Apply configuration changes to current windows.
  199. Dynamically update the current parent window instances
  200. with some of the configuration changes.
  201. """
  202. win_instances = self.parent.instance_dict.keys()
  203. for instance in win_instances:
  204. instance.ResetColorizer()
  205. instance.ResetFont()
  206. instance.set_notabs_indentwidth()
  207. instance.ApplyKeybindings()
  208. instance.reset_help_menu_entries()
  209. instance.update_cursor_blink()
  210. for klass in reloadables:
  211. klass.reload()
  212. def create_page_extensions(self):
  213. """Part of the config dialog used for configuring IDLE extensions.
  214. This code is generic - it works for any and all IDLE extensions.
  215. IDLE extensions save their configuration options using idleConf.
  216. This code reads the current configuration using idleConf, supplies a
  217. GUI interface to change the configuration values, and saves the
  218. changes using idleConf.
  219. Not all changes take effect immediately - some may require restarting IDLE.
  220. This depends on each extension's implementation.
  221. All values are treated as text, and it is up to the user to supply
  222. reasonable values. The only exception to this are the 'enable*' options,
  223. which are boolean, and can be toggled with a True/False button.
  224. Methods:
  225. load_extensions:
  226. extension_selected: Handle selection from list.
  227. create_extension_frame: Hold widgets for one extension.
  228. set_extension_value: Set in userCfg['extensions'].
  229. save_all_changed_extensions: Call extension page Save().
  230. """
  231. parent = self.parent
  232. frame = Frame(self.note)
  233. self.ext_defaultCfg = idleConf.defaultCfg['extensions']
  234. self.ext_userCfg = idleConf.userCfg['extensions']
  235. self.is_int = self.register(is_int)
  236. self.load_extensions()
  237. # Create widgets - a listbox shows all available extensions, with the
  238. # controls for the extension selected in the listbox to the right.
  239. self.extension_names = StringVar(self)
  240. frame.rowconfigure(0, weight=1)
  241. frame.columnconfigure(2, weight=1)
  242. self.extension_list = Listbox(frame, listvariable=self.extension_names,
  243. selectmode='browse')
  244. self.extension_list.bind('<<ListboxSelect>>', self.extension_selected)
  245. scroll = Scrollbar(frame, command=self.extension_list.yview)
  246. self.extension_list.yscrollcommand=scroll.set
  247. self.details_frame = LabelFrame(frame, width=250, height=250)
  248. self.extension_list.grid(column=0, row=0, sticky='nws')
  249. scroll.grid(column=1, row=0, sticky='ns')
  250. self.details_frame.grid(column=2, row=0, sticky='nsew', padx=[10, 0])
  251. frame.configure(padding=10)
  252. self.config_frame = {}
  253. self.current_extension = None
  254. self.outerframe = self # TEMPORARY
  255. self.tabbed_page_set = self.extension_list # TEMPORARY
  256. # Create the frame holding controls for each extension.
  257. ext_names = ''
  258. for ext_name in sorted(self.extensions):
  259. self.create_extension_frame(ext_name)
  260. ext_names = ext_names + '{' + ext_name + '} '
  261. self.extension_names.set(ext_names)
  262. self.extension_list.selection_set(0)
  263. self.extension_selected(None)
  264. return frame
  265. def load_extensions(self):
  266. "Fill self.extensions with data from the default and user configs."
  267. self.extensions = {}
  268. for ext_name in idleConf.GetExtensions(active_only=False):
  269. # Former built-in extensions are already filtered out.
  270. self.extensions[ext_name] = []
  271. for ext_name in self.extensions:
  272. opt_list = sorted(self.ext_defaultCfg.GetOptionList(ext_name))
  273. # Bring 'enable' options to the beginning of the list.
  274. enables = [opt_name for opt_name in opt_list
  275. if opt_name.startswith('enable')]
  276. for opt_name in enables:
  277. opt_list.remove(opt_name)
  278. opt_list = enables + opt_list
  279. for opt_name in opt_list:
  280. def_str = self.ext_defaultCfg.Get(
  281. ext_name, opt_name, raw=True)
  282. try:
  283. def_obj = {'True':True, 'False':False}[def_str]
  284. opt_type = 'bool'
  285. except KeyError:
  286. try:
  287. def_obj = int(def_str)
  288. opt_type = 'int'
  289. except ValueError:
  290. def_obj = def_str
  291. opt_type = None
  292. try:
  293. value = self.ext_userCfg.Get(
  294. ext_name, opt_name, type=opt_type, raw=True,
  295. default=def_obj)
  296. except ValueError: # Need this until .Get fixed.
  297. value = def_obj # Bad values overwritten by entry.
  298. var = StringVar(self)
  299. var.set(str(value))
  300. self.extensions[ext_name].append({'name': opt_name,
  301. 'type': opt_type,
  302. 'default': def_str,
  303. 'value': value,
  304. 'var': var,
  305. })
  306. def extension_selected(self, event):
  307. "Handle selection of an extension from the list."
  308. newsel = self.extension_list.curselection()
  309. if newsel:
  310. newsel = self.extension_list.get(newsel)
  311. if newsel is None or newsel != self.current_extension:
  312. if self.current_extension:
  313. self.details_frame.config(text='')
  314. self.config_frame[self.current_extension].grid_forget()
  315. self.current_extension = None
  316. if newsel:
  317. self.details_frame.config(text=newsel)
  318. self.config_frame[newsel].grid(column=0, row=0, sticky='nsew')
  319. self.current_extension = newsel
  320. def create_extension_frame(self, ext_name):
  321. """Create a frame holding the widgets to configure one extension"""
  322. f = VerticalScrolledFrame(self.details_frame, height=250, width=250)
  323. self.config_frame[ext_name] = f
  324. entry_area = f.interior
  325. # Create an entry for each configuration option.
  326. for row, opt in enumerate(self.extensions[ext_name]):
  327. # Create a row with a label and entry/checkbutton.
  328. label = Label(entry_area, text=opt['name'])
  329. label.grid(row=row, column=0, sticky=NW)
  330. var = opt['var']
  331. if opt['type'] == 'bool':
  332. Checkbutton(entry_area, variable=var,
  333. onvalue='True', offvalue='False', width=8
  334. ).grid(row=row, column=1, sticky=W, padx=7)
  335. elif opt['type'] == 'int':
  336. Entry(entry_area, textvariable=var, validate='key',
  337. validatecommand=(self.is_int, '%P'), width=10
  338. ).grid(row=row, column=1, sticky=NSEW, padx=7)
  339. else: # type == 'str'
  340. # Limit size to fit non-expanding space with larger font.
  341. Entry(entry_area, textvariable=var, width=15
  342. ).grid(row=row, column=1, sticky=NSEW, padx=7)
  343. return
  344. def set_extension_value(self, section, opt):
  345. """Return True if the configuration was added or changed.
  346. If the value is the same as the default, then remove it
  347. from user config file.
  348. """
  349. name = opt['name']
  350. default = opt['default']
  351. value = opt['var'].get().strip() or default
  352. opt['var'].set(value)
  353. # if self.defaultCfg.has_section(section):
  354. # Currently, always true; if not, indent to return.
  355. if (value == default):
  356. return self.ext_userCfg.RemoveOption(section, name)
  357. # Set the option.
  358. return self.ext_userCfg.SetOption(section, name, value)
  359. def save_all_changed_extensions(self):
  360. """Save configuration changes to the user config file.
  361. Attributes accessed:
  362. extensions
  363. Methods:
  364. set_extension_value
  365. """
  366. has_changes = False
  367. for ext_name in self.extensions:
  368. options = self.extensions[ext_name]
  369. for opt in options:
  370. if self.set_extension_value(ext_name, opt):
  371. has_changes = True
  372. if has_changes:
  373. self.ext_userCfg.Save()
  374. # class TabPage(Frame): # A template for Page classes.
  375. # def __init__(self, master):
  376. # super().__init__(master)
  377. # self.create_page_tab()
  378. # self.load_tab_cfg()
  379. # def create_page_tab(self):
  380. # # Define tk vars and register var and callback with tracers.
  381. # # Create subframes and widgets.
  382. # # Pack widgets.
  383. # def load_tab_cfg(self):
  384. # # Initialize widgets with data from idleConf.
  385. # def var_changed_var_name():
  386. # # For each tk var that needs other than default callback.
  387. # def other_methods():
  388. # # Define tab-specific behavior.
  389. font_sample_text = (
  390. '<ASCII/Latin1>\n'
  391. 'AaBbCcDdEeFfGgHhIiJj\n1234567890#:+=(){}[]\n'
  392. '\u00a2\u00a3\u00a5\u00a7\u00a9\u00ab\u00ae\u00b6\u00bd\u011e'
  393. '\u00c0\u00c1\u00c2\u00c3\u00c4\u00c5\u00c7\u00d0\u00d8\u00df\n'
  394. '\n<IPA,Greek,Cyrillic>\n'
  395. '\u0250\u0255\u0258\u025e\u025f\u0264\u026b\u026e\u0270\u0277'
  396. '\u027b\u0281\u0283\u0286\u028e\u029e\u02a2\u02ab\u02ad\u02af\n'
  397. '\u0391\u03b1\u0392\u03b2\u0393\u03b3\u0394\u03b4\u0395\u03b5'
  398. '\u0396\u03b6\u0397\u03b7\u0398\u03b8\u0399\u03b9\u039a\u03ba\n'
  399. '\u0411\u0431\u0414\u0434\u0416\u0436\u041f\u043f\u0424\u0444'
  400. '\u0427\u0447\u042a\u044a\u042d\u044d\u0460\u0464\u046c\u04dc\n'
  401. '\n<Hebrew, Arabic>\n'
  402. '\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9'
  403. '\u05da\u05db\u05dc\u05dd\u05de\u05df\u05e0\u05e1\u05e2\u05e3\n'
  404. '\u0627\u0628\u062c\u062f\u0647\u0648\u0632\u062d\u0637\u064a'
  405. '\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669\n'
  406. '\n<Devanagari, Tamil>\n'
  407. '\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f'
  408. '\u0905\u0906\u0907\u0908\u0909\u090a\u090f\u0910\u0913\u0914\n'
  409. '\u0be6\u0be7\u0be8\u0be9\u0bea\u0beb\u0bec\u0bed\u0bee\u0bef'
  410. '\u0b85\u0b87\u0b89\u0b8e\n'
  411. '\n<East Asian>\n'
  412. '\u3007\u4e00\u4e8c\u4e09\u56db\u4e94\u516d\u4e03\u516b\u4e5d\n'
  413. '\u6c49\u5b57\u6f22\u5b57\u4eba\u6728\u706b\u571f\u91d1\u6c34\n'
  414. '\uac00\ub0d0\ub354\ub824\ubaa8\ubd64\uc218\uc720\uc988\uce58\n'
  415. '\u3042\u3044\u3046\u3048\u304a\u30a2\u30a4\u30a6\u30a8\u30aa\n'
  416. )
  417. class FontPage(Frame):
  418. def __init__(self, master, highpage):
  419. super().__init__(master)
  420. self.highlight_sample = highpage.highlight_sample
  421. self.create_page_font_tab()
  422. self.load_font_cfg()
  423. self.load_tab_cfg()
  424. def create_page_font_tab(self):
  425. """Return frame of widgets for Font/Tabs tab.
  426. Fonts: Enable users to provisionally change font face, size, or
  427. boldness and to see the consequence of proposed choices. Each
  428. action set 3 options in changes structuree and changes the
  429. corresponding aspect of the font sample on this page and
  430. highlight sample on highlight page.
  431. Function load_font_cfg initializes font vars and widgets from
  432. idleConf entries and tk.
  433. Fontlist: mouse button 1 click or up or down key invoke
  434. on_fontlist_select(), which sets var font_name.
  435. Sizelist: clicking the menubutton opens the dropdown menu. A
  436. mouse button 1 click or return key sets var font_size.
  437. Bold_toggle: clicking the box toggles var font_bold.
  438. Changing any of the font vars invokes var_changed_font, which
  439. adds all 3 font options to changes and calls set_samples.
  440. Set_samples applies a new font constructed from the font vars to
  441. font_sample and to highlight_sample on the highlight page.
  442. Tabs: Enable users to change spaces entered for indent tabs.
  443. Changing indent_scale value with the mouse sets Var space_num,
  444. which invokes the default callback to add an entry to
  445. changes. Load_tab_cfg initializes space_num to default.
  446. Widgets for FontPage(Frame): (*) widgets bound to self
  447. frame_font: LabelFrame
  448. frame_font_name: Frame
  449. font_name_title: Label
  450. (*)fontlist: ListBox - font_name
  451. scroll_font: Scrollbar
  452. frame_font_param: Frame
  453. font_size_title: Label
  454. (*)sizelist: DynOptionMenu - font_size
  455. (*)bold_toggle: Checkbutton - font_bold
  456. frame_sample: LabelFrame
  457. (*)font_sample: Label
  458. frame_indent: LabelFrame
  459. indent_title: Label
  460. (*)indent_scale: Scale - space_num
  461. """
  462. self.font_name = tracers.add(StringVar(self), self.var_changed_font)
  463. self.font_size = tracers.add(StringVar(self), self.var_changed_font)
  464. self.font_bold = tracers.add(BooleanVar(self), self.var_changed_font)
  465. self.space_num = tracers.add(IntVar(self), ('main', 'Indent', 'num-spaces'))
  466. # Define frames and widgets.
  467. frame_font = LabelFrame(
  468. self, borderwidth=2, relief=GROOVE, text=' Shell/Editor Font ')
  469. frame_sample = LabelFrame(
  470. self, borderwidth=2, relief=GROOVE,
  471. text=' Font Sample (Editable) ')
  472. frame_indent = LabelFrame(
  473. self, borderwidth=2, relief=GROOVE, text=' Indentation Width ')
  474. # frame_font.
  475. frame_font_name = Frame(frame_font)
  476. frame_font_param = Frame(frame_font)
  477. font_name_title = Label(
  478. frame_font_name, justify=LEFT, text='Font Face :')
  479. self.fontlist = Listbox(frame_font_name, height=15,
  480. takefocus=True, exportselection=FALSE)
  481. self.fontlist.bind('<ButtonRelease-1>', self.on_fontlist_select)
  482. self.fontlist.bind('<KeyRelease-Up>', self.on_fontlist_select)
  483. self.fontlist.bind('<KeyRelease-Down>', self.on_fontlist_select)
  484. scroll_font = Scrollbar(frame_font_name)
  485. scroll_font.config(command=self.fontlist.yview)
  486. self.fontlist.config(yscrollcommand=scroll_font.set)
  487. font_size_title = Label(frame_font_param, text='Size :')
  488. self.sizelist = DynOptionMenu(frame_font_param, self.font_size, None)
  489. self.bold_toggle = Checkbutton(
  490. frame_font_param, variable=self.font_bold,
  491. onvalue=1, offvalue=0, text='Bold')
  492. # frame_sample.
  493. font_sample_frame = ScrollableTextFrame(frame_sample)
  494. self.font_sample = font_sample_frame.text
  495. self.font_sample.config(wrap=NONE, width=1, height=1)
  496. self.font_sample.insert(END, font_sample_text)
  497. # frame_indent.
  498. indent_title = Label(
  499. frame_indent, justify=LEFT,
  500. text='Python Standard: 4 Spaces!')
  501. self.indent_scale = Scale(
  502. frame_indent, variable=self.space_num,
  503. orient='horizontal', tickinterval=2, from_=2, to=16)
  504. # Grid and pack widgets:
  505. self.columnconfigure(1, weight=1)
  506. self.rowconfigure(2, weight=1)
  507. frame_font.grid(row=0, column=0, padx=5, pady=5)
  508. frame_sample.grid(row=0, column=1, rowspan=3, padx=5, pady=5,
  509. sticky='nsew')
  510. frame_indent.grid(row=1, column=0, padx=5, pady=5, sticky='ew')
  511. # frame_font.
  512. frame_font_name.pack(side=TOP, padx=5, pady=5, fill=X)
  513. frame_font_param.pack(side=TOP, padx=5, pady=5, fill=X)
  514. font_name_title.pack(side=TOP, anchor=W)
  515. self.fontlist.pack(side=LEFT, expand=TRUE, fill=X)
  516. scroll_font.pack(side=LEFT, fill=Y)
  517. font_size_title.pack(side=LEFT, anchor=W)
  518. self.sizelist.pack(side=LEFT, anchor=W)
  519. self.bold_toggle.pack(side=LEFT, anchor=W, padx=20)
  520. # frame_sample.
  521. font_sample_frame.pack(expand=TRUE, fill=BOTH)
  522. # frame_indent.
  523. indent_title.pack(side=TOP, anchor=W, padx=5)
  524. self.indent_scale.pack(side=TOP, padx=5, fill=X)
  525. def load_font_cfg(self):
  526. """Load current configuration settings for the font options.
  527. Retrieve current font with idleConf.GetFont and font families
  528. from tk. Setup fontlist and set font_name. Setup sizelist,
  529. which sets font_size. Set font_bold. Call set_samples.
  530. """
  531. configured_font = idleConf.GetFont(self, 'main', 'EditorWindow')
  532. font_name = configured_font[0].lower()
  533. font_size = configured_font[1]
  534. font_bold = configured_font[2]=='bold'
  535. # Set sorted no-duplicate editor font selection list and font_name.
  536. fonts = sorted(set(tkFont.families(self)))
  537. for font in fonts:
  538. self.fontlist.insert(END, font)
  539. self.font_name.set(font_name)
  540. lc_fonts = [s.lower() for s in fonts]
  541. try:
  542. current_font_index = lc_fonts.index(font_name)
  543. self.fontlist.see(current_font_index)
  544. self.fontlist.select_set(current_font_index)
  545. self.fontlist.select_anchor(current_font_index)
  546. self.fontlist.activate(current_font_index)
  547. except ValueError:
  548. pass
  549. # Set font size dropdown.
  550. self.sizelist.SetMenu(('7', '8', '9', '10', '11', '12', '13', '14',
  551. '16', '18', '20', '22', '25', '29', '34', '40'),
  552. font_size)
  553. # Set font weight.
  554. self.font_bold.set(font_bold)
  555. self.set_samples()
  556. def var_changed_font(self, *params):
  557. """Store changes to font attributes.
  558. When one font attribute changes, save them all, as they are
  559. not independent from each other. In particular, when we are
  560. overriding the default font, we need to write out everything.
  561. """
  562. value = self.font_name.get()
  563. changes.add_option('main', 'EditorWindow', 'font', value)
  564. value = self.font_size.get()
  565. changes.add_option('main', 'EditorWindow', 'font-size', value)
  566. value = self.font_bold.get()
  567. changes.add_option('main', 'EditorWindow', 'font-bold', value)
  568. self.set_samples()
  569. def on_fontlist_select(self, event):
  570. """Handle selecting a font from the list.
  571. Event can result from either mouse click or Up or Down key.
  572. Set font_name and example displays to selection.
  573. """
  574. font = self.fontlist.get(
  575. ACTIVE if event.type.name == 'KeyRelease' else ANCHOR)
  576. self.font_name.set(font.lower())
  577. def set_samples(self, event=None):
  578. """Update update both screen samples with the font settings.
  579. Called on font initialization and change events.
  580. Accesses font_name, font_size, and font_bold Variables.
  581. Updates font_sample and highlight page highlight_sample.
  582. """
  583. font_name = self.font_name.get()
  584. font_weight = tkFont.BOLD if self.font_bold.get() else tkFont.NORMAL
  585. new_font = (font_name, self.font_size.get(), font_weight)
  586. self.font_sample['font'] = new_font
  587. self.highlight_sample['font'] = new_font
  588. def load_tab_cfg(self):
  589. """Load current configuration settings for the tab options.
  590. Attributes updated:
  591. space_num: Set to value from idleConf.
  592. """
  593. # Set indent sizes.
  594. space_num = idleConf.GetOption(
  595. 'main', 'Indent', 'num-spaces', default=4, type='int')
  596. self.space_num.set(space_num)
  597. def var_changed_space_num(self, *params):
  598. "Store change to indentation size."
  599. value = self.space_num.get()
  600. changes.add_option('main', 'Indent', 'num-spaces', value)
  601. class HighPage(Frame):
  602. def __init__(self, master):
  603. super().__init__(master)
  604. self.cd = master.master
  605. self.style = Style(master)
  606. self.create_page_highlight()
  607. self.load_theme_cfg()
  608. def create_page_highlight(self):
  609. """Return frame of widgets for Highlighting tab.
  610. Enable users to provisionally change foreground and background
  611. colors applied to textual tags. Color mappings are stored in
  612. complete listings called themes. Built-in themes in
  613. idlelib/config-highlight.def are fixed as far as the dialog is
  614. concerned. Any theme can be used as the base for a new custom
  615. theme, stored in .idlerc/config-highlight.cfg.
  616. Function load_theme_cfg() initializes tk variables and theme
  617. lists and calls paint_theme_sample() and set_highlight_target()
  618. for the current theme. Radiobuttons builtin_theme_on and
  619. custom_theme_on toggle var theme_source, which controls if the
  620. current set of colors are from a builtin or custom theme.
  621. DynOptionMenus builtinlist and customlist contain lists of the
  622. builtin and custom themes, respectively, and the current item
  623. from each list is stored in vars builtin_name and custom_name.
  624. Function paint_theme_sample() applies the colors from the theme
  625. to the tags in text widget highlight_sample and then invokes
  626. set_color_sample(). Function set_highlight_target() sets the state
  627. of the radiobuttons fg_on and bg_on based on the tag and it also
  628. invokes set_color_sample().
  629. Function set_color_sample() sets the background color for the frame
  630. holding the color selector. This provides a larger visual of the
  631. color for the current tag and plane (foreground/background).
  632. Note: set_color_sample() is called from many places and is often
  633. called more than once when a change is made. It is invoked when
  634. foreground or background is selected (radiobuttons), from
  635. paint_theme_sample() (theme is changed or load_cfg is called), and
  636. from set_highlight_target() (target tag is changed or load_cfg called).
  637. Button delete_custom invokes delete_custom() to delete
  638. a custom theme from idleConf.userCfg['highlight'] and changes.
  639. Button save_custom invokes save_as_new_theme() which calls
  640. get_new_theme_name() and create_new() to save a custom theme
  641. and its colors to idleConf.userCfg['highlight'].
  642. Radiobuttons fg_on and bg_on toggle var fg_bg_toggle to control
  643. if the current selected color for a tag is for the foreground or
  644. background.
  645. DynOptionMenu targetlist contains a readable description of the
  646. tags applied to Python source within IDLE. Selecting one of the
  647. tags from this list populates highlight_target, which has a callback
  648. function set_highlight_target().
  649. Text widget highlight_sample displays a block of text (which is
  650. mock Python code) in which is embedded the defined tags and reflects
  651. the color attributes of the current theme and changes for those tags.
  652. Mouse button 1 allows for selection of a tag and updates
  653. highlight_target with that tag value.
  654. Note: The font in highlight_sample is set through the config in
  655. the fonts tab.
  656. In other words, a tag can be selected either from targetlist or
  657. by clicking on the sample text within highlight_sample. The
  658. plane (foreground/background) is selected via the radiobutton.
  659. Together, these two (tag and plane) control what color is
  660. shown in set_color_sample() for the current theme. Button set_color
  661. invokes get_color() which displays a ColorChooser to change the
  662. color for the selected tag/plane. If a new color is picked,
  663. it will be saved to changes and the highlight_sample and
  664. frame background will be updated.
  665. Tk Variables:
  666. color: Color of selected target.
  667. builtin_name: Menu variable for built-in theme.
  668. custom_name: Menu variable for custom theme.
  669. fg_bg_toggle: Toggle for foreground/background color.
  670. Note: this has no callback.
  671. theme_source: Selector for built-in or custom theme.
  672. highlight_target: Menu variable for the highlight tag target.
  673. Instance Data Attributes:
  674. theme_elements: Dictionary of tags for text highlighting.
  675. The key is the display name and the value is a tuple of
  676. (tag name, display sort order).
  677. Methods [attachment]:
  678. load_theme_cfg: Load current highlight colors.
  679. get_color: Invoke colorchooser [button_set_color].
  680. set_color_sample_binding: Call set_color_sample [fg_bg_toggle].
  681. set_highlight_target: set fg_bg_toggle, set_color_sample().
  682. set_color_sample: Set frame background to target.
  683. on_new_color_set: Set new color and add option.
  684. paint_theme_sample: Recolor sample.
  685. get_new_theme_name: Get from popup.
  686. create_new: Combine theme with changes and save.
  687. save_as_new_theme: Save [button_save_custom].
  688. set_theme_type: Command for [theme_source].
  689. delete_custom: Activate default [button_delete_custom].
  690. save_new: Save to userCfg['theme'] (is function).
  691. Widgets of highlights page frame: (*) widgets bound to self
  692. frame_custom: LabelFrame
  693. (*)highlight_sample: Text
  694. (*)frame_color_set: Frame
  695. (*)button_set_color: Button
  696. (*)targetlist: DynOptionMenu - highlight_target
  697. frame_fg_bg_toggle: Frame
  698. (*)fg_on: Radiobutton - fg_bg_toggle
  699. (*)bg_on: Radiobutton - fg_bg_toggle
  700. (*)button_save_custom: Button
  701. frame_theme: LabelFrame
  702. theme_type_title: Label
  703. (*)builtin_theme_on: Radiobutton - theme_source
  704. (*)custom_theme_on: Radiobutton - theme_source
  705. (*)builtinlist: DynOptionMenu - builtin_name
  706. (*)customlist: DynOptionMenu - custom_name
  707. (*)button_delete_custom: Button
  708. (*)theme_message: Label
  709. """
  710. self.theme_elements = {
  711. 'Normal Code or Text': ('normal', '00'),
  712. 'Code Context': ('context', '01'),
  713. 'Python Keywords': ('keyword', '02'),
  714. 'Python Definitions': ('definition', '03'),
  715. 'Python Builtins': ('builtin', '04'),
  716. 'Python Comments': ('comment', '05'),
  717. 'Python Strings': ('string', '06'),
  718. 'Selected Text': ('hilite', '07'),
  719. 'Found Text': ('hit', '08'),
  720. 'Cursor': ('cursor', '09'),
  721. 'Editor Breakpoint': ('break', '10'),
  722. 'Shell Prompt': ('console', '11'),
  723. 'Error Text': ('error', '12'),
  724. 'Shell User Output': ('stdout', '13'),
  725. 'Shell User Exception': ('stderr', '14'),
  726. 'Line Number': ('linenumber', '16'),
  727. }
  728. self.builtin_name = tracers.add(
  729. StringVar(self), self.var_changed_builtin_name)
  730. self.custom_name = tracers.add(
  731. StringVar(self), self.var_changed_custom_name)
  732. self.fg_bg_toggle = BooleanVar(self)
  733. self.color = tracers.add(
  734. StringVar(self), self.var_changed_color)
  735. self.theme_source = tracers.add(
  736. BooleanVar(self), self.var_changed_theme_source)
  737. self.highlight_target = tracers.add(
  738. StringVar(self), self.var_changed_highlight_target)
  739. # Create widgets:
  740. # body frame and section frames.
  741. frame_custom = LabelFrame(self, borderwidth=2, relief=GROOVE,
  742. text=' Custom Highlighting ')
  743. frame_theme = LabelFrame(self, borderwidth=2, relief=GROOVE,
  744. text=' Highlighting Theme ')
  745. # frame_custom.
  746. sample_frame = ScrollableTextFrame(
  747. frame_custom, relief=SOLID, borderwidth=1)
  748. text = self.highlight_sample = sample_frame.text
  749. text.configure(
  750. font=('courier', 12, ''), cursor='hand2', width=1, height=1,
  751. takefocus=FALSE, highlightthickness=0, wrap=NONE)
  752. # Prevent perhaps invisible selection of word or slice.
  753. text.bind('<Double-Button-1>', lambda e: 'break')
  754. text.bind('<B1-Motion>', lambda e: 'break')
  755. string_tags=(
  756. ('# Click selects item.', 'comment'), ('\n', 'normal'),
  757. ('code context section', 'context'), ('\n', 'normal'),
  758. ('| cursor', 'cursor'), ('\n', 'normal'),
  759. ('def', 'keyword'), (' ', 'normal'),
  760. ('func', 'definition'), ('(param):\n ', 'normal'),
  761. ('"Return None."', 'string'), ('\n var0 = ', 'normal'),
  762. ("'string'", 'string'), ('\n var1 = ', 'normal'),
  763. ("'selected'", 'hilite'), ('\n var2 = ', 'normal'),
  764. ("'found'", 'hit'), ('\n var3 = ', 'normal'),
  765. ('list', 'builtin'), ('(', 'normal'),
  766. ('None', 'keyword'), (')\n', 'normal'),
  767. (' breakpoint("line")', 'break'), ('\n\n', 'normal'),
  768. ('>>>', 'console'), (' 3.14**2\n', 'normal'),
  769. ('9.8596', 'stdout'), ('\n', 'normal'),
  770. ('>>>', 'console'), (' pri ', 'normal'),
  771. ('n', 'error'), ('t(\n', 'normal'),
  772. ('SyntaxError', 'stderr'), ('\n', 'normal'))
  773. for string, tag in string_tags:
  774. text.insert(END, string, tag)
  775. n_lines = len(text.get('1.0', END).splitlines())
  776. for lineno in range(1, n_lines):
  777. text.insert(f'{lineno}.0',
  778. f'{lineno:{len(str(n_lines))}d} ',
  779. 'linenumber')
  780. for element in self.theme_elements:
  781. def tem(event, elem=element):
  782. # event.widget.winfo_top_level().highlight_target.set(elem)
  783. self.highlight_target.set(elem)
  784. text.tag_bind(
  785. self.theme_elements[element][0], '<ButtonPress-1>', tem)
  786. text['state'] = 'disabled'
  787. self.style.configure('frame_color_set.TFrame', borderwidth=1,
  788. relief='solid')
  789. self.frame_color_set = Frame(frame_custom, style='frame_color_set.TFrame')
  790. frame_fg_bg_toggle = Frame(frame_custom)
  791. self.button_set_color = Button(
  792. self.frame_color_set, text='Choose Color for :',
  793. command=self.get_color)
  794. self.targetlist = DynOptionMenu(
  795. self.frame_color_set, self.highlight_target, None,
  796. highlightthickness=0) #, command=self.set_highlight_targetBinding
  797. self.fg_on = Radiobutton(
  798. frame_fg_bg_toggle, variable=self.fg_bg_toggle, value=1,
  799. text='Foreground', command=self.set_color_sample_binding)
  800. self.bg_on = Radiobutton(
  801. frame_fg_bg_toggle, variable=self.fg_bg_toggle, value=0,
  802. text='Background', command=self.set_color_sample_binding)
  803. self.fg_bg_toggle.set(1)
  804. self.button_save_custom = Button(
  805. frame_custom, text='Save as New Custom Theme',
  806. command=self.save_as_new_theme)
  807. # frame_theme.
  808. theme_type_title = Label(frame_theme, text='Select : ')
  809. self.builtin_theme_on = Radiobutton(
  810. frame_theme, variable=self.theme_source, value=1,
  811. command=self.set_theme_type, text='a Built-in Theme')
  812. self.custom_theme_on = Radiobutton(
  813. frame_theme, variable=self.theme_source, value=0,
  814. command=self.set_theme_type, text='a Custom Theme')
  815. self.builtinlist = DynOptionMenu(
  816. frame_theme, self.builtin_name, None, command=None)
  817. self.customlist = DynOptionMenu(
  818. frame_theme, self.custom_name, None, command=None)
  819. self.button_delete_custom = Button(
  820. frame_theme, text='Delete Custom Theme',
  821. command=self.delete_custom)
  822. self.theme_message = Label(frame_theme, borderwidth=2)
  823. # Pack widgets:
  824. # body.
  825. frame_custom.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
  826. frame_theme.pack(side=TOP, padx=5, pady=5, fill=X)
  827. # frame_custom.
  828. self.frame_color_set.pack(side=TOP, padx=5, pady=5, fill=X)
  829. frame_fg_bg_toggle.pack(side=TOP, padx=5, pady=0)
  830. sample_frame.pack(
  831. side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
  832. self.button_set_color.pack(side=TOP, expand=TRUE, fill=X, padx=8, pady=4)
  833. self.targetlist.pack(side=TOP, expand=TRUE, fill=X, padx=8, pady=3)
  834. self.fg_on.pack(side=LEFT, anchor=E)
  835. self.bg_on.pack(side=RIGHT, anchor=W)
  836. self.button_save_custom.pack(side=BOTTOM, fill=X, padx=5, pady=5)
  837. # frame_theme.
  838. theme_type_title.pack(side=TOP, anchor=W, padx=5, pady=5)
  839. self.builtin_theme_on.pack(side=TOP, anchor=W, padx=5)
  840. self.custom_theme_on.pack(side=TOP, anchor=W, padx=5, pady=2)
  841. self.builtinlist.pack(side=TOP, fill=X, padx=5, pady=5)
  842. self.customlist.pack(side=TOP, fill=X, anchor=W, padx=5, pady=5)
  843. self.button_delete_custom.pack(side=TOP, fill=X, padx=5, pady=5)
  844. self.theme_message.pack(side=TOP, fill=X, pady=5)
  845. def load_theme_cfg(self):
  846. """Load current configuration settings for the theme options.
  847. Based on the theme_source toggle, the theme is set as
  848. either builtin or custom and the initial widget values
  849. reflect the current settings from idleConf.
  850. Attributes updated:
  851. theme_source: Set from idleConf.
  852. builtinlist: List of default themes from idleConf.
  853. customlist: List of custom themes from idleConf.
  854. custom_theme_on: Disabled if there are no custom themes.
  855. custom_theme: Message with additional information.
  856. targetlist: Create menu from self.theme_elements.
  857. Methods:
  858. set_theme_type
  859. paint_theme_sample
  860. set_highlight_target
  861. """
  862. # Set current theme type radiobutton.
  863. self.theme_source.set(idleConf.GetOption(
  864. 'main', 'Theme', 'default', type='bool', default=1))
  865. # Set current theme.
  866. current_option = idleConf.CurrentTheme()
  867. # Load available theme option menus.
  868. if self.theme_source.get(): # Default theme selected.
  869. item_list = idleConf.GetSectionList('default', 'highlight')
  870. item_list.sort()
  871. self.builtinlist.SetMenu(item_list, current_option)
  872. item_list = idleConf.GetSectionList('user', 'highlight')
  873. item_list.sort()
  874. if not item_list:
  875. self.custom_theme_on.state(('disabled',))
  876. self.custom_name.set('- no custom themes -')
  877. else:
  878. self.customlist.SetMenu(item_list, item_list[0])
  879. else: # User theme selected.
  880. item_list = idleConf.GetSectionList('user', 'highlight')
  881. item_list.sort()
  882. self.customlist.SetMenu(item_list, current_option)
  883. item_list = idleConf.GetSectionList('default', 'highlight')
  884. item_list.sort()
  885. self.builtinlist.SetMenu(item_list, item_list[0])
  886. self.set_theme_type()
  887. # Load theme element option menu.
  888. theme_names = list(self.theme_elements.keys())
  889. theme_names.sort(key=lambda x: self.theme_elements[x][1])
  890. self.targetlist.SetMenu(theme_names, theme_names[0])
  891. self.paint_theme_sample()
  892. self.set_highlight_target()
  893. def var_changed_builtin_name(self, *params):
  894. """Process new builtin theme selection.
  895. Add the changed theme's name to the changed_items and recreate
  896. the sample with the values from the selected theme.
  897. """
  898. old_themes = ('IDLE Classic', 'IDLE New')
  899. value = self.builtin_name.get()
  900. if value not in old_themes:
  901. if idleConf.GetOption('main', 'Theme', 'name') not in old_themes:
  902. changes.add_option('main', 'Theme', 'name', old_themes[0])
  903. changes.add_option('main', 'Theme', 'name2', value)
  904. self.theme_message['text'] = 'New theme, see Help'
  905. else:
  906. changes.add_option('main', 'Theme', 'name', value)
  907. changes.add_option('main', 'Theme', 'name2', '')
  908. self.theme_message['text'] = ''
  909. self.paint_theme_sample()
  910. def var_changed_custom_name(self, *params):
  911. """Process new custom theme selection.
  912. If a new custom theme is selected, add the name to the
  913. changed_items and apply the theme to the sample.
  914. """
  915. value = self.custom_name.get()
  916. if value != '- no custom themes -':
  917. changes.add_option('main', 'Theme', 'name', value)
  918. self.paint_theme_sample()
  919. def var_changed_theme_source(self, *params):
  920. """Process toggle between builtin and custom theme.
  921. Update the default toggle value and apply the newly
  922. selected theme type.
  923. """
  924. value = self.theme_source.get()
  925. changes.add_option('main', 'Theme', 'default', value)
  926. if value:
  927. self.var_changed_builtin_name()
  928. else:
  929. self.var_changed_custom_name()
  930. def var_changed_color(self, *params):
  931. "Process change to color choice."
  932. self.on_new_color_set()
  933. def var_changed_highlight_target(self, *params):
  934. "Process selection of new target tag for highlighting."
  935. self.set_highlight_target()
  936. def set_theme_type(self):
  937. """Set available screen options based on builtin or custom theme.
  938. Attributes accessed:
  939. theme_source
  940. Attributes updated:
  941. builtinlist
  942. customlist
  943. button_delete_custom
  944. custom_theme_on
  945. Called from:
  946. handler for builtin_theme_on and custom_theme_on
  947. delete_custom
  948. create_new
  949. load_theme_cfg
  950. """
  951. if self.theme_source.get():
  952. self.builtinlist['state'] = 'normal'
  953. self.customlist['state'] = 'disabled'
  954. self.button_delete_custom.state(('disabled',))
  955. else:
  956. self.builtinlist['state'] = 'disabled'
  957. self.custom_theme_on.state(('!disabled',))
  958. self.customlist['state'] = 'normal'
  959. self.button_delete_custom.state(('!disabled',))
  960. def get_color(self):
  961. """Handle button to select a new color for the target tag.
  962. If a new color is selected while using a builtin theme, a
  963. name must be supplied to create a custom theme.
  964. Attributes accessed:
  965. highlight_target
  966. frame_color_set
  967. theme_source
  968. Attributes updated:
  969. color
  970. Methods:
  971. get_new_theme_name
  972. create_new
  973. """
  974. target = self.highlight_target.get()
  975. prev_color = self.style.lookup(self.frame_color_set['style'],
  976. 'background')
  977. rgbTuplet, color_string = tkColorChooser.askcolor(
  978. parent=self, title='Pick new color for : '+target,
  979. initialcolor=prev_color)
  980. if color_string and (color_string != prev_color):
  981. # User didn't cancel and they chose a new color.
  982. if self.theme_source.get(): # Current theme is a built-in.
  983. message = ('Your changes will be saved as a new Custom Theme. '
  984. 'Enter a name for your new Custom Theme below.')
  985. new_theme = self.get_new_theme_name(message)
  986. if not new_theme: # User cancelled custom theme creation.
  987. return
  988. else: # Create new custom theme based on previously active theme.
  989. self.create_new(new_theme)
  990. self.color.set(color_string)
  991. else: # Current theme is user defined.
  992. self.color.set(color_string)
  993. def on_new_color_set(self):
  994. "Display sample of new color selection on the dialog."
  995. new_color = self.color.get()
  996. self.style.configure('frame_color_set.TFrame', background=new_color)
  997. plane = 'foreground' if self.fg_bg_toggle.get() else 'background'
  998. sample_element = self.theme_elements[self.highlight_target.get()][0]
  999. self.highlight_sample.tag_config(sample_element, **{plane: new_color})
  1000. theme = self.custom_name.get()
  1001. theme_element = sample_element + '-' + plane
  1002. changes.add_option('highlight', theme, theme_element, new_color)
  1003. def get_new_theme_name(self, message):
  1004. "Return name of new theme from query popup."
  1005. used_names = (idleConf.GetSectionList('user', 'highlight') +
  1006. idleConf.GetSectionList('default', 'highlight'))
  1007. new_theme = SectionName(
  1008. self, 'New Custom Theme', message, used_names).result
  1009. return new_theme
  1010. def save_as_new_theme(self):
  1011. """Prompt for new theme name and create the theme.
  1012. Methods:
  1013. get_new_theme_name
  1014. create_new
  1015. """
  1016. new_theme_name = self.get_new_theme_name('New Theme Name:')
  1017. if new_theme_name:
  1018. self.create_new(new_theme_name)
  1019. def create_new(self, new_theme_name):
  1020. """Create a new custom theme with the given name.
  1021. Create the new theme based on the previously active theme
  1022. with the current changes applied. Once it is saved, then
  1023. activate the new theme.
  1024. Attributes accessed:
  1025. builtin_name
  1026. custom_name
  1027. Attributes updated:
  1028. customlist
  1029. theme_source
  1030. Method:
  1031. save_new
  1032. set_theme_type
  1033. """
  1034. if self.theme_source.get():
  1035. theme_type = 'default'
  1036. theme_name = self.builtin_name.get()
  1037. else:
  1038. theme_type = 'user'
  1039. theme_name = self.custom_name.get()
  1040. new_theme = idleConf.GetThemeDict(theme_type, theme_name)
  1041. # Apply any of the old theme's unsaved changes to the new theme.
  1042. if theme_name in changes['highlight']:
  1043. theme_changes = changes['highlight'][theme_name]
  1044. for element in theme_changes:
  1045. new_theme[element] = theme_changes[element]
  1046. # Save the new theme.
  1047. self.save_new(new_theme_name, new_theme)
  1048. # Change GUI over to the new theme.
  1049. custom_theme_list = idleConf.GetSectionList('user', 'highlight')
  1050. custom_theme_list.sort()
  1051. self.customlist.SetMenu(custom_theme_list, new_theme_name)
  1052. self.theme_source.set(0)
  1053. self.set_theme_type()
  1054. def set_highlight_target(self):
  1055. """Set fg/bg toggle and color based on highlight tag target.
  1056. Instance variables accessed:
  1057. highlight_target
  1058. Attributes updated:
  1059. fg_on
  1060. bg_on
  1061. fg_bg_toggle
  1062. Methods:
  1063. set_color_sample
  1064. Called from:
  1065. var_changed_highlight_target
  1066. load_theme_cfg
  1067. """
  1068. if self.highlight_target.get() == 'Cursor': # bg not possible
  1069. self.fg_on.state(('disabled',))
  1070. self.bg_on.state(('disabled',))
  1071. self.fg_bg_toggle.set(1)
  1072. else: # Both fg and bg can be set.
  1073. self.fg_on.state(('!disabled',))
  1074. self.bg_on.state(('!disabled',))
  1075. self.fg_bg_toggle.set(1)
  1076. self.set_color_sample()
  1077. def set_color_sample_binding(self, *args):
  1078. """Change color sample based on foreground/background toggle.
  1079. Methods:
  1080. set_color_sample
  1081. """
  1082. self.set_color_sample()
  1083. def set_color_sample(self):
  1084. """Set the color of the frame background to reflect the selected target.
  1085. Instance variables accessed:
  1086. theme_elements
  1087. highlight_target
  1088. fg_bg_toggle
  1089. highlight_sample
  1090. Attributes updated:
  1091. frame_color_set
  1092. """
  1093. # Set the color sample area.
  1094. tag = self.theme_elements[self.highlight_target.get()][0]
  1095. plane = 'foreground' if self.fg_bg_toggle.get() else 'background'
  1096. color = self.highlight_sample.tag_cget(tag, plane)
  1097. self.style.configure('frame_color_set.TFrame', background=color)
  1098. def paint_theme_sample(self):
  1099. """Apply the theme colors to each element tag in the sample text.
  1100. Instance attributes accessed:
  1101. theme_elements
  1102. theme_source
  1103. builtin_name
  1104. custom_name
  1105. Attributes updated:
  1106. highlight_sample: Set the tag elements to the theme.
  1107. Methods:
  1108. set_color_sample
  1109. Called from:
  1110. var_changed_builtin_name
  1111. var_changed_custom_name
  1112. load_theme_cfg
  1113. """
  1114. if self.theme_source.get(): # Default theme
  1115. theme = self.builtin_name.get()
  1116. else: # User theme
  1117. theme = self.custom_name.get()
  1118. for element_title in self.theme_elements:
  1119. element = self.theme_elements[element_title][0]
  1120. colors = idleConf.GetHighlight(theme, element)
  1121. if element == 'cursor': # Cursor sample needs special painting.
  1122. colors['background'] = idleConf.GetHighlight(
  1123. theme, 'normal')['background']
  1124. # Handle any unsaved changes to this theme.
  1125. if theme in changes['highlight']:
  1126. theme_dict = changes['highlight'][theme]
  1127. if element + '-foreground' in theme_dict:
  1128. colors['foreground'] = theme_dict[element + '-foreground']
  1129. if element + '-background' in theme_dict:
  1130. colors['background'] = theme_dict[element + '-background']
  1131. self.highlight_sample.tag_config(element, **colors)
  1132. self.set_color_sample()
  1133. def save_new(self, theme_name, theme):
  1134. """Save a newly created theme to idleConf.
  1135. theme_name - string, the name of the new theme
  1136. theme - dictionary containing the new theme
  1137. """
  1138. idleConf.userCfg['highlight'].AddSection(theme_name)
  1139. for element in theme:
  1140. value = theme[element]
  1141. idleConf.userCfg['highlight'].SetOption(theme_name, element, value)
  1142. def askyesno(self, *args, **kwargs):
  1143. # Make testing easier. Could change implementation.
  1144. return messagebox.askyesno(*args, **kwargs)
  1145. def delete_custom(self):
  1146. """Handle event to delete custom theme.
  1147. The current theme is deactivated and the default theme is
  1148. activated. The custom theme is permanently removed from
  1149. the config file.
  1150. Attributes accessed:
  1151. custom_name
  1152. Attributes updated:
  1153. custom_theme_on
  1154. customlist
  1155. theme_source
  1156. builtin_name
  1157. Methods:
  1158. deactivate_current_config
  1159. save_all_changed_extensions
  1160. activate_config_changes
  1161. set_theme_type
  1162. """
  1163. theme_name = self.custom_name.get()
  1164. delmsg = 'Are you sure you wish to delete the theme %r ?'
  1165. if not self.askyesno(
  1166. 'Delete Theme', delmsg % theme_name, parent=self):
  1167. return
  1168. self.cd.deactivate_current_config()
  1169. # Remove theme from changes, config, and file.
  1170. changes.delete_section('highlight', theme_name)
  1171. # Reload user theme list.
  1172. item_list = idleConf.GetSectionList('user', 'highlight')
  1173. item_list.sort()
  1174. if not item_list:
  1175. self.custom_theme_on.state(('disabled',))
  1176. self.customlist.SetMenu(item_list, '- no custom themes -')
  1177. else:
  1178. self.customlist.SetMenu(item_list, item_list[0])
  1179. # Revert to default theme.
  1180. self.theme_source.set(idleConf.defaultCfg['main'].Get('Theme', 'default'))
  1181. self.builtin_name.set(idleConf.defaultCfg['main'].Get('Theme', 'name'))
  1182. # User can't back out of these changes, they must be applied now.
  1183. changes.save_all()
  1184. self.cd.save_all_changed_extensions()
  1185. self.cd.activate_config_changes()
  1186. self.set_theme_type()
  1187. class KeysPage(Frame):
  1188. def __init__(self, master):
  1189. super().__init__(master)
  1190. self.cd = master.master
  1191. self.create_page_keys()
  1192. self.load_key_cfg()
  1193. def create_page_keys(self):
  1194. """Return frame of widgets for Keys tab.
  1195. Enable users to provisionally change both individual and sets of
  1196. keybindings (shortcut keys). Except for features implemented as
  1197. extensions, keybindings are stored in complete sets called
  1198. keysets. Built-in keysets in idlelib/config-keys.def are fixed
  1199. as far as the dialog is concerned. Any keyset can be used as the
  1200. base for a new custom keyset, stored in .idlerc/config-keys.cfg.
  1201. Function load_key_cfg() initializes tk variables and keyset
  1202. lists and calls load_keys_list for the current keyset.
  1203. Radiobuttons builtin_keyset_on and custom_keyset_on toggle var
  1204. keyset_source, which controls if the current set of keybindings
  1205. are from a builtin or custom keyset. DynOptionMenus builtinlist
  1206. and customlist contain lists of the builtin and custom keysets,
  1207. respectively, and the current item from each list is stored in
  1208. vars builtin_name and custom_name.
  1209. Button delete_custom_keys invokes delete_custom_keys() to delete
  1210. a custom keyset from idleConf.userCfg['keys'] and changes. Button
  1211. save_custom_keys invokes save_as_new_key_set() which calls
  1212. get_new_keys_name() and create_new_key_set() to save a custom keyset
  1213. and its keybindings to idleConf.userCfg['keys'].
  1214. Listbox bindingslist contains all of the keybindings for the
  1215. selected keyset. The keybindings are loaded in load_keys_list()
  1216. and are pairs of (event, [keys]) where keys can be a list
  1217. of one or more key combinations to bind to the same event.
  1218. Mouse button 1 click invokes on_bindingslist_select(), which
  1219. allows button_new_keys to be clicked.
  1220. So, an item is selected in listbindings, which activates
  1221. button_new_keys, and clicking button_new_keys calls function
  1222. get_new_keys(). Function get_new_keys() gets the key mappings from the
  1223. current keyset for the binding event item that was selected. The
  1224. function then displays another dialog, GetKeysDialog, with the
  1225. selected binding event and current keys and allows new key sequences
  1226. to be entered for that binding event. If the keys aren't
  1227. changed, nothing happens. If the keys are changed and the keyset
  1228. is a builtin, function get_new_keys_name() will be called
  1229. for input of a custom keyset name. If no name is given, then the
  1230. change to the keybinding will abort and no updates will be made. If
  1231. a custom name is entered in the prompt or if the current keyset was
  1232. already custom (and thus didn't require a prompt), then
  1233. idleConf.userCfg['keys'] is updated in function create_new_key_set()
  1234. with the change to the event binding. The item listing in bindingslist
  1235. is updated with the new keys. Var keybinding is also set which invokes
  1236. the callback function, var_changed_keybinding, to add the change to
  1237. the 'keys' or 'extensions' changes tracker based on the binding type.
  1238. Tk Variables:
  1239. keybinding: Action/key bindings.
  1240. Methods:
  1241. load_keys_list: Reload active set.
  1242. create_new_key_set: Combine active keyset and changes.
  1243. set_keys_type: Command for keyset_source.
  1244. save_new_key_set: Save to idleConf.userCfg['keys'] (is function).
  1245. deactivate_current_config: Remove keys bindings in editors.
  1246. Widgets for KeysPage(frame): (*) widgets bound to self
  1247. frame_key_sets: LabelFrame
  1248. frames[0]: Frame
  1249. (*)builtin_keyset_on: Radiobutton - var keyset_source
  1250. (*)custom_keyset_on: Radiobutton - var keyset_source
  1251. (*)builtinlist: DynOptionMenu - var builtin_name,
  1252. func keybinding_selected
  1253. (*)customlist: DynOptionMenu - var custom_name,
  1254. func keybinding_selected
  1255. (*)keys_message: Label
  1256. frames[1]: Frame
  1257. (*)button_delete_custom_keys: Button - delete_custom_keys
  1258. (*)button_save_custom_keys: Button - save_as_new_key_set
  1259. frame_custom: LabelFrame
  1260. frame_target: Frame
  1261. target_title: Label
  1262. scroll_target_y: Scrollbar
  1263. scroll_target_x: Scrollbar
  1264. (*)bindingslist: ListBox - on_bindingslist_select
  1265. (*)button_new_keys: Button - get_new_keys & ..._name
  1266. """
  1267. self.builtin_name = tracers.add(
  1268. StringVar(self), self.var_changed_builtin_name)
  1269. self.custom_name = tracers.add(
  1270. StringVar(self), self.var_changed_custom_name)
  1271. self.keyset_source = tracers.add(
  1272. BooleanVar(self), self.var_changed_keyset_source)
  1273. self.keybinding = tracers.add(
  1274. StringVar(self), self.var_changed_keybinding)
  1275. # Create widgets:
  1276. # body and section frames.
  1277. frame_custom = LabelFrame(
  1278. self, borderwidth=2, relief=GROOVE,
  1279. text=' Custom Key Bindings ')
  1280. frame_key_sets = LabelFrame(
  1281. self, borderwidth=2, relief=GROOVE, text=' Key Set ')
  1282. # frame_custom.
  1283. frame_target = Frame(frame_custom)
  1284. target_title = Label(frame_target, text='Action - Key(s)')
  1285. scroll_target_y = Scrollbar(frame_target)
  1286. scroll_target_x = Scrollbar(frame_target, orient=HORIZONTAL)
  1287. self.bindingslist = Listbox(
  1288. frame_target, takefocus=FALSE, exportselection=FALSE)
  1289. self.bindingslist.bind('<ButtonRelease-1>',
  1290. self.on_bindingslist_select)
  1291. scroll_target_y['command'] = self.bindingslist.yview
  1292. scroll_target_x['command'] = self.bindingslist.xview
  1293. self.bindingslist['yscrollcommand'] = scroll_target_y.set
  1294. self.bindingslist['xscrollcommand'] = scroll_target_x.set
  1295. self.button_new_keys = Button(
  1296. frame_custom, text='Get New Keys for Selection',
  1297. command=self.get_new_keys, state='disabled')
  1298. # frame_key_sets.
  1299. frames = [Frame(frame_key_sets, padding=2, borderwidth=0)
  1300. for i in range(2)]
  1301. self.builtin_keyset_on = Radiobutton(
  1302. frames[0], variable=self.keyset_source, value=1,
  1303. command=self.set_keys_type, text='Use a Built-in Key Set')
  1304. self.custom_keyset_on = Radiobutton(
  1305. frames[0], variable=self.keyset_source, value=0,
  1306. command=self.set_keys_type, text='Use a Custom Key Set')
  1307. self.builtinlist = DynOptionMenu(
  1308. frames[0], self.builtin_name, None, command=None)
  1309. self.customlist = DynOptionMenu(
  1310. frames[0], self.custom_name, None, command=None)
  1311. self.button_delete_custom_keys = Button(
  1312. frames[1], text='Delete Custom Key Set',
  1313. command=self.delete_custom_keys)
  1314. self.button_save_custom_keys = Button(
  1315. frames[1], text='Save as New Custom Key Set',
  1316. command=self.save_as_new_key_set)
  1317. self.keys_message = Label(frames[0], borderwidth=2)
  1318. # Pack widgets:
  1319. # body.
  1320. frame_custom.pack(side=BOTTOM, padx=5, pady=5, expand=TRUE, fill=BOTH)
  1321. frame_key_sets.pack(side=BOTTOM, padx=5, pady=5, fill=BOTH)
  1322. # frame_custom.
  1323. self.button_new_keys.pack(side=BOTTOM, fill=X, padx=5, pady=5)
  1324. frame_target.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
  1325. # frame_target.
  1326. frame_target.columnconfigure(0, weight=1)
  1327. frame_target.rowconfigure(1, weight=1)
  1328. target_title.grid(row=0, column=0, columnspan=2, sticky=W)
  1329. self.bindingslist.grid(row=1, column=0, sticky=NSEW)
  1330. scroll_target_y.grid(row=1, column=1, sticky=NS)
  1331. scroll_target_x.grid(row=2, column=0, sticky=EW)
  1332. # frame_key_sets.
  1333. self.builtin_keyset_on.grid(row=0, column=0, sticky=W+NS)
  1334. self.custom_keyset_on.grid(row=1, column=0, sticky=W+NS)
  1335. self.builtinlist.grid(row=0, column=1, sticky=NSEW)
  1336. self.customlist.grid(row=1, column=1, sticky=NSEW)
  1337. self.keys_message.grid(row=0, column=2, sticky=NSEW, padx=5, pady=5)
  1338. self.button_delete_custom_keys.pack(side=LEFT, fill=X, expand=True, padx=2)
  1339. self.button_save_custom_keys.pack(side=LEFT, fill=X, expand=True, padx=2)
  1340. frames[0].pack(side=TOP, fill=BOTH, expand=True)
  1341. frames[1].pack(side=TOP, fill=X, expand=True, pady=2)
  1342. def load_key_cfg(self):
  1343. "Load current configuration settings for the keybinding options."
  1344. # Set current keys type radiobutton.
  1345. self.keyset_source.set(idleConf.GetOption(
  1346. 'main', 'Keys', 'default', type='bool', default=1))
  1347. # Set current keys.
  1348. current_option = idleConf.CurrentKeys()
  1349. # Load available keyset option menus.
  1350. if self.keyset_source.get(): # Default theme selected.
  1351. item_list = idleConf.GetSectionList('default', 'keys')
  1352. item_list.sort()
  1353. self.builtinlist.SetMenu(item_list, current_option)
  1354. item_list = idleConf.GetSectionList('user', 'keys')
  1355. item_list.sort()
  1356. if not item_list:
  1357. self.custom_keyset_on.state(('disabled',))
  1358. self.custom_name.set('- no custom keys -')
  1359. else:
  1360. self.customlist.SetMenu(item_list, item_list[0])
  1361. else: # User key set selected.
  1362. item_list = idleConf.GetSectionList('user', 'keys')
  1363. item_list.sort()
  1364. self.customlist.SetMenu(item_list, current_option)
  1365. item_list = idleConf.GetSectionList('default', 'keys')
  1366. item_list.sort()
  1367. self.builtinlist.SetMenu(item_list, idleConf.default_keys())
  1368. self.set_keys_type()
  1369. # Load keyset element list.
  1370. keyset_name = idleConf.CurrentKeys()
  1371. self.load_keys_list(keyset_name)
  1372. def var_changed_builtin_name(self, *params):
  1373. "Process selection of builtin key set."
  1374. old_keys = (
  1375. 'IDLE Classic Windows',
  1376. 'IDLE Classic Unix',
  1377. 'IDLE Classic Mac',
  1378. 'IDLE Classic OSX',
  1379. )
  1380. value = self.builtin_name.get()
  1381. if value not in old_keys:
  1382. if idleConf.GetOption('main', 'Keys', 'name') not in old_keys:
  1383. changes.add_option('main', 'Keys', 'name', old_keys[0])
  1384. changes.add_option('main', 'Keys', 'name2', value)
  1385. self.keys_message['text'] = 'New key set, see Help'
  1386. else:
  1387. changes.add_option('main', 'Keys', 'name', value)
  1388. changes.add_option('main', 'Keys', 'name2', '')
  1389. self.keys_message['text'] = ''
  1390. self.load_keys_list(value)
  1391. def var_changed_custom_name(self, *params):
  1392. "Process selection of custom key set."
  1393. value = self.custom_name.get()
  1394. if value != '- no custom keys -':
  1395. changes.add_option('main', 'Keys', 'name', value)
  1396. self.load_keys_list(value)
  1397. def var_changed_keyset_source(self, *params):
  1398. "Process toggle between builtin key set and custom key set."
  1399. value = self.keyset_source.get()
  1400. changes.add_option('main', 'Keys', 'default', value)
  1401. if value:
  1402. self.var_changed_builtin_name()
  1403. else:
  1404. self.var_changed_custom_name()
  1405. def var_changed_keybinding(self, *params):
  1406. "Store change to a keybinding."
  1407. value = self.keybinding.get()
  1408. key_set = self.custom_name.get()
  1409. event = self.bindingslist.get(ANCHOR).split()[0]
  1410. if idleConf.IsCoreBinding(event):
  1411. changes.add_option('keys', key_set, event, value)
  1412. else: # Event is an extension binding.
  1413. ext_name = idleConf.GetExtnNameForEvent(event)
  1414. ext_keybind_section = ext_name + '_cfgBindings'
  1415. changes.add_option('extensions', ext_keybind_section, event, value)
  1416. def set_keys_type(self):
  1417. "Set available screen options based on builtin or custom key set."
  1418. if self.keyset_source.get():
  1419. self.builtinlist['state'] = 'normal'
  1420. self.customlist['state'] = 'disabled'
  1421. self.button_delete_custom_keys.state(('disabled',))
  1422. else:
  1423. self.builtinlist['state'] = 'disabled'
  1424. self.custom_keyset_on.state(('!disabled',))
  1425. self.customlist['state'] = 'normal'
  1426. self.button_delete_custom_keys.state(('!disabled',))
  1427. def get_new_keys(self):
  1428. """Handle event to change key binding for selected line.
  1429. A selection of a key/binding in the list of current
  1430. bindings pops up a dialog to enter a new binding. If
  1431. the current key set is builtin and a binding has
  1432. changed, then a name for a custom key set needs to be
  1433. entered for the change to be applied.
  1434. """
  1435. list_index = self.bindingslist.index(ANCHOR)
  1436. binding = self.bindingslist.get(list_index)
  1437. bind_name = binding.split()[0]
  1438. if self.keyset_source.get():
  1439. current_key_set_name = self.builtin_name.get()
  1440. else:
  1441. current_key_set_name = self.custom_name.get()
  1442. current_bindings = idleConf.GetCurrentKeySet()
  1443. if current_key_set_name in changes['keys']: # unsaved changes
  1444. key_set_changes = changes['keys'][current_key_set_name]
  1445. for event in key_set_changes:
  1446. current_bindings[event] = key_set_changes[event].split()
  1447. current_key_sequences = list(current_bindings.values())
  1448. new_keys = GetKeysDialog(self, 'Get New Keys', bind_name,
  1449. current_key_sequences).result
  1450. if new_keys:
  1451. if self.keyset_source.get(): # Current key set is a built-in.
  1452. message = ('Your changes will be saved as a new Custom Key Set.'
  1453. ' Enter a name for your new Custom Key Set below.')
  1454. new_keyset = self.get_new_keys_name(message)
  1455. if not new_keyset: # User cancelled custom key set creation.
  1456. self.bindingslist.select_set(list_index)
  1457. self.bindingslist.select_anchor(list_index)
  1458. return
  1459. else: # Create new custom key set based on previously active key set.
  1460. self.create_new_key_set(new_keyset)
  1461. self.bindingslist.delete(list_index)
  1462. self.bindingslist.insert(list_index, bind_name+' - '+new_keys)
  1463. self.bindingslist.select_set(list_index)
  1464. self.bindingslist.select_anchor(list_index)
  1465. self.keybinding.set(new_keys)
  1466. else:
  1467. self.bindingslist.select_set(list_index)
  1468. self.bindingslist.select_anchor(list_index)
  1469. def get_new_keys_name(self, message):
  1470. "Return new key set name from query popup."
  1471. used_names = (idleConf.GetSectionList('user', 'keys') +
  1472. idleConf.GetSectionList('default', 'keys'))
  1473. new_keyset = SectionName(
  1474. self, 'New Custom Key Set', message, used_names).result
  1475. return new_keyset
  1476. def save_as_new_key_set(self):
  1477. "Prompt for name of new key set and save changes using that name."
  1478. new_keys_name = self.get_new_keys_name('New Key Set Name:')
  1479. if new_keys_name:
  1480. self.create_new_key_set(new_keys_name)
  1481. def on_bindingslist_select(self, event):
  1482. "Activate button to assign new keys to selected action."
  1483. self.button_new_keys.state(('!disabled',))
  1484. def create_new_key_set(self, new_key_set_name):
  1485. """Create a new custom key set with the given name.
  1486. Copy the bindings/keys from the previously active keyset
  1487. to the new keyset and activate the new custom keyset.
  1488. """
  1489. if self.keyset_source.get():
  1490. prev_key_set_name = self.builtin_name.get()
  1491. else:
  1492. prev_key_set_name = self.custom_name.get()
  1493. prev_keys = idleConf.GetCoreKeys(prev_key_set_name)
  1494. new_keys = {}
  1495. for event in prev_keys: # Add key set to changed items.
  1496. event_name = event[2:-2] # Trim off the angle brackets.
  1497. binding = ' '.join(prev_keys[event])
  1498. new_keys[event_name] = binding
  1499. # Handle any unsaved changes to prev key set.
  1500. if prev_key_set_name in changes['keys']:
  1501. key_set_changes = changes['keys'][prev_key_set_name]
  1502. for event in key_set_changes:
  1503. new_keys[event] = key_set_changes[event]
  1504. # Save the new key set.
  1505. self.save_new_key_set(new_key_set_name, new_keys)
  1506. # Change GUI over to the new key set.
  1507. custom_key_list = idleConf.GetSectionList('user', 'keys')
  1508. custom_key_list.sort()
  1509. self.customlist.SetMenu(custom_key_list, new_key_set_name)
  1510. self.keyset_source.set(0)
  1511. self.set_keys_type()
  1512. def load_keys_list(self, keyset_name):
  1513. """Reload the list of action/key binding pairs for the active key set.
  1514. An action/key binding can be selected to change the key binding.
  1515. """
  1516. reselect = False
  1517. if self.bindingslist.curselection():
  1518. reselect = True
  1519. list_index = self.bindingslist.index(ANCHOR)
  1520. keyset = idleConf.GetKeySet(keyset_name)
  1521. bind_names = list(keyset.keys())
  1522. bind_names.sort()
  1523. self.bindingslist.delete(0, END)
  1524. for bind_name in bind_names:
  1525. key = ' '.join(keyset[bind_name])
  1526. bind_name = bind_name[2:-2] # Trim off the angle brackets.
  1527. if keyset_name in changes['keys']:
  1528. # Handle any unsaved changes to this key set.
  1529. if bind_name in changes['keys'][keyset_name]:
  1530. key = changes['keys'][keyset_name][bind_name]
  1531. self.bindingslist.insert(END, bind_name+' - '+key)
  1532. if reselect:
  1533. self.bindingslist.see(list_index)
  1534. self.bindingslist.select_set(list_index)
  1535. self.bindingslist.select_anchor(list_index)
  1536. @staticmethod
  1537. def save_new_key_set(keyset_name, keyset):
  1538. """Save a newly created core key set.
  1539. Add keyset to idleConf.userCfg['keys'], not to disk.
  1540. If the keyset doesn't exist, it is created. The
  1541. binding/keys are taken from the keyset argument.
  1542. keyset_name - string, the name of the new key set
  1543. keyset - dictionary containing the new keybindings
  1544. """
  1545. idleConf.userCfg['keys'].AddSection(keyset_name)
  1546. for event in keyset:
  1547. value = keyset[event]
  1548. idleConf.userCfg['keys'].SetOption(keyset_name, event, value)
  1549. def askyesno(self, *args, **kwargs):
  1550. # Make testing easier. Could change implementation.
  1551. return messagebox.askyesno(*args, **kwargs)
  1552. def delete_custom_keys(self):
  1553. """Handle event to delete a custom key set.
  1554. Applying the delete deactivates the current configuration and
  1555. reverts to the default. The custom key set is permanently
  1556. deleted from the config file.
  1557. """
  1558. keyset_name = self.custom_name.get()
  1559. delmsg = 'Are you sure you wish to delete the key set %r ?'
  1560. if not self.askyesno(
  1561. 'Delete Key Set', delmsg % keyset_name, parent=self):
  1562. return
  1563. self.cd.deactivate_current_config()
  1564. # Remove key set from changes, config, and file.
  1565. changes.delete_section('keys', keyset_name)
  1566. # Reload user key set list.
  1567. item_list = idleConf.GetSectionList('user', 'keys')
  1568. item_list.sort()
  1569. if not item_list:
  1570. self.custom_keyset_on.state(('disabled',))
  1571. self.customlist.SetMenu(item_list, '- no custom keys -')
  1572. else:
  1573. self.customlist.SetMenu(item_list, item_list[0])
  1574. # Revert to default key set.
  1575. self.keyset_source.set(idleConf.defaultCfg['main']
  1576. .Get('Keys', 'default'))
  1577. self.builtin_name.set(idleConf.defaultCfg['main'].Get('Keys', 'name')
  1578. or idleConf.default_keys())
  1579. # User can't back out of these changes, they must be applied now.
  1580. changes.save_all()
  1581. self.cd.save_all_changed_extensions()
  1582. self.cd.activate_config_changes()
  1583. self.set_keys_type()
  1584. class GenPage(Frame):
  1585. def __init__(self, master):
  1586. super().__init__(master)
  1587. self.init_validators()
  1588. self.create_page_general()
  1589. self.load_general_cfg()
  1590. def init_validators(self):
  1591. digits_or_empty_re = re.compile(r'[0-9]*')
  1592. def is_digits_or_empty(s):
  1593. "Return 's is blank or contains only digits'"
  1594. return digits_or_empty_re.fullmatch(s) is not None
  1595. self.digits_only = (self.register(is_digits_or_empty), '%P',)
  1596. def create_page_general(self):
  1597. """Return frame of widgets for General tab.
  1598. Enable users to provisionally change general options. Function
  1599. load_general_cfg initializes tk variables and helplist using
  1600. idleConf. Radiobuttons startup_shell_on and startup_editor_on
  1601. set var startup_edit. Radiobuttons save_ask_on and save_auto_on
  1602. set var autosave. Entry boxes win_width_int and win_height_int
  1603. set var win_width and win_height. Setting var_name invokes the
  1604. default callback that adds option to changes.
  1605. Helplist: load_general_cfg loads list user_helplist with
  1606. name, position pairs and copies names to listbox helplist.
  1607. Clicking a name invokes help_source selected. Clicking
  1608. button_helplist_name invokes helplist_item_name, which also
  1609. changes user_helplist. These functions all call
  1610. set_add_delete_state. All but load call update_help_changes to
  1611. rewrite changes['main']['HelpFiles'].
  1612. Widgets for GenPage(Frame): (*) widgets bound to self
  1613. frame_window: LabelFrame
  1614. frame_run: Frame
  1615. startup_title: Label
  1616. (*)startup_editor_on: Radiobutton - startup_edit
  1617. (*)startup_shell_on: Radiobutton - startup_edit
  1618. frame_win_size: Frame
  1619. win_size_title: Label
  1620. win_width_title: Label
  1621. (*)win_width_int: Entry - win_width
  1622. win_height_title: Label
  1623. (*)win_height_int: Entry - win_height
  1624. frame_cursor_blink: Frame
  1625. cursor_blink_title: Label
  1626. (*)cursor_blink_bool: Checkbutton - cursor_blink
  1627. frame_autocomplete: Frame
  1628. auto_wait_title: Label
  1629. (*)auto_wait_int: Entry - autocomplete_wait
  1630. frame_paren1: Frame
  1631. paren_style_title: Label
  1632. (*)paren_style_type: OptionMenu - paren_style
  1633. frame_paren2: Frame
  1634. paren_time_title: Label
  1635. (*)paren_flash_time: Entry - flash_delay
  1636. (*)bell_on: Checkbutton - paren_bell
  1637. frame_editor: LabelFrame
  1638. frame_save: Frame
  1639. run_save_title: Label
  1640. (*)save_ask_on: Radiobutton - autosave
  1641. (*)save_auto_on: Radiobutton - autosave
  1642. frame_format: Frame
  1643. format_width_title: Label
  1644. (*)format_width_int: Entry - format_width
  1645. frame_line_numbers_default: Frame
  1646. line_numbers_default_title: Label
  1647. (*)line_numbers_default_bool: Checkbutton - line_numbers_default
  1648. frame_context: Frame
  1649. context_title: Label
  1650. (*)context_int: Entry - context_lines
  1651. frame_shell: LabelFrame
  1652. frame_auto_squeeze_min_lines: Frame
  1653. auto_squeeze_min_lines_title: Label
  1654. (*)auto_squeeze_min_lines_int: Entry - auto_squeeze_min_lines
  1655. frame_help: LabelFrame
  1656. frame_helplist: Frame
  1657. frame_helplist_buttons: Frame
  1658. (*)button_helplist_edit
  1659. (*)button_helplist_add
  1660. (*)button_helplist_remove
  1661. (*)helplist: ListBox
  1662. scroll_helplist: Scrollbar
  1663. """
  1664. # Integer values need StringVar because int('') raises.
  1665. self.startup_edit = tracers.add(
  1666. IntVar(self), ('main', 'General', 'editor-on-startup'))
  1667. self.win_width = tracers.add(
  1668. StringVar(self), ('main', 'EditorWindow', 'width'))
  1669. self.win_height = tracers.add(
  1670. StringVar(self), ('main', 'EditorWindow', 'height'))
  1671. self.cursor_blink = tracers.add(
  1672. BooleanVar(self), ('main', 'EditorWindow', 'cursor-blink'))
  1673. self.autocomplete_wait = tracers.add(
  1674. StringVar(self), ('extensions', 'AutoComplete', 'popupwait'))
  1675. self.paren_style = tracers.add(
  1676. StringVar(self), ('extensions', 'ParenMatch', 'style'))
  1677. self.flash_delay = tracers.add(
  1678. StringVar(self), ('extensions', 'ParenMatch', 'flash-delay'))
  1679. self.paren_bell = tracers.add(
  1680. BooleanVar(self), ('extensions', 'ParenMatch', 'bell'))
  1681. self.auto_squeeze_min_lines = tracers.add(
  1682. StringVar(self), ('main', 'PyShell', 'auto-squeeze-min-lines'))
  1683. self.autosave = tracers.add(
  1684. IntVar(self), ('main', 'General', 'autosave'))
  1685. self.format_width = tracers.add(
  1686. StringVar(self), ('extensions', 'FormatParagraph', 'max-width'))
  1687. self.line_numbers_default = tracers.add(
  1688. BooleanVar(self),
  1689. ('main', 'EditorWindow', 'line-numbers-default'))
  1690. self.context_lines = tracers.add(
  1691. StringVar(self), ('extensions', 'CodeContext', 'maxlines'))
  1692. # Create widgets:
  1693. # Section frames.
  1694. frame_window = LabelFrame(self, borderwidth=2, relief=GROOVE,
  1695. text=' Window Preferences')
  1696. frame_editor = LabelFrame(self, borderwidth=2, relief=GROOVE,
  1697. text=' Editor Preferences')
  1698. frame_shell = LabelFrame(self, borderwidth=2, relief=GROOVE,
  1699. text=' Shell Preferences')
  1700. frame_help = LabelFrame(self, borderwidth=2, relief=GROOVE,
  1701. text=' Additional Help Sources ')
  1702. # Frame_window.
  1703. frame_run = Frame(frame_window, borderwidth=0)
  1704. startup_title = Label(frame_run, text='At Startup')
  1705. self.startup_editor_on = Radiobutton(
  1706. frame_run, variable=self.startup_edit, value=1,
  1707. text="Open Edit Window")
  1708. self.startup_shell_on = Radiobutton(
  1709. frame_run, variable=self.startup_edit, value=0,
  1710. text='Open Shell Window')
  1711. frame_win_size = Frame(frame_window, borderwidth=0)
  1712. win_size_title = Label(
  1713. frame_win_size, text='Initial Window Size (in characters)')
  1714. win_width_title = Label(frame_win_size, text='Width')
  1715. self.win_width_int = Entry(
  1716. frame_win_size, textvariable=self.win_width, width=3,
  1717. validatecommand=self.digits_only, validate='key',
  1718. )
  1719. win_height_title = Label(frame_win_size, text='Height')
  1720. self.win_height_int = Entry(
  1721. frame_win_size, textvariable=self.win_height, width=3,
  1722. validatecommand=self.digits_only, validate='key',
  1723. )
  1724. frame_cursor_blink = Frame(frame_window, borderwidth=0)
  1725. cursor_blink_title = Label(frame_cursor_blink, text='Cursor Blink')
  1726. self.cursor_blink_bool = Checkbutton(frame_cursor_blink,
  1727. variable=self.cursor_blink, width=1)
  1728. frame_autocomplete = Frame(frame_window, borderwidth=0,)
  1729. auto_wait_title = Label(frame_autocomplete,
  1730. text='Completions Popup Wait (milliseconds)')
  1731. self.auto_wait_int = Entry(frame_autocomplete, width=6,
  1732. textvariable=self.autocomplete_wait,
  1733. validatecommand=self.digits_only,
  1734. validate='key',
  1735. )
  1736. frame_paren1 = Frame(frame_window, borderwidth=0)
  1737. paren_style_title = Label(frame_paren1, text='Paren Match Style')
  1738. self.paren_style_type = OptionMenu(
  1739. frame_paren1, self.paren_style, 'expression',
  1740. "opener","parens","expression")
  1741. frame_paren2 = Frame(frame_window, borderwidth=0)
  1742. paren_time_title = Label(
  1743. frame_paren2, text='Time Match Displayed (milliseconds)\n'
  1744. '(0 is until next input)')
  1745. self.paren_flash_time = Entry(
  1746. frame_paren2, textvariable=self.flash_delay, width=6)
  1747. self.bell_on = Checkbutton(
  1748. frame_paren2, text="Bell on Mismatch", variable=self.paren_bell)
  1749. # Frame_editor.
  1750. frame_save = Frame(frame_editor, borderwidth=0)
  1751. run_save_title = Label(frame_save, text='At Start of Run (F5) ')
  1752. self.save_ask_on = Radiobutton(
  1753. frame_save, variable=self.autosave, value=0,
  1754. text="Prompt to Save")
  1755. self.save_auto_on = Radiobutton(
  1756. frame_save, variable=self.autosave, value=1,
  1757. text='No Prompt')
  1758. frame_format = Frame(frame_editor, borderwidth=0)
  1759. format_width_title = Label(frame_format,
  1760. text='Format Paragraph Max Width')
  1761. self.format_width_int = Entry(
  1762. frame_format, textvariable=self.format_width, width=4,
  1763. validatecommand=self.digits_only, validate='key',
  1764. )
  1765. frame_line_numbers_default = Frame(frame_editor, borderwidth=0)
  1766. line_numbers_default_title = Label(
  1767. frame_line_numbers_default, text='Show line numbers in new windows')
  1768. self.line_numbers_default_bool = Checkbutton(
  1769. frame_line_numbers_default,
  1770. variable=self.line_numbers_default,
  1771. width=1)
  1772. frame_context = Frame(frame_editor, borderwidth=0)
  1773. context_title = Label(frame_context, text='Max Context Lines :')
  1774. self.context_int = Entry(
  1775. frame_context, textvariable=self.context_lines, width=3,
  1776. validatecommand=self.digits_only, validate='key',
  1777. )
  1778. # Frame_shell.
  1779. frame_auto_squeeze_min_lines = Frame(frame_shell, borderwidth=0)
  1780. auto_squeeze_min_lines_title = Label(frame_auto_squeeze_min_lines,
  1781. text='Auto-Squeeze Min. Lines:')
  1782. self.auto_squeeze_min_lines_int = Entry(
  1783. frame_auto_squeeze_min_lines, width=4,
  1784. textvariable=self.auto_squeeze_min_lines,
  1785. validatecommand=self.digits_only, validate='key',
  1786. )
  1787. # frame_help.
  1788. frame_helplist = Frame(frame_help)
  1789. frame_helplist_buttons = Frame(frame_helplist)
  1790. self.helplist = Listbox(
  1791. frame_helplist, height=5, takefocus=True,
  1792. exportselection=FALSE)
  1793. scroll_helplist = Scrollbar(frame_helplist)
  1794. scroll_helplist['command'] = self.helplist.yview
  1795. self.helplist['yscrollcommand'] = scroll_helplist.set
  1796. self.helplist.bind('<ButtonRelease-1>', self.help_source_selected)
  1797. self.button_helplist_edit = Button(
  1798. frame_helplist_buttons, text='Edit', state='disabled',
  1799. width=8, command=self.helplist_item_edit)
  1800. self.button_helplist_add = Button(
  1801. frame_helplist_buttons, text='Add',
  1802. width=8, command=self.helplist_item_add)
  1803. self.button_helplist_remove = Button(
  1804. frame_helplist_buttons, text='Remove', state='disabled',
  1805. width=8, command=self.helplist_item_remove)
  1806. # Pack widgets:
  1807. # Body.
  1808. frame_window.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
  1809. frame_editor.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
  1810. frame_shell.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
  1811. frame_help.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
  1812. # frame_run.
  1813. frame_run.pack(side=TOP, padx=5, pady=0, fill=X)
  1814. startup_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
  1815. self.startup_shell_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
  1816. self.startup_editor_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
  1817. # frame_win_size.
  1818. frame_win_size.pack(side=TOP, padx=5, pady=0, fill=X)
  1819. win_size_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
  1820. self.win_height_int.pack(side=RIGHT, anchor=E, padx=10, pady=5)
  1821. win_height_title.pack(side=RIGHT, anchor=E, pady=5)
  1822. self.win_width_int.pack(side=RIGHT, anchor=E, padx=10, pady=5)
  1823. win_width_title.pack(side=RIGHT, anchor=E, pady=5)
  1824. # frame_cursor_blink.
  1825. frame_cursor_blink.pack(side=TOP, padx=5, pady=0, fill=X)
  1826. cursor_blink_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
  1827. self.cursor_blink_bool.pack(side=LEFT, padx=5, pady=5)
  1828. # frame_autocomplete.
  1829. frame_autocomplete.pack(side=TOP, padx=5, pady=0, fill=X)
  1830. auto_wait_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
  1831. self.auto_wait_int.pack(side=TOP, padx=10, pady=5)
  1832. # frame_paren.
  1833. frame_paren1.pack(side=TOP, padx=5, pady=0, fill=X)
  1834. paren_style_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
  1835. self.paren_style_type.pack(side=TOP, padx=10, pady=5)
  1836. frame_paren2.pack(side=TOP, padx=5, pady=0, fill=X)
  1837. paren_time_title.pack(side=LEFT, anchor=W, padx=5)
  1838. self.bell_on.pack(side=RIGHT, anchor=E, padx=15, pady=5)
  1839. self.paren_flash_time.pack(side=TOP, anchor=W, padx=15, pady=5)
  1840. # frame_save.
  1841. frame_save.pack(side=TOP, padx=5, pady=0, fill=X)
  1842. run_save_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
  1843. self.save_auto_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
  1844. self.save_ask_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
  1845. # frame_format.
  1846. frame_format.pack(side=TOP, padx=5, pady=0, fill=X)
  1847. format_width_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
  1848. self.format_width_int.pack(side=TOP, padx=10, pady=5)
  1849. # frame_line_numbers_default.
  1850. frame_line_numbers_default.pack(side=TOP, padx=5, pady=0, fill=X)
  1851. line_numbers_default_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
  1852. self.line_numbers_default_bool.pack(side=LEFT, padx=5, pady=5)
  1853. # frame_context.
  1854. frame_context.pack(side=TOP, padx=5, pady=0, fill=X)
  1855. context_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
  1856. self.context_int.pack(side=TOP, padx=5, pady=5)
  1857. # frame_auto_squeeze_min_lines
  1858. frame_auto_squeeze_min_lines.pack(side=TOP, padx=5, pady=0, fill=X)
  1859. auto_squeeze_min_lines_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
  1860. self.auto_squeeze_min_lines_int.pack(side=TOP, padx=5, pady=5)
  1861. # frame_help.
  1862. frame_helplist_buttons.pack(side=RIGHT, padx=5, pady=5, fill=Y)
  1863. frame_helplist.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
  1864. scroll_helplist.pack(side=RIGHT, anchor=W, fill=Y)
  1865. self.helplist.pack(side=LEFT, anchor=E, expand=TRUE, fill=BOTH)
  1866. self.button_helplist_edit.pack(side=TOP, anchor=W, pady=5)
  1867. self.button_helplist_add.pack(side=TOP, anchor=W)
  1868. self.button_helplist_remove.pack(side=TOP, anchor=W, pady=5)
  1869. def load_general_cfg(self):
  1870. "Load current configuration settings for the general options."
  1871. # Set variables for all windows.
  1872. self.startup_edit.set(idleConf.GetOption(
  1873. 'main', 'General', 'editor-on-startup', type='bool'))
  1874. self.win_width.set(idleConf.GetOption(
  1875. 'main', 'EditorWindow', 'width', type='int'))
  1876. self.win_height.set(idleConf.GetOption(
  1877. 'main', 'EditorWindow', 'height', type='int'))
  1878. self.cursor_blink.set(idleConf.GetOption(
  1879. 'main', 'EditorWindow', 'cursor-blink', type='bool'))
  1880. self.autocomplete_wait.set(idleConf.GetOption(
  1881. 'extensions', 'AutoComplete', 'popupwait', type='int'))
  1882. self.paren_style.set(idleConf.GetOption(
  1883. 'extensions', 'ParenMatch', 'style'))
  1884. self.flash_delay.set(idleConf.GetOption(
  1885. 'extensions', 'ParenMatch', 'flash-delay', type='int'))
  1886. self.paren_bell.set(idleConf.GetOption(
  1887. 'extensions', 'ParenMatch', 'bell'))
  1888. # Set variables for editor windows.
  1889. self.autosave.set(idleConf.GetOption(
  1890. 'main', 'General', 'autosave', default=0, type='bool'))
  1891. self.format_width.set(idleConf.GetOption(
  1892. 'extensions', 'FormatParagraph', 'max-width', type='int'))
  1893. self.line_numbers_default.set(idleConf.GetOption(
  1894. 'main', 'EditorWindow', 'line-numbers-default', type='bool'))
  1895. self.context_lines.set(idleConf.GetOption(
  1896. 'extensions', 'CodeContext', 'maxlines', type='int'))
  1897. # Set variables for shell windows.
  1898. self.auto_squeeze_min_lines.set(idleConf.GetOption(
  1899. 'main', 'PyShell', 'auto-squeeze-min-lines', type='int'))
  1900. # Set additional help sources.
  1901. self.user_helplist = idleConf.GetAllExtraHelpSourcesList()
  1902. self.helplist.delete(0, 'end')
  1903. for help_item in self.user_helplist:
  1904. self.helplist.insert(END, help_item[0])
  1905. self.set_add_delete_state()
  1906. def help_source_selected(self, event):
  1907. "Handle event for selecting additional help."
  1908. self.set_add_delete_state()
  1909. def set_add_delete_state(self):
  1910. "Toggle the state for the help list buttons based on list entries."
  1911. if self.helplist.size() < 1: # No entries in list.
  1912. self.button_helplist_edit.state(('disabled',))
  1913. self.button_helplist_remove.state(('disabled',))
  1914. else: # Some entries.
  1915. if self.helplist.curselection(): # There currently is a selection.
  1916. self.button_helplist_edit.state(('!disabled',))
  1917. self.button_helplist_remove.state(('!disabled',))
  1918. else: # There currently is not a selection.
  1919. self.button_helplist_edit.state(('disabled',))
  1920. self.button_helplist_remove.state(('disabled',))
  1921. def helplist_item_add(self):
  1922. """Handle add button for the help list.
  1923. Query for name and location of new help sources and add
  1924. them to the list.
  1925. """
  1926. help_source = HelpSource(self, 'New Help Source').result
  1927. if help_source:
  1928. self.user_helplist.append(help_source)
  1929. self.helplist.insert(END, help_source[0])
  1930. self.update_help_changes()
  1931. def helplist_item_edit(self):
  1932. """Handle edit button for the help list.
  1933. Query with existing help source information and update
  1934. config if the values are changed.
  1935. """
  1936. item_index = self.helplist.index(ANCHOR)
  1937. help_source = self.user_helplist[item_index]
  1938. new_help_source = HelpSource(
  1939. self, 'Edit Help Source',
  1940. menuitem=help_source[0],
  1941. filepath=help_source[1],
  1942. ).result
  1943. if new_help_source and new_help_source != help_source:
  1944. self.user_helplist[item_index] = new_help_source
  1945. self.helplist.delete(item_index)
  1946. self.helplist.insert(item_index, new_help_source[0])
  1947. self.update_help_changes()
  1948. self.set_add_delete_state() # Selected will be un-selected
  1949. def helplist_item_remove(self):
  1950. """Handle remove button for the help list.
  1951. Delete the help list item from config.
  1952. """
  1953. item_index = self.helplist.index(ANCHOR)
  1954. del(self.user_helplist[item_index])
  1955. self.helplist.delete(item_index)
  1956. self.update_help_changes()
  1957. self.set_add_delete_state()
  1958. def update_help_changes(self):
  1959. "Clear and rebuild the HelpFiles section in changes"
  1960. changes['main']['HelpFiles'] = {}
  1961. for num in range(1, len(self.user_helplist) + 1):
  1962. changes.add_option(
  1963. 'main', 'HelpFiles', str(num),
  1964. ';'.join(self.user_helplist[num-1][:2]))
  1965. class VarTrace:
  1966. """Maintain Tk variables trace state."""
  1967. def __init__(self):
  1968. """Store Tk variables and callbacks.
  1969. untraced: List of tuples (var, callback)
  1970. that do not have the callback attached
  1971. to the Tk var.
  1972. traced: List of tuples (var, callback) where
  1973. that callback has been attached to the var.
  1974. """
  1975. self.untraced = []
  1976. self.traced = []
  1977. def clear(self):
  1978. "Clear lists (for tests)."
  1979. # Call after all tests in a module to avoid memory leaks.
  1980. self.untraced.clear()
  1981. self.traced.clear()
  1982. def add(self, var, callback):
  1983. """Add (var, callback) tuple to untraced list.
  1984. Args:
  1985. var: Tk variable instance.
  1986. callback: Either function name to be used as a callback
  1987. or a tuple with IdleConf config-type, section, and
  1988. option names used in the default callback.
  1989. Return:
  1990. Tk variable instance.
  1991. """
  1992. if isinstance(callback, tuple):
  1993. callback = self.make_callback(var, callback)
  1994. self.untraced.append((var, callback))
  1995. return var
  1996. @staticmethod
  1997. def make_callback(var, config):
  1998. "Return default callback function to add values to changes instance."
  1999. def default_callback(*params):
  2000. "Add config values to changes instance."
  2001. changes.add_option(*config, var.get())
  2002. return default_callback
  2003. def attach(self):
  2004. "Attach callback to all vars that are not traced."
  2005. while self.untraced:
  2006. var, callback = self.untraced.pop()
  2007. var.trace_add('write', callback)
  2008. self.traced.append((var, callback))
  2009. def detach(self):
  2010. "Remove callback from traced vars."
  2011. while self.traced:
  2012. var, callback = self.traced.pop()
  2013. var.trace_remove('write', var.trace_info()[0][1])
  2014. self.untraced.append((var, callback))
  2015. tracers = VarTrace()
  2016. help_common = '''\
  2017. When you click either the Apply or Ok buttons, settings in this
  2018. dialog that are different from IDLE's default are saved in
  2019. a .idlerc directory in your home directory. Except as noted,
  2020. these changes apply to all versions of IDLE installed on this
  2021. machine. [Cancel] only cancels changes made since the last save.
  2022. '''
  2023. help_pages = {
  2024. 'Fonts/Tabs':'''
  2025. Font sample: This shows what a selection of Basic Multilingual Plane
  2026. unicode characters look like for the current font selection. If the
  2027. selected font does not define a character, Tk attempts to find another
  2028. font that does. Substitute glyphs depend on what is available on a
  2029. particular system and will not necessarily have the same size as the
  2030. font selected. Line contains 20 characters up to Devanagari, 14 for
  2031. Tamil, and 10 for East Asia.
  2032. Hebrew and Arabic letters should display right to left, starting with
  2033. alef, \u05d0 and \u0627. Arabic digits display left to right. The
  2034. Devanagari and Tamil lines start with digits. The East Asian lines
  2035. are Chinese digits, Chinese Hanzi, Korean Hangul, and Japanese
  2036. Hiragana and Katakana.
  2037. You can edit the font sample. Changes remain until IDLE is closed.
  2038. ''',
  2039. 'Highlights': '''
  2040. Highlighting:
  2041. The IDLE Dark color theme is new in October 2015. It can only
  2042. be used with older IDLE releases if it is saved as a custom
  2043. theme, with a different name.
  2044. ''',
  2045. 'Keys': '''
  2046. Keys:
  2047. The IDLE Modern Unix key set is new in June 2016. It can only
  2048. be used with older IDLE releases if it is saved as a custom
  2049. key set, with a different name.
  2050. ''',
  2051. 'General': '''
  2052. General:
  2053. AutoComplete: Popupwait is milliseconds to wait after key char, without
  2054. cursor movement, before popping up completion box. Key char is '.' after
  2055. identifier or a '/' (or '\\' on Windows) within a string.
  2056. FormatParagraph: Max-width is max chars in lines after re-formatting.
  2057. Use with paragraphs in both strings and comment blocks.
  2058. ParenMatch: Style indicates what is highlighted when closer is entered:
  2059. 'opener' - opener '({[' corresponding to closer; 'parens' - both chars;
  2060. 'expression' (default) - also everything in between. Flash-delay is how
  2061. long to highlight if cursor is not moved (0 means forever).
  2062. CodeContext: Maxlines is the maximum number of code context lines to
  2063. display when Code Context is turned on for an editor window.
  2064. Shell Preferences: Auto-Squeeze Min. Lines is the minimum number of lines
  2065. of output to automatically "squeeze".
  2066. '''
  2067. }
  2068. def is_int(s):
  2069. "Return 's is blank or represents an int'"
  2070. if not s:
  2071. return True
  2072. try:
  2073. int(s)
  2074. return True
  2075. except ValueError:
  2076. return False
  2077. class VerticalScrolledFrame(Frame):
  2078. """A pure Tkinter vertically scrollable frame.
  2079. * Use the 'interior' attribute to place widgets inside the scrollable frame
  2080. * Construct and pack/place/grid normally
  2081. * This frame only allows vertical scrolling
  2082. """
  2083. def __init__(self, parent, *args, **kw):
  2084. Frame.__init__(self, parent, *args, **kw)
  2085. # Create a canvas object and a vertical scrollbar for scrolling it.
  2086. vscrollbar = Scrollbar(self, orient=VERTICAL)
  2087. vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE)
  2088. canvas = Canvas(self, borderwidth=0, highlightthickness=0,
  2089. yscrollcommand=vscrollbar.set, width=240)
  2090. canvas.pack(side=LEFT, fill=BOTH, expand=TRUE)
  2091. vscrollbar.config(command=canvas.yview)
  2092. # Reset the view.
  2093. canvas.xview_moveto(0)
  2094. canvas.yview_moveto(0)
  2095. # Create a frame inside the canvas which will be scrolled with it.
  2096. self.interior = interior = Frame(canvas)
  2097. interior_id = canvas.create_window(0, 0, window=interior, anchor=NW)
  2098. # Track changes to the canvas and frame width and sync them,
  2099. # also updating the scrollbar.
  2100. def _configure_interior(event):
  2101. # Update the scrollbars to match the size of the inner frame.
  2102. size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
  2103. canvas.config(scrollregion="0 0 %s %s" % size)
  2104. interior.bind('<Configure>', _configure_interior)
  2105. def _configure_canvas(event):
  2106. if interior.winfo_reqwidth() != canvas.winfo_width():
  2107. # Update the inner frame's width to fill the canvas.
  2108. canvas.itemconfigure(interior_id, width=canvas.winfo_width())
  2109. canvas.bind('<Configure>', _configure_canvas)
  2110. return
  2111. if __name__ == '__main__':
  2112. from unittest import main
  2113. main('idlelib.idle_test.test_configdialog', verbosity=2, exit=False)
  2114. from idlelib.idle_test.htest import run
  2115. run(ConfigDialog)