blob: a84e1c5668f99fee634c7fb06b2e275eede4e299 [file] [log] [blame]
Kurt B. Kaisere7a161e2003-01-10 20:13:57 +00001"""IDLE Configuration Dialog: support user customization of IDLE by GUI
2
3Customize font faces, sizes, and colorization attributes. Set indentation
4defaults. Customize keybindings. Colorization and keybindings can be
5saved as user defined sets. Select startup options including shell/editor
6and default window size. Define additional help sources.
7
8Note that tab width in IDLE is currently fixed at eight due to Tk issues.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +00009Refer to comments in EditorWindow autoindent code for details.
Kurt B. Kaisere7a161e2003-01-10 20:13:57 +000010
Steven M. Gava44d3d1a2001-07-31 06:59:02 +000011"""
Tal Einat1ebee372019-07-23 13:02:11 +030012import re
13
Victor Stinner6900f162020-04-30 03:28:51 +020014from tkinter import (Toplevel, Listbox, Scale, Canvas,
csabellabac7d332017-06-26 17:46:26 -040015 StringVar, BooleanVar, IntVar, TRUE, FALSE,
Terry Jan Reedye8f7c782017-11-28 21:52:32 -050016 TOP, BOTTOM, RIGHT, LEFT, SOLID, GROOVE,
17 NONE, BOTH, X, Y, W, E, EW, NS, NSEW, NW,
Louie Lubb2bae82017-07-10 06:57:18 +080018 HORIZONTAL, VERTICAL, ANCHOR, ACTIVE, END)
Terry Jan Reedyaff0ada2019-01-02 22:04:06 -050019from tkinter.ttk import (Frame, LabelFrame, Button, Checkbutton, Entry, Label,
wohlganger58fc71c2017-09-10 16:19:47 -050020 OptionMenu, Notebook, Radiobutton, Scrollbar, Style)
Georg Brandl14fc4272008-05-17 18:39:55 +000021import tkinter.colorchooser as tkColorChooser
22import tkinter.font as tkFont
Terry Jan Reedy3457f422017-08-27 16:39:41 -040023from tkinter import messagebox
Steven M. Gava44d3d1a2001-07-31 06:59:02 +000024
terryjreedy349abd92017-07-07 16:00:57 -040025from idlelib.config import idleConf, ConfigChanges
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040026from idlelib.config_key import GetKeysDialog
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -040027from idlelib.dynoption import DynOptionMenu
28from idlelib import macosx
Terry Jan Reedy8b22c0a2016-07-08 00:22:50 -040029from idlelib.query import SectionName, HelpSource
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040030from idlelib.textview import view_text
Terry Jan Reedy5777ecc2017-09-16 01:42:28 -040031from idlelib.autocomplete import AutoComplete
32from idlelib.codecontext import CodeContext
33from idlelib.parenmatch import ParenMatch
Cheryl Sabella82494aa2019-07-17 09:44:44 -040034from idlelib.format import FormatParagraph
Tal Einat604e7b92018-09-25 15:10:14 +030035from idlelib.squeezer import Squeezer
Tal Einat3221a632019-07-27 19:57:48 +030036from idlelib.textview import ScrollableTextFrame
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -040037
terryjreedy349abd92017-07-07 16:00:57 -040038changes = ConfigChanges()
Terry Jan Reedy5777ecc2017-09-16 01:42:28 -040039# Reload changed options in the following classes.
Tal Einat604e7b92018-09-25 15:10:14 +030040reloadables = (AutoComplete, CodeContext, ParenMatch, FormatParagraph,
41 Squeezer)
terryjreedy349abd92017-07-07 16:00:57 -040042
csabella5b591542017-07-28 14:40:59 -040043
Steven M. Gava44d3d1a2001-07-31 06:59:02 +000044class ConfigDialog(Toplevel):
csabella7eb58832017-07-04 21:30:58 -040045 """Config dialog for IDLE.
46 """
Kurt B. Kaiseracdef852005-01-31 03:34:26 +000047
Terry Jan Reedybfebfd82017-09-30 17:37:53 -040048 def __init__(self, parent, title='', *, _htest=False, _utest=False):
csabella7eb58832017-07-04 21:30:58 -040049 """Show the tabbed dialog for user configuration.
50
csabella36329a42017-07-13 23:32:01 -040051 Args:
52 parent - parent of this dialog
53 title - string which is the title of this popup dialog
54 _htest - bool, change box location when running htest
55 _utest - bool, don't wait_window when running unittest
56
57 Note: Focus set on font page fontlist.
58
59 Methods:
60 create_widgets
61 cancel: Bound to DELETE_WINDOW protocol.
Terry Jan Reedy2e8234a2014-05-29 01:46:26 -040062 """
Steven M. Gavad721c482001-07-31 10:46:53 +000063 Toplevel.__init__(self, parent)
Terry Jan Reedy22405332014-07-30 19:24:32 -040064 self.parent = parent
Terry Jan Reedy4036d872014-08-03 23:02:58 -040065 if _htest:
66 parent.instance_dict = {}
Louie Lu9b622fb2017-07-14 08:35:48 +080067 if not _utest:
68 self.withdraw()
Guido van Rossum8ce8a782007-11-01 19:42:39 +000069
Terry Jan Reedycd567362014-10-17 01:31:35 -040070 self.title(title or 'IDLE Preferences')
csabellabac7d332017-06-26 17:46:26 -040071 x = parent.winfo_rootx() + 20
72 y = parent.winfo_rooty() + (30 if not _htest else 150)
73 self.geometry(f'+{x}+{y}')
csabella7eb58832017-07-04 21:30:58 -040074 # Each theme element key is its display name.
75 # The first value of the tuple is the sample area tag name.
76 # The second value is the display name list sort index.
csabellabac7d332017-06-26 17:46:26 -040077 self.create_widgets()
Terry Jan Reedy4036d872014-08-03 23:02:58 -040078 self.resizable(height=FALSE, width=FALSE)
Steven M. Gavad721c482001-07-31 10:46:53 +000079 self.transient(parent)
csabellabac7d332017-06-26 17:46:26 -040080 self.protocol("WM_DELETE_WINDOW", self.cancel)
csabella9397e2a2017-07-30 13:34:25 -040081 self.fontpage.fontlist.focus_set()
csabella7eb58832017-07-04 21:30:58 -040082 # XXX Decide whether to keep or delete these key bindings.
83 # Key bindings for this dialog.
84 # self.bind('<Escape>', self.Cancel) #dismiss dialog, no save
85 # self.bind('<Alt-a>', self.Apply) #apply changes, save
86 # self.bind('<F1>', self.Help) #context help
Cheryl Sabella8f7a7982017-08-19 22:04:40 -040087 # Attach callbacks after loading config to avoid calling them.
csabella5b591542017-07-28 14:40:59 -040088 tracers.attach()
Guido van Rossum8ce8a782007-11-01 19:42:39 +000089
Terry Jan Reedycfa89502014-07-14 23:07:32 -040090 if not _utest:
Louie Lu9b622fb2017-07-14 08:35:48 +080091 self.grab_set()
Terry Jan Reedycfa89502014-07-14 23:07:32 -040092 self.wm_deiconify()
93 self.wait_window()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +000094
csabellabac7d332017-06-26 17:46:26 -040095 def create_widgets(self):
csabella36329a42017-07-13 23:32:01 -040096 """Create and place widgets for tabbed dialog.
97
98 Widgets Bound to self:
Mark Rosemanc579ad12020-10-24 16:45:00 -070099 frame: encloses all other widgets
csabellae8eb17b2017-07-30 18:39:17 -0400100 note: Notebook
Cheryl Sabella8f7a7982017-08-19 22:04:40 -0400101 highpage: HighPage
csabellae8eb17b2017-07-30 18:39:17 -0400102 fontpage: FontPage
Cheryl Sabellae36d9f52017-08-15 18:26:23 -0400103 keyspage: KeysPage
csabellae8eb17b2017-07-30 18:39:17 -0400104 genpage: GenPage
Cheryl Sabellae36d9f52017-08-15 18:26:23 -0400105 extpage: self.create_page_extensions
csabella36329a42017-07-13 23:32:01 -0400106
107 Methods:
csabella36329a42017-07-13 23:32:01 -0400108 create_action_buttons
109 load_configs: Load pages except for extensions.
csabella36329a42017-07-13 23:32:01 -0400110 activate_config_changes: Tell editors to reload.
111 """
Mark Rosemanc579ad12020-10-24 16:45:00 -0700112 self.frame = frame = Frame(self, padding="5px")
113 self.frame.grid(sticky="nwes")
114 self.note = note = Notebook(frame)
Cheryl Sabella8f7a7982017-08-19 22:04:40 -0400115 self.highpage = HighPage(note)
csabella9397e2a2017-07-30 13:34:25 -0400116 self.fontpage = FontPage(note, self.highpage)
Cheryl Sabellae36d9f52017-08-15 18:26:23 -0400117 self.keyspage = KeysPage(note)
csabellae8eb17b2017-07-30 18:39:17 -0400118 self.genpage = GenPage(note)
csabella9397e2a2017-07-30 13:34:25 -0400119 self.extpage = self.create_page_extensions()
120 note.add(self.fontpage, text='Fonts/Tabs')
121 note.add(self.highpage, text='Highlights')
122 note.add(self.keyspage, text=' Keys ')
123 note.add(self.genpage, text=' General ')
124 note.add(self.extpage, text='Extensions')
Terry Jan Reedyb331f802017-07-29 00:49:39 -0400125 note.enable_traversal()
126 note.pack(side=TOP, expand=TRUE, fill=BOTH)
Terry Jan Reedy92cb0a32014-10-08 20:29:13 -0400127 self.create_action_buttons().pack(side=BOTTOM)
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -0400128
Terry Jan Reedy92cb0a32014-10-08 20:29:13 -0400129 def create_action_buttons(self):
csabella36329a42017-07-13 23:32:01 -0400130 """Return frame of action buttons for dialog.
131
132 Methods:
133 ok
134 apply
135 cancel
136 help
137
138 Widget Structure:
139 outer: Frame
140 buttons: Frame
141 (no assignment): Button (ok)
142 (no assignment): Button (apply)
143 (no assignment): Button (cancel)
144 (no assignment): Button (help)
145 (no assignment): Frame
146 """
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400147 if macosx.isAquaTk():
Terry Jan Reedye3416e62014-07-26 19:40:16 -0400148 # Changing the default padding on OSX results in unreadable
csabella7eb58832017-07-04 21:30:58 -0400149 # text in the buttons.
csabellabac7d332017-06-26 17:46:26 -0400150 padding_args = {}
Ronald Oussoren9e350042009-02-12 16:02:11 +0000151 else:
Cheryl Sabella7028e592017-08-26 14:26:02 -0400152 padding_args = {'padding': (6, 3)}
Mark Rosemanc579ad12020-10-24 16:45:00 -0700153 outer = Frame(self.frame, padding=2)
Cheryl Sabelladd023ad2020-01-27 17:15:56 -0500154 buttons_frame = Frame(outer, padding=2)
155 self.buttons = {}
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -0400156 for txt, cmd in (
csabellabac7d332017-06-26 17:46:26 -0400157 ('Ok', self.ok),
158 ('Apply', self.apply),
159 ('Cancel', self.cancel),
160 ('Help', self.help)):
Cheryl Sabelladd023ad2020-01-27 17:15:56 -0500161 self.buttons[txt] = Button(buttons_frame, text=txt, command=cmd,
162 takefocus=FALSE, **padding_args)
163 self.buttons[txt].pack(side=LEFT, padx=5)
csabella7eb58832017-07-04 21:30:58 -0400164 # Add space above buttons.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -0400165 Frame(outer, height=2, borderwidth=0).pack(side=TOP)
Cheryl Sabelladd023ad2020-01-27 17:15:56 -0500166 buttons_frame.pack(side=BOTTOM)
Terry Jan Reedya9421fb2014-10-22 20:15:18 -0400167 return outer
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -0400168
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400169 def ok(self):
170 """Apply config changes, then dismiss dialog.
171
172 Methods:
173 apply
174 destroy: inherited
175 """
176 self.apply()
177 self.destroy()
178
179 def apply(self):
180 """Apply config changes and leave dialog open.
181
182 Methods:
183 deactivate_current_config
184 save_all_changed_extensions
185 activate_config_changes
186 """
187 self.deactivate_current_config()
188 changes.save_all()
189 self.save_all_changed_extensions()
190 self.activate_config_changes()
191
192 def cancel(self):
193 """Dismiss config dialog.
194
195 Methods:
196 destroy: inherited
197 """
Cheryl Sabellad0d9fa82020-01-25 04:00:54 -0500198 changes.clear()
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400199 self.destroy()
200
Serhiy Storchakaed6554c2017-10-28 03:22:44 +0300201 def destroy(self):
202 global font_sample_text
203 font_sample_text = self.fontpage.font_sample.get('1.0', 'end')
Tal Einat10ea9402018-08-02 09:18:29 +0300204 self.grab_release()
Serhiy Storchakaed6554c2017-10-28 03:22:44 +0300205 super().destroy()
206
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400207 def help(self):
208 """Create textview for config dialog help.
209
Xtreakd9677f32019-06-03 09:51:15 +0530210 Attributes accessed:
Cheryl Sabella3866d9b2017-09-10 22:41:10 -0400211 note
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400212 Methods:
213 view_text: Method from textview module.
214 """
Cheryl Sabella3866d9b2017-09-10 22:41:10 -0400215 page = self.note.tab(self.note.select(), option='text').strip()
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400216 view_text(self, title='Help for IDLE preferences',
Zackery Spytz2e43b642020-01-22 20:54:30 -0700217 contents=help_common+help_pages.get(page, ''))
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400218
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400219 def deactivate_current_config(self):
220 """Remove current key bindings.
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400221 Iterate over window instances defined in parent and remove
222 the keybindings.
223 """
224 # Before a config is saved, some cleanup of current
225 # config must be done - remove the previous keybindings.
226 win_instances = self.parent.instance_dict.keys()
227 for instance in win_instances:
228 instance.RemoveKeybindings()
229
230 def activate_config_changes(self):
231 """Apply configuration changes to current windows.
232
233 Dynamically update the current parent window instances
234 with some of the configuration changes.
235 """
236 win_instances = self.parent.instance_dict.keys()
237 for instance in win_instances:
238 instance.ResetColorizer()
239 instance.ResetFont()
240 instance.set_notabs_indentwidth()
241 instance.ApplyKeybindings()
242 instance.reset_help_menu_entries()
Zackery Spytz9c284492019-11-13 00:13:33 -0700243 instance.update_cursor_blink()
Terry Jan Reedy5777ecc2017-09-16 01:42:28 -0400244 for klass in reloadables:
245 klass.reload()
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400246
csabellabac7d332017-06-26 17:46:26 -0400247 def create_page_extensions(self):
Terry Jan Reedy93f35422015-10-13 22:03:51 -0400248 """Part of the config dialog used for configuring IDLE extensions.
249
250 This code is generic - it works for any and all IDLE extensions.
251
252 IDLE extensions save their configuration options using idleConf.
Terry Jan Reedyb2f87602015-10-13 22:09:06 -0400253 This code reads the current configuration using idleConf, supplies a
Terry Jan Reedy93f35422015-10-13 22:03:51 -0400254 GUI interface to change the configuration values, and saves the
255 changes using idleConf.
256
257 Not all changes take effect immediately - some may require restarting IDLE.
258 This depends on each extension's implementation.
259
260 All values are treated as text, and it is up to the user to supply
261 reasonable values. The only exception to this are the 'enable*' options,
Serhiy Storchaka6a7b3a72016-04-17 08:32:47 +0300262 which are boolean, and can be toggled with a True/False button.
csabella36329a42017-07-13 23:32:01 -0400263
264 Methods:
Ville Skyttä49b27342017-08-03 09:00:59 +0300265 load_extensions:
csabella36329a42017-07-13 23:32:01 -0400266 extension_selected: Handle selection from list.
267 create_extension_frame: Hold widgets for one extension.
268 set_extension_value: Set in userCfg['extensions'].
269 save_all_changed_extensions: Call extension page Save().
Terry Jan Reedy93f35422015-10-13 22:03:51 -0400270 """
271 parent = self.parent
Terry Jan Reedyb331f802017-07-29 00:49:39 -0400272 frame = Frame(self.note)
Terry Jan Reedy93f35422015-10-13 22:03:51 -0400273 self.ext_defaultCfg = idleConf.defaultCfg['extensions']
274 self.ext_userCfg = idleConf.userCfg['extensions']
275 self.is_int = self.register(is_int)
276 self.load_extensions()
csabella7eb58832017-07-04 21:30:58 -0400277 # Create widgets - a listbox shows all available extensions, with the
278 # controls for the extension selected in the listbox to the right.
Terry Jan Reedy93f35422015-10-13 22:03:51 -0400279 self.extension_names = StringVar(self)
280 frame.rowconfigure(0, weight=1)
281 frame.columnconfigure(2, weight=1)
282 self.extension_list = Listbox(frame, listvariable=self.extension_names,
283 selectmode='browse')
284 self.extension_list.bind('<<ListboxSelect>>', self.extension_selected)
285 scroll = Scrollbar(frame, command=self.extension_list.yview)
286 self.extension_list.yscrollcommand=scroll.set
287 self.details_frame = LabelFrame(frame, width=250, height=250)
288 self.extension_list.grid(column=0, row=0, sticky='nws')
289 scroll.grid(column=1, row=0, sticky='ns')
290 self.details_frame.grid(column=2, row=0, sticky='nsew', padx=[10, 0])
Cheryl Sabella7028e592017-08-26 14:26:02 -0400291 frame.configure(padding=10)
Terry Jan Reedy93f35422015-10-13 22:03:51 -0400292 self.config_frame = {}
293 self.current_extension = None
294
295 self.outerframe = self # TEMPORARY
296 self.tabbed_page_set = self.extension_list # TEMPORARY
297
csabella7eb58832017-07-04 21:30:58 -0400298 # Create the frame holding controls for each extension.
Terry Jan Reedy93f35422015-10-13 22:03:51 -0400299 ext_names = ''
300 for ext_name in sorted(self.extensions):
301 self.create_extension_frame(ext_name)
302 ext_names = ext_names + '{' + ext_name + '} '
303 self.extension_names.set(ext_names)
304 self.extension_list.selection_set(0)
305 self.extension_selected(None)
306
Terry Jan Reedyb331f802017-07-29 00:49:39 -0400307 return frame
308
Terry Jan Reedy93f35422015-10-13 22:03:51 -0400309 def load_extensions(self):
310 "Fill self.extensions with data from the default and user configs."
311 self.extensions = {}
312 for ext_name in idleConf.GetExtensions(active_only=False):
wohlganger58fc71c2017-09-10 16:19:47 -0500313 # Former built-in extensions are already filtered out.
Terry Jan Reedy93f35422015-10-13 22:03:51 -0400314 self.extensions[ext_name] = []
315
316 for ext_name in self.extensions:
317 opt_list = sorted(self.ext_defaultCfg.GetOptionList(ext_name))
318
csabella7eb58832017-07-04 21:30:58 -0400319 # Bring 'enable' options to the beginning of the list.
Terry Jan Reedy93f35422015-10-13 22:03:51 -0400320 enables = [opt_name for opt_name in opt_list
321 if opt_name.startswith('enable')]
322 for opt_name in enables:
323 opt_list.remove(opt_name)
324 opt_list = enables + opt_list
325
326 for opt_name in opt_list:
327 def_str = self.ext_defaultCfg.Get(
328 ext_name, opt_name, raw=True)
329 try:
330 def_obj = {'True':True, 'False':False}[def_str]
331 opt_type = 'bool'
332 except KeyError:
333 try:
334 def_obj = int(def_str)
335 opt_type = 'int'
336 except ValueError:
337 def_obj = def_str
338 opt_type = None
339 try:
340 value = self.ext_userCfg.Get(
341 ext_name, opt_name, type=opt_type, raw=True,
342 default=def_obj)
csabella7eb58832017-07-04 21:30:58 -0400343 except ValueError: # Need this until .Get fixed.
344 value = def_obj # Bad values overwritten by entry.
Terry Jan Reedy93f35422015-10-13 22:03:51 -0400345 var = StringVar(self)
346 var.set(str(value))
347
348 self.extensions[ext_name].append({'name': opt_name,
349 'type': opt_type,
350 'default': def_str,
351 'value': value,
352 'var': var,
353 })
354
355 def extension_selected(self, event):
csabella7eb58832017-07-04 21:30:58 -0400356 "Handle selection of an extension from the list."
Terry Jan Reedy93f35422015-10-13 22:03:51 -0400357 newsel = self.extension_list.curselection()
358 if newsel:
359 newsel = self.extension_list.get(newsel)
360 if newsel is None or newsel != self.current_extension:
361 if self.current_extension:
362 self.details_frame.config(text='')
363 self.config_frame[self.current_extension].grid_forget()
364 self.current_extension = None
365 if newsel:
366 self.details_frame.config(text=newsel)
367 self.config_frame[newsel].grid(column=0, row=0, sticky='nsew')
368 self.current_extension = newsel
369
370 def create_extension_frame(self, ext_name):
371 """Create a frame holding the widgets to configure one extension"""
372 f = VerticalScrolledFrame(self.details_frame, height=250, width=250)
373 self.config_frame[ext_name] = f
374 entry_area = f.interior
csabella7eb58832017-07-04 21:30:58 -0400375 # Create an entry for each configuration option.
Terry Jan Reedy93f35422015-10-13 22:03:51 -0400376 for row, opt in enumerate(self.extensions[ext_name]):
csabella7eb58832017-07-04 21:30:58 -0400377 # Create a row with a label and entry/checkbutton.
Terry Jan Reedy93f35422015-10-13 22:03:51 -0400378 label = Label(entry_area, text=opt['name'])
379 label.grid(row=row, column=0, sticky=NW)
380 var = opt['var']
381 if opt['type'] == 'bool':
Cheryl Sabella7028e592017-08-26 14:26:02 -0400382 Checkbutton(entry_area, variable=var,
383 onvalue='True', offvalue='False', width=8
Terry Jan Reedy93f35422015-10-13 22:03:51 -0400384 ).grid(row=row, column=1, sticky=W, padx=7)
385 elif opt['type'] == 'int':
386 Entry(entry_area, textvariable=var, validate='key',
Terry Jan Reedy800415e2018-06-11 14:14:32 -0400387 validatecommand=(self.is_int, '%P'), width=10
Terry Jan Reedy93f35422015-10-13 22:03:51 -0400388 ).grid(row=row, column=1, sticky=NSEW, padx=7)
389
Terry Jan Reedy800415e2018-06-11 14:14:32 -0400390 else: # type == 'str'
391 # Limit size to fit non-expanding space with larger font.
392 Entry(entry_area, textvariable=var, width=15
Terry Jan Reedy93f35422015-10-13 22:03:51 -0400393 ).grid(row=row, column=1, sticky=NSEW, padx=7)
394 return
395
396 def set_extension_value(self, section, opt):
csabella7eb58832017-07-04 21:30:58 -0400397 """Return True if the configuration was added or changed.
398
399 If the value is the same as the default, then remove it
400 from user config file.
401 """
Terry Jan Reedy93f35422015-10-13 22:03:51 -0400402 name = opt['name']
403 default = opt['default']
404 value = opt['var'].get().strip() or default
405 opt['var'].set(value)
406 # if self.defaultCfg.has_section(section):
csabella7eb58832017-07-04 21:30:58 -0400407 # Currently, always true; if not, indent to return.
Terry Jan Reedy93f35422015-10-13 22:03:51 -0400408 if (value == default):
409 return self.ext_userCfg.RemoveOption(section, name)
csabella7eb58832017-07-04 21:30:58 -0400410 # Set the option.
Terry Jan Reedy93f35422015-10-13 22:03:51 -0400411 return self.ext_userCfg.SetOption(section, name, value)
412
413 def save_all_changed_extensions(self):
csabella36329a42017-07-13 23:32:01 -0400414 """Save configuration changes to the user config file.
415
416 Attributes accessed:
417 extensions
418
419 Methods:
420 set_extension_value
421 """
Terry Jan Reedy93f35422015-10-13 22:03:51 -0400422 has_changes = False
423 for ext_name in self.extensions:
424 options = self.extensions[ext_name]
425 for opt in options:
426 if self.set_extension_value(ext_name, opt):
427 has_changes = True
428 if has_changes:
429 self.ext_userCfg.Save()
430
431
csabella6f446be2017-08-01 00:24:07 -0400432# class TabPage(Frame): # A template for Page classes.
433# def __init__(self, master):
434# super().__init__(master)
435# self.create_page_tab()
436# self.load_tab_cfg()
437# def create_page_tab(self):
438# # Define tk vars and register var and callback with tracers.
439# # Create subframes and widgets.
440# # Pack widgets.
441# def load_tab_cfg(self):
442# # Initialize widgets with data from idleConf.
443# def var_changed_var_name():
444# # For each tk var that needs other than default callback.
445# def other_methods():
446# # Define tab-specific behavior.
447
Serhiy Storchakaed6554c2017-10-28 03:22:44 +0300448font_sample_text = (
449 '<ASCII/Latin1>\n'
450 'AaBbCcDdEeFfGgHhIiJj\n1234567890#:+=(){}[]\n'
451 '\u00a2\u00a3\u00a5\u00a7\u00a9\u00ab\u00ae\u00b6\u00bd\u011e'
452 '\u00c0\u00c1\u00c2\u00c3\u00c4\u00c5\u00c7\u00d0\u00d8\u00df\n'
453 '\n<IPA,Greek,Cyrillic>\n'
454 '\u0250\u0255\u0258\u025e\u025f\u0264\u026b\u026e\u0270\u0277'
455 '\u027b\u0281\u0283\u0286\u028e\u029e\u02a2\u02ab\u02ad\u02af\n'
456 '\u0391\u03b1\u0392\u03b2\u0393\u03b3\u0394\u03b4\u0395\u03b5'
457 '\u0396\u03b6\u0397\u03b7\u0398\u03b8\u0399\u03b9\u039a\u03ba\n'
458 '\u0411\u0431\u0414\u0434\u0416\u0436\u041f\u043f\u0424\u0444'
459 '\u0427\u0447\u042a\u044a\u042d\u044d\u0460\u0464\u046c\u04dc\n'
460 '\n<Hebrew, Arabic>\n'
461 '\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9'
462 '\u05da\u05db\u05dc\u05dd\u05de\u05df\u05e0\u05e1\u05e2\u05e3\n'
463 '\u0627\u0628\u062c\u062f\u0647\u0648\u0632\u062d\u0637\u064a'
464 '\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669\n'
465 '\n<Devanagari, Tamil>\n'
466 '\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f'
467 '\u0905\u0906\u0907\u0908\u0909\u090a\u090f\u0910\u0913\u0914\n'
468 '\u0be6\u0be7\u0be8\u0be9\u0bea\u0beb\u0bec\u0bed\u0bee\u0bef'
469 '\u0b85\u0b87\u0b89\u0b8e\n'
470 '\n<East Asian>\n'
471 '\u3007\u4e00\u4e8c\u4e09\u56db\u4e94\u516d\u4e03\u516b\u4e5d\n'
472 '\u6c49\u5b57\u6f22\u5b57\u4eba\u6728\u706b\u571f\u91d1\u6c34\n'
473 '\uac00\ub0d0\ub354\ub824\ubaa8\ubd64\uc218\uc720\uc988\uce58\n'
474 '\u3042\u3044\u3046\u3048\u304a\u30a2\u30a4\u30a6\u30a8\u30aa\n'
475 )
476
csabella6f446be2017-08-01 00:24:07 -0400477
csabella9397e2a2017-07-30 13:34:25 -0400478class FontPage(Frame):
479
csabella6f446be2017-08-01 00:24:07 -0400480 def __init__(self, master, highpage):
481 super().__init__(master)
csabella9397e2a2017-07-30 13:34:25 -0400482 self.highlight_sample = highpage.highlight_sample
483 self.create_page_font_tab()
484 self.load_font_cfg()
485 self.load_tab_cfg()
486
487 def create_page_font_tab(self):
488 """Return frame of widgets for Font/Tabs tab.
489
490 Fonts: Enable users to provisionally change font face, size, or
491 boldness and to see the consequence of proposed choices. Each
492 action set 3 options in changes structuree and changes the
493 corresponding aspect of the font sample on this page and
494 highlight sample on highlight page.
495
496 Function load_font_cfg initializes font vars and widgets from
497 idleConf entries and tk.
498
499 Fontlist: mouse button 1 click or up or down key invoke
500 on_fontlist_select(), which sets var font_name.
501
502 Sizelist: clicking the menubutton opens the dropdown menu. A
503 mouse button 1 click or return key sets var font_size.
504
505 Bold_toggle: clicking the box toggles var font_bold.
506
507 Changing any of the font vars invokes var_changed_font, which
508 adds all 3 font options to changes and calls set_samples.
509 Set_samples applies a new font constructed from the font vars to
Leo Ariasc3d95082018-02-03 18:36:10 -0600510 font_sample and to highlight_sample on the highlight page.
csabella9397e2a2017-07-30 13:34:25 -0400511
512 Tabs: Enable users to change spaces entered for indent tabs.
513 Changing indent_scale value with the mouse sets Var space_num,
514 which invokes the default callback to add an entry to
515 changes. Load_tab_cfg initializes space_num to default.
516
Cheryl Sabella2f896462017-08-14 21:21:43 -0400517 Widgets for FontPage(Frame): (*) widgets bound to self
518 frame_font: LabelFrame
519 frame_font_name: Frame
520 font_name_title: Label
521 (*)fontlist: ListBox - font_name
522 scroll_font: Scrollbar
523 frame_font_param: Frame
524 font_size_title: Label
525 (*)sizelist: DynOptionMenu - font_size
526 (*)bold_toggle: Checkbutton - font_bold
Terry Jan Reedye2e42272017-10-17 18:56:16 -0400527 frame_sample: LabelFrame
528 (*)font_sample: Label
Cheryl Sabella2f896462017-08-14 21:21:43 -0400529 frame_indent: LabelFrame
530 indent_title: Label
531 (*)indent_scale: Scale - space_num
csabella9397e2a2017-07-30 13:34:25 -0400532 """
csabella6f446be2017-08-01 00:24:07 -0400533 self.font_name = tracers.add(StringVar(self), self.var_changed_font)
534 self.font_size = tracers.add(StringVar(self), self.var_changed_font)
535 self.font_bold = tracers.add(BooleanVar(self), self.var_changed_font)
csabella9397e2a2017-07-30 13:34:25 -0400536 self.space_num = tracers.add(IntVar(self), ('main', 'Indent', 'num-spaces'))
537
Terry Jan Reedye2e42272017-10-17 18:56:16 -0400538 # Define frames and widgets.
csabella9397e2a2017-07-30 13:34:25 -0400539 frame_font = LabelFrame(
Terry Jan Reedye2e42272017-10-17 18:56:16 -0400540 self, borderwidth=2, relief=GROOVE, text=' Shell/Editor Font ')
541 frame_sample = LabelFrame(
Serhiy Storchakaed6554c2017-10-28 03:22:44 +0300542 self, borderwidth=2, relief=GROOVE,
543 text=' Font Sample (Editable) ')
csabella9397e2a2017-07-30 13:34:25 -0400544 frame_indent = LabelFrame(
csabella6f446be2017-08-01 00:24:07 -0400545 self, borderwidth=2, relief=GROOVE, text=' Indentation Width ')
csabella9397e2a2017-07-30 13:34:25 -0400546 # frame_font.
547 frame_font_name = Frame(frame_font)
548 frame_font_param = Frame(frame_font)
549 font_name_title = Label(
550 frame_font_name, justify=LEFT, text='Font Face :')
Terry Jan Reedye2e42272017-10-17 18:56:16 -0400551 self.fontlist = Listbox(frame_font_name, height=15,
csabella9397e2a2017-07-30 13:34:25 -0400552 takefocus=True, exportselection=FALSE)
553 self.fontlist.bind('<ButtonRelease-1>', self.on_fontlist_select)
554 self.fontlist.bind('<KeyRelease-Up>', self.on_fontlist_select)
555 self.fontlist.bind('<KeyRelease-Down>', self.on_fontlist_select)
556 scroll_font = Scrollbar(frame_font_name)
557 scroll_font.config(command=self.fontlist.yview)
558 self.fontlist.config(yscrollcommand=scroll_font.set)
559 font_size_title = Label(frame_font_param, text='Size :')
560 self.sizelist = DynOptionMenu(frame_font_param, self.font_size, None)
561 self.bold_toggle = Checkbutton(
562 frame_font_param, variable=self.font_bold,
563 onvalue=1, offvalue=0, text='Bold')
Terry Jan Reedye2e42272017-10-17 18:56:16 -0400564 # frame_sample.
Tal Einat3221a632019-07-27 19:57:48 +0300565 font_sample_frame = ScrollableTextFrame(frame_sample)
566 self.font_sample = font_sample_frame.text
567 self.font_sample.config(wrap=NONE, width=1, height=1)
Serhiy Storchakaed6554c2017-10-28 03:22:44 +0300568 self.font_sample.insert(END, font_sample_text)
csabella9397e2a2017-07-30 13:34:25 -0400569 # frame_indent.
570 indent_title = Label(
571 frame_indent, justify=LEFT,
572 text='Python Standard: 4 Spaces!')
573 self.indent_scale = Scale(
574 frame_indent, variable=self.space_num,
575 orient='horizontal', tickinterval=2, from_=2, to=16)
576
Terry Jan Reedye2e42272017-10-17 18:56:16 -0400577 # Grid and pack widgets:
578 self.columnconfigure(1, weight=1)
Tal Einat3221a632019-07-27 19:57:48 +0300579 self.rowconfigure(2, weight=1)
Terry Jan Reedye2e42272017-10-17 18:56:16 -0400580 frame_font.grid(row=0, column=0, padx=5, pady=5)
Tal Einat3221a632019-07-27 19:57:48 +0300581 frame_sample.grid(row=0, column=1, rowspan=3, padx=5, pady=5,
Terry Jan Reedye2e42272017-10-17 18:56:16 -0400582 sticky='nsew')
583 frame_indent.grid(row=1, column=0, padx=5, pady=5, sticky='ew')
csabella9397e2a2017-07-30 13:34:25 -0400584 # frame_font.
585 frame_font_name.pack(side=TOP, padx=5, pady=5, fill=X)
586 frame_font_param.pack(side=TOP, padx=5, pady=5, fill=X)
587 font_name_title.pack(side=TOP, anchor=W)
588 self.fontlist.pack(side=LEFT, expand=TRUE, fill=X)
589 scroll_font.pack(side=LEFT, fill=Y)
590 font_size_title.pack(side=LEFT, anchor=W)
591 self.sizelist.pack(side=LEFT, anchor=W)
592 self.bold_toggle.pack(side=LEFT, anchor=W, padx=20)
Terry Jan Reedye2e42272017-10-17 18:56:16 -0400593 # frame_sample.
Tal Einat3221a632019-07-27 19:57:48 +0300594 font_sample_frame.pack(expand=TRUE, fill=BOTH)
csabella9397e2a2017-07-30 13:34:25 -0400595 # frame_indent.
csabella9397e2a2017-07-30 13:34:25 -0400596 indent_title.pack(side=TOP, anchor=W, padx=5)
597 self.indent_scale.pack(side=TOP, padx=5, fill=X)
598
csabella9397e2a2017-07-30 13:34:25 -0400599 def load_font_cfg(self):
600 """Load current configuration settings for the font options.
601
602 Retrieve current font with idleConf.GetFont and font families
603 from tk. Setup fontlist and set font_name. Setup sizelist,
604 which sets font_size. Set font_bold. Call set_samples.
605 """
606 configured_font = idleConf.GetFont(self, 'main', 'EditorWindow')
607 font_name = configured_font[0].lower()
608 font_size = configured_font[1]
609 font_bold = configured_font[2]=='bold'
610
Terry Jan Reedy96ce2272020-02-10 20:08:58 -0500611 # Set sorted no-duplicate editor font selection list and font_name.
612 fonts = sorted(set(tkFont.families(self)))
csabella9397e2a2017-07-30 13:34:25 -0400613 for font in fonts:
614 self.fontlist.insert(END, font)
615 self.font_name.set(font_name)
616 lc_fonts = [s.lower() for s in fonts]
617 try:
618 current_font_index = lc_fonts.index(font_name)
619 self.fontlist.see(current_font_index)
620 self.fontlist.select_set(current_font_index)
621 self.fontlist.select_anchor(current_font_index)
622 self.fontlist.activate(current_font_index)
623 except ValueError:
624 pass
625 # Set font size dropdown.
626 self.sizelist.SetMenu(('7', '8', '9', '10', '11', '12', '13', '14',
627 '16', '18', '20', '22', '25', '29', '34', '40'),
628 font_size)
629 # Set font weight.
630 self.font_bold.set(font_bold)
631 self.set_samples()
632
633 def var_changed_font(self, *params):
634 """Store changes to font attributes.
635
636 When one font attribute changes, save them all, as they are
637 not independent from each other. In particular, when we are
638 overriding the default font, we need to write out everything.
639 """
640 value = self.font_name.get()
641 changes.add_option('main', 'EditorWindow', 'font', value)
642 value = self.font_size.get()
643 changes.add_option('main', 'EditorWindow', 'font-size', value)
644 value = self.font_bold.get()
645 changes.add_option('main', 'EditorWindow', 'font-bold', value)
646 self.set_samples()
647
648 def on_fontlist_select(self, event):
649 """Handle selecting a font from the list.
650
651 Event can result from either mouse click or Up or Down key.
652 Set font_name and example displays to selection.
653 """
654 font = self.fontlist.get(
655 ACTIVE if event.type.name == 'KeyRelease' else ANCHOR)
656 self.font_name.set(font.lower())
657
658 def set_samples(self, event=None):
659 """Update update both screen samples with the font settings.
660
661 Called on font initialization and change events.
662 Accesses font_name, font_size, and font_bold Variables.
Leo Ariasc3d95082018-02-03 18:36:10 -0600663 Updates font_sample and highlight page highlight_sample.
csabella9397e2a2017-07-30 13:34:25 -0400664 """
665 font_name = self.font_name.get()
666 font_weight = tkFont.BOLD if self.font_bold.get() else tkFont.NORMAL
667 new_font = (font_name, self.font_size.get(), font_weight)
668 self.font_sample['font'] = new_font
669 self.highlight_sample['font'] = new_font
670
671 def load_tab_cfg(self):
672 """Load current configuration settings for the tab options.
673
674 Attributes updated:
675 space_num: Set to value from idleConf.
676 """
677 # Set indent sizes.
678 space_num = idleConf.GetOption(
679 'main', 'Indent', 'num-spaces', default=4, type='int')
680 self.space_num.set(space_num)
681
682 def var_changed_space_num(self, *params):
683 "Store change to indentation size."
684 value = self.space_num.get()
685 changes.add_option('main', 'Indent', 'num-spaces', value)
686
687
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400688class HighPage(Frame):
689
690 def __init__(self, master):
691 super().__init__(master)
Mark Rosemanc579ad12020-10-24 16:45:00 -0700692 self.cd = master.winfo_toplevel()
Cheryl Sabella7028e592017-08-26 14:26:02 -0400693 self.style = Style(master)
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400694 self.create_page_highlight()
695 self.load_theme_cfg()
696
697 def create_page_highlight(self):
698 """Return frame of widgets for Highlighting tab.
699
700 Enable users to provisionally change foreground and background
701 colors applied to textual tags. Color mappings are stored in
702 complete listings called themes. Built-in themes in
703 idlelib/config-highlight.def are fixed as far as the dialog is
704 concerned. Any theme can be used as the base for a new custom
705 theme, stored in .idlerc/config-highlight.cfg.
706
707 Function load_theme_cfg() initializes tk variables and theme
708 lists and calls paint_theme_sample() and set_highlight_target()
709 for the current theme. Radiobuttons builtin_theme_on and
710 custom_theme_on toggle var theme_source, which controls if the
711 current set of colors are from a builtin or custom theme.
712 DynOptionMenus builtinlist and customlist contain lists of the
713 builtin and custom themes, respectively, and the current item
714 from each list is stored in vars builtin_name and custom_name.
715
716 Function paint_theme_sample() applies the colors from the theme
717 to the tags in text widget highlight_sample and then invokes
718 set_color_sample(). Function set_highlight_target() sets the state
719 of the radiobuttons fg_on and bg_on based on the tag and it also
720 invokes set_color_sample().
721
722 Function set_color_sample() sets the background color for the frame
723 holding the color selector. This provides a larger visual of the
724 color for the current tag and plane (foreground/background).
725
726 Note: set_color_sample() is called from many places and is often
727 called more than once when a change is made. It is invoked when
728 foreground or background is selected (radiobuttons), from
729 paint_theme_sample() (theme is changed or load_cfg is called), and
730 from set_highlight_target() (target tag is changed or load_cfg called).
731
732 Button delete_custom invokes delete_custom() to delete
733 a custom theme from idleConf.userCfg['highlight'] and changes.
734 Button save_custom invokes save_as_new_theme() which calls
735 get_new_theme_name() and create_new() to save a custom theme
736 and its colors to idleConf.userCfg['highlight'].
737
738 Radiobuttons fg_on and bg_on toggle var fg_bg_toggle to control
739 if the current selected color for a tag is for the foreground or
740 background.
741
742 DynOptionMenu targetlist contains a readable description of the
743 tags applied to Python source within IDLE. Selecting one of the
744 tags from this list populates highlight_target, which has a callback
745 function set_highlight_target().
746
747 Text widget highlight_sample displays a block of text (which is
748 mock Python code) in which is embedded the defined tags and reflects
749 the color attributes of the current theme and changes for those tags.
750 Mouse button 1 allows for selection of a tag and updates
751 highlight_target with that tag value.
752
753 Note: The font in highlight_sample is set through the config in
754 the fonts tab.
755
756 In other words, a tag can be selected either from targetlist or
757 by clicking on the sample text within highlight_sample. The
758 plane (foreground/background) is selected via the radiobutton.
759 Together, these two (tag and plane) control what color is
760 shown in set_color_sample() for the current theme. Button set_color
761 invokes get_color() which displays a ColorChooser to change the
762 color for the selected tag/plane. If a new color is picked,
763 it will be saved to changes and the highlight_sample and
764 frame background will be updated.
765
766 Tk Variables:
767 color: Color of selected target.
768 builtin_name: Menu variable for built-in theme.
769 custom_name: Menu variable for custom theme.
770 fg_bg_toggle: Toggle for foreground/background color.
771 Note: this has no callback.
772 theme_source: Selector for built-in or custom theme.
773 highlight_target: Menu variable for the highlight tag target.
774
775 Instance Data Attributes:
776 theme_elements: Dictionary of tags for text highlighting.
777 The key is the display name and the value is a tuple of
778 (tag name, display sort order).
779
780 Methods [attachment]:
781 load_theme_cfg: Load current highlight colors.
782 get_color: Invoke colorchooser [button_set_color].
783 set_color_sample_binding: Call set_color_sample [fg_bg_toggle].
784 set_highlight_target: set fg_bg_toggle, set_color_sample().
785 set_color_sample: Set frame background to target.
786 on_new_color_set: Set new color and add option.
787 paint_theme_sample: Recolor sample.
788 get_new_theme_name: Get from popup.
789 create_new: Combine theme with changes and save.
790 save_as_new_theme: Save [button_save_custom].
791 set_theme_type: Command for [theme_source].
792 delete_custom: Activate default [button_delete_custom].
793 save_new: Save to userCfg['theme'] (is function).
794
795 Widgets of highlights page frame: (*) widgets bound to self
796 frame_custom: LabelFrame
797 (*)highlight_sample: Text
798 (*)frame_color_set: Frame
799 (*)button_set_color: Button
800 (*)targetlist: DynOptionMenu - highlight_target
801 frame_fg_bg_toggle: Frame
802 (*)fg_on: Radiobutton - fg_bg_toggle
803 (*)bg_on: Radiobutton - fg_bg_toggle
804 (*)button_save_custom: Button
805 frame_theme: LabelFrame
806 theme_type_title: Label
807 (*)builtin_theme_on: Radiobutton - theme_source
808 (*)custom_theme_on: Radiobutton - theme_source
809 (*)builtinlist: DynOptionMenu - builtin_name
810 (*)customlist: DynOptionMenu - custom_name
811 (*)button_delete_custom: Button
812 (*)theme_message: Label
813 """
814 self.theme_elements = {
Terry Jan Reedyb2229552019-07-28 12:04:31 -0400815 'Normal Code or Text': ('normal', '00'),
Cheryl Sabellade651622018-06-01 21:45:54 -0400816 'Code Context': ('context', '01'),
817 'Python Keywords': ('keyword', '02'),
818 'Python Definitions': ('definition', '03'),
819 'Python Builtins': ('builtin', '04'),
820 'Python Comments': ('comment', '05'),
821 'Python Strings': ('string', '06'),
822 'Selected Text': ('hilite', '07'),
823 'Found Text': ('hit', '08'),
824 'Cursor': ('cursor', '09'),
825 'Editor Breakpoint': ('break', '10'),
Terry Jan Reedyb2229552019-07-28 12:04:31 -0400826 'Shell Prompt': ('console', '11'),
827 'Error Text': ('error', '12'),
828 'Shell User Output': ('stdout', '13'),
829 'Shell User Exception': ('stderr', '14'),
Tal Einat7123ea02019-07-23 15:22:11 +0300830 'Line Number': ('linenumber', '16'),
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400831 }
832 self.builtin_name = tracers.add(
833 StringVar(self), self.var_changed_builtin_name)
834 self.custom_name = tracers.add(
835 StringVar(self), self.var_changed_custom_name)
836 self.fg_bg_toggle = BooleanVar(self)
837 self.color = tracers.add(
838 StringVar(self), self.var_changed_color)
839 self.theme_source = tracers.add(
840 BooleanVar(self), self.var_changed_theme_source)
841 self.highlight_target = tracers.add(
842 StringVar(self), self.var_changed_highlight_target)
843
844 # Create widgets:
845 # body frame and section frames.
846 frame_custom = LabelFrame(self, borderwidth=2, relief=GROOVE,
847 text=' Custom Highlighting ')
848 frame_theme = LabelFrame(self, borderwidth=2, relief=GROOVE,
849 text=' Highlighting Theme ')
850 # frame_custom.
Tal Einat3221a632019-07-27 19:57:48 +0300851 sample_frame = ScrollableTextFrame(
852 frame_custom, relief=SOLID, borderwidth=1)
853 text = self.highlight_sample = sample_frame.text
854 text.configure(
855 font=('courier', 12, ''), cursor='hand2', width=1, height=1,
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400856 takefocus=FALSE, highlightthickness=0, wrap=NONE)
Cheryl Sabelladd023ad2020-01-27 17:15:56 -0500857 # Prevent perhaps invisible selection of word or slice.
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400858 text.bind('<Double-Button-1>', lambda e: 'break')
859 text.bind('<B1-Motion>', lambda e: 'break')
Terry Jan Reedyb2229552019-07-28 12:04:31 -0400860 string_tags=(
861 ('# Click selects item.', 'comment'), ('\n', 'normal'),
862 ('code context section', 'context'), ('\n', 'normal'),
863 ('| cursor', 'cursor'), ('\n', 'normal'),
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400864 ('def', 'keyword'), (' ', 'normal'),
865 ('func', 'definition'), ('(param):\n ', 'normal'),
Terry Jan Reedyb2229552019-07-28 12:04:31 -0400866 ('"Return None."', 'string'), ('\n var0 = ', 'normal'),
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400867 ("'string'", 'string'), ('\n var1 = ', 'normal'),
868 ("'selected'", 'hilite'), ('\n var2 = ', 'normal'),
869 ("'found'", 'hit'), ('\n var3 = ', 'normal'),
870 ('list', 'builtin'), ('(', 'normal'),
871 ('None', 'keyword'), (')\n', 'normal'),
872 (' breakpoint("line")', 'break'), ('\n\n', 'normal'),
Terry Jan Reedyb2229552019-07-28 12:04:31 -0400873 ('>>>', 'console'), (' 3.14**2\n', 'normal'),
874 ('9.8596', 'stdout'), ('\n', 'normal'),
875 ('>>>', 'console'), (' pri ', 'normal'),
876 ('n', 'error'), ('t(\n', 'normal'),
877 ('SyntaxError', 'stderr'), ('\n', 'normal'))
878 for string, tag in string_tags:
879 text.insert(END, string, tag)
Tal Einat7123ea02019-07-23 15:22:11 +0300880 n_lines = len(text.get('1.0', END).splitlines())
Tal Einat3221a632019-07-27 19:57:48 +0300881 for lineno in range(1, n_lines):
Tal Einat7123ea02019-07-23 15:22:11 +0300882 text.insert(f'{lineno}.0',
883 f'{lineno:{len(str(n_lines))}d} ',
884 'linenumber')
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400885 for element in self.theme_elements:
886 def tem(event, elem=element):
Cheryl Sabella8f7a7982017-08-19 22:04:40 -0400887 # event.widget.winfo_top_level().highlight_target.set(elem)
888 self.highlight_target.set(elem)
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400889 text.tag_bind(
890 self.theme_elements[element][0], '<ButtonPress-1>', tem)
Cheryl Sabella7028e592017-08-26 14:26:02 -0400891 text['state'] = 'disabled'
892 self.style.configure('frame_color_set.TFrame', borderwidth=1,
893 relief='solid')
894 self.frame_color_set = Frame(frame_custom, style='frame_color_set.TFrame')
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400895 frame_fg_bg_toggle = Frame(frame_custom)
896 self.button_set_color = Button(
897 self.frame_color_set, text='Choose Color for :',
Cheryl Sabella7028e592017-08-26 14:26:02 -0400898 command=self.get_color)
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400899 self.targetlist = DynOptionMenu(
900 self.frame_color_set, self.highlight_target, None,
901 highlightthickness=0) #, command=self.set_highlight_targetBinding
902 self.fg_on = Radiobutton(
903 frame_fg_bg_toggle, variable=self.fg_bg_toggle, value=1,
904 text='Foreground', command=self.set_color_sample_binding)
905 self.bg_on = Radiobutton(
906 frame_fg_bg_toggle, variable=self.fg_bg_toggle, value=0,
907 text='Background', command=self.set_color_sample_binding)
908 self.fg_bg_toggle.set(1)
909 self.button_save_custom = Button(
910 frame_custom, text='Save as New Custom Theme',
911 command=self.save_as_new_theme)
912 # frame_theme.
913 theme_type_title = Label(frame_theme, text='Select : ')
914 self.builtin_theme_on = Radiobutton(
915 frame_theme, variable=self.theme_source, value=1,
916 command=self.set_theme_type, text='a Built-in Theme')
917 self.custom_theme_on = Radiobutton(
918 frame_theme, variable=self.theme_source, value=0,
919 command=self.set_theme_type, text='a Custom Theme')
920 self.builtinlist = DynOptionMenu(
921 frame_theme, self.builtin_name, None, command=None)
922 self.customlist = DynOptionMenu(
923 frame_theme, self.custom_name, None, command=None)
924 self.button_delete_custom = Button(
925 frame_theme, text='Delete Custom Theme',
926 command=self.delete_custom)
Cheryl Sabella7028e592017-08-26 14:26:02 -0400927 self.theme_message = Label(frame_theme, borderwidth=2)
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400928 # Pack widgets:
929 # body.
930 frame_custom.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
wohlganger58fc71c2017-09-10 16:19:47 -0500931 frame_theme.pack(side=TOP, padx=5, pady=5, fill=X)
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400932 # frame_custom.
Tal Einat3221a632019-07-27 19:57:48 +0300933 self.frame_color_set.pack(side=TOP, padx=5, pady=5, fill=X)
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400934 frame_fg_bg_toggle.pack(side=TOP, padx=5, pady=0)
Tal Einat3221a632019-07-27 19:57:48 +0300935 sample_frame.pack(
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400936 side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
937 self.button_set_color.pack(side=TOP, expand=TRUE, fill=X, padx=8, pady=4)
938 self.targetlist.pack(side=TOP, expand=TRUE, fill=X, padx=8, pady=3)
939 self.fg_on.pack(side=LEFT, anchor=E)
940 self.bg_on.pack(side=RIGHT, anchor=W)
941 self.button_save_custom.pack(side=BOTTOM, fill=X, padx=5, pady=5)
942 # frame_theme.
943 theme_type_title.pack(side=TOP, anchor=W, padx=5, pady=5)
944 self.builtin_theme_on.pack(side=TOP, anchor=W, padx=5)
945 self.custom_theme_on.pack(side=TOP, anchor=W, padx=5, pady=2)
946 self.builtinlist.pack(side=TOP, fill=X, padx=5, pady=5)
947 self.customlist.pack(side=TOP, fill=X, anchor=W, padx=5, pady=5)
948 self.button_delete_custom.pack(side=TOP, fill=X, padx=5, pady=5)
949 self.theme_message.pack(side=TOP, fill=X, pady=5)
950
951 def load_theme_cfg(self):
952 """Load current configuration settings for the theme options.
953
954 Based on the theme_source toggle, the theme is set as
955 either builtin or custom and the initial widget values
956 reflect the current settings from idleConf.
957
958 Attributes updated:
959 theme_source: Set from idleConf.
960 builtinlist: List of default themes from idleConf.
961 customlist: List of custom themes from idleConf.
962 custom_theme_on: Disabled if there are no custom themes.
963 custom_theme: Message with additional information.
964 targetlist: Create menu from self.theme_elements.
965
966 Methods:
967 set_theme_type
968 paint_theme_sample
969 set_highlight_target
970 """
971 # Set current theme type radiobutton.
972 self.theme_source.set(idleConf.GetOption(
973 'main', 'Theme', 'default', type='bool', default=1))
974 # Set current theme.
975 current_option = idleConf.CurrentTheme()
976 # Load available theme option menus.
977 if self.theme_source.get(): # Default theme selected.
978 item_list = idleConf.GetSectionList('default', 'highlight')
979 item_list.sort()
980 self.builtinlist.SetMenu(item_list, current_option)
981 item_list = idleConf.GetSectionList('user', 'highlight')
982 item_list.sort()
983 if not item_list:
Cheryl Sabella7028e592017-08-26 14:26:02 -0400984 self.custom_theme_on.state(('disabled',))
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400985 self.custom_name.set('- no custom themes -')
986 else:
987 self.customlist.SetMenu(item_list, item_list[0])
988 else: # User theme selected.
989 item_list = idleConf.GetSectionList('user', 'highlight')
990 item_list.sort()
991 self.customlist.SetMenu(item_list, current_option)
992 item_list = idleConf.GetSectionList('default', 'highlight')
993 item_list.sort()
994 self.builtinlist.SetMenu(item_list, item_list[0])
995 self.set_theme_type()
996 # Load theme element option menu.
997 theme_names = list(self.theme_elements.keys())
998 theme_names.sort(key=lambda x: self.theme_elements[x][1])
999 self.targetlist.SetMenu(theme_names, theme_names[0])
1000 self.paint_theme_sample()
1001 self.set_highlight_target()
1002
1003 def var_changed_builtin_name(self, *params):
1004 """Process new builtin theme selection.
1005
1006 Add the changed theme's name to the changed_items and recreate
1007 the sample with the values from the selected theme.
1008 """
1009 old_themes = ('IDLE Classic', 'IDLE New')
1010 value = self.builtin_name.get()
1011 if value not in old_themes:
1012 if idleConf.GetOption('main', 'Theme', 'name') not in old_themes:
1013 changes.add_option('main', 'Theme', 'name', old_themes[0])
1014 changes.add_option('main', 'Theme', 'name2', value)
1015 self.theme_message['text'] = 'New theme, see Help'
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001016 else:
1017 changes.add_option('main', 'Theme', 'name', value)
1018 changes.add_option('main', 'Theme', 'name2', '')
1019 self.theme_message['text'] = ''
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001020 self.paint_theme_sample()
1021
1022 def var_changed_custom_name(self, *params):
1023 """Process new custom theme selection.
1024
1025 If a new custom theme is selected, add the name to the
1026 changed_items and apply the theme to the sample.
1027 """
1028 value = self.custom_name.get()
1029 if value != '- no custom themes -':
1030 changes.add_option('main', 'Theme', 'name', value)
1031 self.paint_theme_sample()
1032
1033 def var_changed_theme_source(self, *params):
1034 """Process toggle between builtin and custom theme.
1035
1036 Update the default toggle value and apply the newly
1037 selected theme type.
1038 """
1039 value = self.theme_source.get()
1040 changes.add_option('main', 'Theme', 'default', value)
1041 if value:
1042 self.var_changed_builtin_name()
1043 else:
1044 self.var_changed_custom_name()
1045
1046 def var_changed_color(self, *params):
1047 "Process change to color choice."
1048 self.on_new_color_set()
1049
1050 def var_changed_highlight_target(self, *params):
1051 "Process selection of new target tag for highlighting."
1052 self.set_highlight_target()
1053
1054 def set_theme_type(self):
1055 """Set available screen options based on builtin or custom theme.
1056
1057 Attributes accessed:
1058 theme_source
1059
1060 Attributes updated:
1061 builtinlist
1062 customlist
1063 button_delete_custom
1064 custom_theme_on
1065
1066 Called from:
1067 handler for builtin_theme_on and custom_theme_on
1068 delete_custom
1069 create_new
1070 load_theme_cfg
1071 """
1072 if self.theme_source.get():
Cheryl Sabella7028e592017-08-26 14:26:02 -04001073 self.builtinlist['state'] = 'normal'
1074 self.customlist['state'] = 'disabled'
1075 self.button_delete_custom.state(('disabled',))
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001076 else:
Cheryl Sabella7028e592017-08-26 14:26:02 -04001077 self.builtinlist['state'] = 'disabled'
1078 self.custom_theme_on.state(('!disabled',))
1079 self.customlist['state'] = 'normal'
1080 self.button_delete_custom.state(('!disabled',))
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001081
1082 def get_color(self):
1083 """Handle button to select a new color for the target tag.
1084
1085 If a new color is selected while using a builtin theme, a
1086 name must be supplied to create a custom theme.
1087
1088 Attributes accessed:
1089 highlight_target
1090 frame_color_set
1091 theme_source
1092
1093 Attributes updated:
1094 color
1095
1096 Methods:
1097 get_new_theme_name
1098 create_new
1099 """
1100 target = self.highlight_target.get()
Cheryl Sabella7028e592017-08-26 14:26:02 -04001101 prev_color = self.style.lookup(self.frame_color_set['style'],
1102 'background')
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001103 rgbTuplet, color_string = tkColorChooser.askcolor(
1104 parent=self, title='Pick new color for : '+target,
1105 initialcolor=prev_color)
1106 if color_string and (color_string != prev_color):
1107 # User didn't cancel and they chose a new color.
1108 if self.theme_source.get(): # Current theme is a built-in.
1109 message = ('Your changes will be saved as a new Custom Theme. '
1110 'Enter a name for your new Custom Theme below.')
1111 new_theme = self.get_new_theme_name(message)
1112 if not new_theme: # User cancelled custom theme creation.
1113 return
1114 else: # Create new custom theme based on previously active theme.
1115 self.create_new(new_theme)
1116 self.color.set(color_string)
1117 else: # Current theme is user defined.
1118 self.color.set(color_string)
1119
1120 def on_new_color_set(self):
1121 "Display sample of new color selection on the dialog."
1122 new_color = self.color.get()
Cheryl Sabella7028e592017-08-26 14:26:02 -04001123 self.style.configure('frame_color_set.TFrame', background=new_color)
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001124 plane = 'foreground' if self.fg_bg_toggle.get() else 'background'
1125 sample_element = self.theme_elements[self.highlight_target.get()][0]
1126 self.highlight_sample.tag_config(sample_element, **{plane: new_color})
1127 theme = self.custom_name.get()
1128 theme_element = sample_element + '-' + plane
1129 changes.add_option('highlight', theme, theme_element, new_color)
1130
1131 def get_new_theme_name(self, message):
1132 "Return name of new theme from query popup."
1133 used_names = (idleConf.GetSectionList('user', 'highlight') +
1134 idleConf.GetSectionList('default', 'highlight'))
1135 new_theme = SectionName(
1136 self, 'New Custom Theme', message, used_names).result
1137 return new_theme
1138
1139 def save_as_new_theme(self):
1140 """Prompt for new theme name and create the theme.
1141
1142 Methods:
1143 get_new_theme_name
1144 create_new
1145 """
1146 new_theme_name = self.get_new_theme_name('New Theme Name:')
1147 if new_theme_name:
1148 self.create_new(new_theme_name)
1149
1150 def create_new(self, new_theme_name):
1151 """Create a new custom theme with the given name.
1152
1153 Create the new theme based on the previously active theme
1154 with the current changes applied. Once it is saved, then
1155 activate the new theme.
1156
1157 Attributes accessed:
1158 builtin_name
1159 custom_name
1160
1161 Attributes updated:
1162 customlist
1163 theme_source
1164
1165 Method:
1166 save_new
1167 set_theme_type
1168 """
1169 if self.theme_source.get():
1170 theme_type = 'default'
1171 theme_name = self.builtin_name.get()
1172 else:
1173 theme_type = 'user'
1174 theme_name = self.custom_name.get()
1175 new_theme = idleConf.GetThemeDict(theme_type, theme_name)
1176 # Apply any of the old theme's unsaved changes to the new theme.
1177 if theme_name in changes['highlight']:
1178 theme_changes = changes['highlight'][theme_name]
1179 for element in theme_changes:
1180 new_theme[element] = theme_changes[element]
1181 # Save the new theme.
1182 self.save_new(new_theme_name, new_theme)
1183 # Change GUI over to the new theme.
1184 custom_theme_list = idleConf.GetSectionList('user', 'highlight')
1185 custom_theme_list.sort()
1186 self.customlist.SetMenu(custom_theme_list, new_theme_name)
1187 self.theme_source.set(0)
1188 self.set_theme_type()
1189
1190 def set_highlight_target(self):
1191 """Set fg/bg toggle and color based on highlight tag target.
1192
1193 Instance variables accessed:
1194 highlight_target
1195
1196 Attributes updated:
1197 fg_on
1198 bg_on
1199 fg_bg_toggle
1200
1201 Methods:
1202 set_color_sample
1203
1204 Called from:
1205 var_changed_highlight_target
1206 load_theme_cfg
1207 """
1208 if self.highlight_target.get() == 'Cursor': # bg not possible
Cheryl Sabella7028e592017-08-26 14:26:02 -04001209 self.fg_on.state(('disabled',))
1210 self.bg_on.state(('disabled',))
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001211 self.fg_bg_toggle.set(1)
1212 else: # Both fg and bg can be set.
Cheryl Sabella7028e592017-08-26 14:26:02 -04001213 self.fg_on.state(('!disabled',))
1214 self.bg_on.state(('!disabled',))
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001215 self.fg_bg_toggle.set(1)
1216 self.set_color_sample()
1217
1218 def set_color_sample_binding(self, *args):
1219 """Change color sample based on foreground/background toggle.
1220
1221 Methods:
1222 set_color_sample
1223 """
1224 self.set_color_sample()
1225
1226 def set_color_sample(self):
1227 """Set the color of the frame background to reflect the selected target.
1228
1229 Instance variables accessed:
1230 theme_elements
1231 highlight_target
1232 fg_bg_toggle
1233 highlight_sample
1234
1235 Attributes updated:
1236 frame_color_set
1237 """
1238 # Set the color sample area.
1239 tag = self.theme_elements[self.highlight_target.get()][0]
1240 plane = 'foreground' if self.fg_bg_toggle.get() else 'background'
1241 color = self.highlight_sample.tag_cget(tag, plane)
Cheryl Sabella7028e592017-08-26 14:26:02 -04001242 self.style.configure('frame_color_set.TFrame', background=color)
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001243
1244 def paint_theme_sample(self):
1245 """Apply the theme colors to each element tag in the sample text.
1246
1247 Instance attributes accessed:
1248 theme_elements
1249 theme_source
1250 builtin_name
1251 custom_name
1252
1253 Attributes updated:
1254 highlight_sample: Set the tag elements to the theme.
1255
1256 Methods:
1257 set_color_sample
1258
1259 Called from:
1260 var_changed_builtin_name
1261 var_changed_custom_name
1262 load_theme_cfg
1263 """
1264 if self.theme_source.get(): # Default theme
1265 theme = self.builtin_name.get()
1266 else: # User theme
1267 theme = self.custom_name.get()
1268 for element_title in self.theme_elements:
1269 element = self.theme_elements[element_title][0]
1270 colors = idleConf.GetHighlight(theme, element)
1271 if element == 'cursor': # Cursor sample needs special painting.
1272 colors['background'] = idleConf.GetHighlight(
Terry Jan Reedyc1419572019-03-22 18:23:41 -04001273 theme, 'normal')['background']
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001274 # Handle any unsaved changes to this theme.
1275 if theme in changes['highlight']:
1276 theme_dict = changes['highlight'][theme]
1277 if element + '-foreground' in theme_dict:
1278 colors['foreground'] = theme_dict[element + '-foreground']
1279 if element + '-background' in theme_dict:
1280 colors['background'] = theme_dict[element + '-background']
1281 self.highlight_sample.tag_config(element, **colors)
1282 self.set_color_sample()
1283
1284 def save_new(self, theme_name, theme):
1285 """Save a newly created theme to idleConf.
1286
1287 theme_name - string, the name of the new theme
1288 theme - dictionary containing the new theme
1289 """
Cheryl Sabelladd023ad2020-01-27 17:15:56 -05001290 idleConf.userCfg['highlight'].AddSection(theme_name)
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001291 for element in theme:
1292 value = theme[element]
1293 idleConf.userCfg['highlight'].SetOption(theme_name, element, value)
1294
Terry Jan Reedy3457f422017-08-27 16:39:41 -04001295 def askyesno(self, *args, **kwargs):
1296 # Make testing easier. Could change implementation.
Terry Jan Reedy0efc7c62017-09-17 20:13:25 -04001297 return messagebox.askyesno(*args, **kwargs)
Terry Jan Reedy3457f422017-08-27 16:39:41 -04001298
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001299 def delete_custom(self):
1300 """Handle event to delete custom theme.
1301
1302 The current theme is deactivated and the default theme is
1303 activated. The custom theme is permanently removed from
1304 the config file.
1305
1306 Attributes accessed:
1307 custom_name
1308
1309 Attributes updated:
1310 custom_theme_on
1311 customlist
1312 theme_source
1313 builtin_name
1314
1315 Methods:
1316 deactivate_current_config
1317 save_all_changed_extensions
1318 activate_config_changes
1319 set_theme_type
1320 """
1321 theme_name = self.custom_name.get()
1322 delmsg = 'Are you sure you wish to delete the theme %r ?'
Terry Jan Reedy3457f422017-08-27 16:39:41 -04001323 if not self.askyesno(
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001324 'Delete Theme', delmsg % theme_name, parent=self):
1325 return
Cheryl Sabella8f7a7982017-08-19 22:04:40 -04001326 self.cd.deactivate_current_config()
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001327 # Remove theme from changes, config, and file.
1328 changes.delete_section('highlight', theme_name)
1329 # Reload user theme list.
1330 item_list = idleConf.GetSectionList('user', 'highlight')
1331 item_list.sort()
1332 if not item_list:
Cheryl Sabella7028e592017-08-26 14:26:02 -04001333 self.custom_theme_on.state(('disabled',))
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001334 self.customlist.SetMenu(item_list, '- no custom themes -')
1335 else:
1336 self.customlist.SetMenu(item_list, item_list[0])
1337 # Revert to default theme.
1338 self.theme_source.set(idleConf.defaultCfg['main'].Get('Theme', 'default'))
1339 self.builtin_name.set(idleConf.defaultCfg['main'].Get('Theme', 'name'))
1340 # User can't back out of these changes, they must be applied now.
1341 changes.save_all()
Cheryl Sabella8f7a7982017-08-19 22:04:40 -04001342 self.cd.save_all_changed_extensions()
1343 self.cd.activate_config_changes()
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001344 self.set_theme_type()
1345
1346
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001347class KeysPage(Frame):
1348
1349 def __init__(self, master):
1350 super().__init__(master)
Mark Rosemanc579ad12020-10-24 16:45:00 -07001351 self.cd = master.winfo_toplevel()
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001352 self.create_page_keys()
1353 self.load_key_cfg()
1354
1355 def create_page_keys(self):
1356 """Return frame of widgets for Keys tab.
1357
1358 Enable users to provisionally change both individual and sets of
1359 keybindings (shortcut keys). Except for features implemented as
1360 extensions, keybindings are stored in complete sets called
1361 keysets. Built-in keysets in idlelib/config-keys.def are fixed
1362 as far as the dialog is concerned. Any keyset can be used as the
1363 base for a new custom keyset, stored in .idlerc/config-keys.cfg.
1364
1365 Function load_key_cfg() initializes tk variables and keyset
1366 lists and calls load_keys_list for the current keyset.
1367 Radiobuttons builtin_keyset_on and custom_keyset_on toggle var
1368 keyset_source, which controls if the current set of keybindings
1369 are from a builtin or custom keyset. DynOptionMenus builtinlist
1370 and customlist contain lists of the builtin and custom keysets,
1371 respectively, and the current item from each list is stored in
1372 vars builtin_name and custom_name.
1373
1374 Button delete_custom_keys invokes delete_custom_keys() to delete
1375 a custom keyset from idleConf.userCfg['keys'] and changes. Button
1376 save_custom_keys invokes save_as_new_key_set() which calls
1377 get_new_keys_name() and create_new_key_set() to save a custom keyset
1378 and its keybindings to idleConf.userCfg['keys'].
1379
1380 Listbox bindingslist contains all of the keybindings for the
1381 selected keyset. The keybindings are loaded in load_keys_list()
1382 and are pairs of (event, [keys]) where keys can be a list
1383 of one or more key combinations to bind to the same event.
1384 Mouse button 1 click invokes on_bindingslist_select(), which
1385 allows button_new_keys to be clicked.
1386
1387 So, an item is selected in listbindings, which activates
1388 button_new_keys, and clicking button_new_keys calls function
1389 get_new_keys(). Function get_new_keys() gets the key mappings from the
1390 current keyset for the binding event item that was selected. The
1391 function then displays another dialog, GetKeysDialog, with the
Cheryl Sabella82aff622017-08-17 20:39:00 -04001392 selected binding event and current keys and allows new key sequences
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001393 to be entered for that binding event. If the keys aren't
1394 changed, nothing happens. If the keys are changed and the keyset
1395 is a builtin, function get_new_keys_name() will be called
1396 for input of a custom keyset name. If no name is given, then the
1397 change to the keybinding will abort and no updates will be made. If
1398 a custom name is entered in the prompt or if the current keyset was
1399 already custom (and thus didn't require a prompt), then
1400 idleConf.userCfg['keys'] is updated in function create_new_key_set()
1401 with the change to the event binding. The item listing in bindingslist
1402 is updated with the new keys. Var keybinding is also set which invokes
1403 the callback function, var_changed_keybinding, to add the change to
1404 the 'keys' or 'extensions' changes tracker based on the binding type.
1405
1406 Tk Variables:
1407 keybinding: Action/key bindings.
1408
1409 Methods:
1410 load_keys_list: Reload active set.
1411 create_new_key_set: Combine active keyset and changes.
1412 set_keys_type: Command for keyset_source.
1413 save_new_key_set: Save to idleConf.userCfg['keys'] (is function).
1414 deactivate_current_config: Remove keys bindings in editors.
1415
1416 Widgets for KeysPage(frame): (*) widgets bound to self
1417 frame_key_sets: LabelFrame
1418 frames[0]: Frame
1419 (*)builtin_keyset_on: Radiobutton - var keyset_source
1420 (*)custom_keyset_on: Radiobutton - var keyset_source
1421 (*)builtinlist: DynOptionMenu - var builtin_name,
1422 func keybinding_selected
1423 (*)customlist: DynOptionMenu - var custom_name,
1424 func keybinding_selected
1425 (*)keys_message: Label
1426 frames[1]: Frame
1427 (*)button_delete_custom_keys: Button - delete_custom_keys
1428 (*)button_save_custom_keys: Button - save_as_new_key_set
1429 frame_custom: LabelFrame
1430 frame_target: Frame
1431 target_title: Label
1432 scroll_target_y: Scrollbar
1433 scroll_target_x: Scrollbar
1434 (*)bindingslist: ListBox - on_bindingslist_select
1435 (*)button_new_keys: Button - get_new_keys & ..._name
1436 """
1437 self.builtin_name = tracers.add(
1438 StringVar(self), self.var_changed_builtin_name)
1439 self.custom_name = tracers.add(
1440 StringVar(self), self.var_changed_custom_name)
1441 self.keyset_source = tracers.add(
1442 BooleanVar(self), self.var_changed_keyset_source)
1443 self.keybinding = tracers.add(
1444 StringVar(self), self.var_changed_keybinding)
1445
1446 # Create widgets:
1447 # body and section frames.
1448 frame_custom = LabelFrame(
1449 self, borderwidth=2, relief=GROOVE,
1450 text=' Custom Key Bindings ')
1451 frame_key_sets = LabelFrame(
1452 self, borderwidth=2, relief=GROOVE, text=' Key Set ')
1453 # frame_custom.
1454 frame_target = Frame(frame_custom)
1455 target_title = Label(frame_target, text='Action - Key(s)')
1456 scroll_target_y = Scrollbar(frame_target)
1457 scroll_target_x = Scrollbar(frame_target, orient=HORIZONTAL)
1458 self.bindingslist = Listbox(
1459 frame_target, takefocus=FALSE, exportselection=FALSE)
1460 self.bindingslist.bind('<ButtonRelease-1>',
1461 self.on_bindingslist_select)
1462 scroll_target_y['command'] = self.bindingslist.yview
1463 scroll_target_x['command'] = self.bindingslist.xview
1464 self.bindingslist['yscrollcommand'] = scroll_target_y.set
1465 self.bindingslist['xscrollcommand'] = scroll_target_x.set
1466 self.button_new_keys = Button(
1467 frame_custom, text='Get New Keys for Selection',
Terry Jan Reedye8f7c782017-11-28 21:52:32 -05001468 command=self.get_new_keys, state='disabled')
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001469 # frame_key_sets.
Cheryl Sabella7028e592017-08-26 14:26:02 -04001470 frames = [Frame(frame_key_sets, padding=2, borderwidth=0)
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001471 for i in range(2)]
1472 self.builtin_keyset_on = Radiobutton(
1473 frames[0], variable=self.keyset_source, value=1,
1474 command=self.set_keys_type, text='Use a Built-in Key Set')
1475 self.custom_keyset_on = Radiobutton(
1476 frames[0], variable=self.keyset_source, value=0,
1477 command=self.set_keys_type, text='Use a Custom Key Set')
1478 self.builtinlist = DynOptionMenu(
1479 frames[0], self.builtin_name, None, command=None)
1480 self.customlist = DynOptionMenu(
1481 frames[0], self.custom_name, None, command=None)
1482 self.button_delete_custom_keys = Button(
1483 frames[1], text='Delete Custom Key Set',
1484 command=self.delete_custom_keys)
1485 self.button_save_custom_keys = Button(
1486 frames[1], text='Save as New Custom Key Set',
1487 command=self.save_as_new_key_set)
Cheryl Sabella7028e592017-08-26 14:26:02 -04001488 self.keys_message = Label(frames[0], borderwidth=2)
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001489
1490 # Pack widgets:
1491 # body.
1492 frame_custom.pack(side=BOTTOM, padx=5, pady=5, expand=TRUE, fill=BOTH)
1493 frame_key_sets.pack(side=BOTTOM, padx=5, pady=5, fill=BOTH)
1494 # frame_custom.
1495 self.button_new_keys.pack(side=BOTTOM, fill=X, padx=5, pady=5)
1496 frame_target.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
1497 # frame_target.
1498 frame_target.columnconfigure(0, weight=1)
1499 frame_target.rowconfigure(1, weight=1)
1500 target_title.grid(row=0, column=0, columnspan=2, sticky=W)
1501 self.bindingslist.grid(row=1, column=0, sticky=NSEW)
1502 scroll_target_y.grid(row=1, column=1, sticky=NS)
1503 scroll_target_x.grid(row=2, column=0, sticky=EW)
1504 # frame_key_sets.
1505 self.builtin_keyset_on.grid(row=0, column=0, sticky=W+NS)
1506 self.custom_keyset_on.grid(row=1, column=0, sticky=W+NS)
1507 self.builtinlist.grid(row=0, column=1, sticky=NSEW)
1508 self.customlist.grid(row=1, column=1, sticky=NSEW)
1509 self.keys_message.grid(row=0, column=2, sticky=NSEW, padx=5, pady=5)
1510 self.button_delete_custom_keys.pack(side=LEFT, fill=X, expand=True, padx=2)
1511 self.button_save_custom_keys.pack(side=LEFT, fill=X, expand=True, padx=2)
1512 frames[0].pack(side=TOP, fill=BOTH, expand=True)
1513 frames[1].pack(side=TOP, fill=X, expand=True, pady=2)
1514
1515 def load_key_cfg(self):
1516 "Load current configuration settings for the keybinding options."
1517 # Set current keys type radiobutton.
1518 self.keyset_source.set(idleConf.GetOption(
1519 'main', 'Keys', 'default', type='bool', default=1))
1520 # Set current keys.
1521 current_option = idleConf.CurrentKeys()
1522 # Load available keyset option menus.
1523 if self.keyset_source.get(): # Default theme selected.
1524 item_list = idleConf.GetSectionList('default', 'keys')
1525 item_list.sort()
1526 self.builtinlist.SetMenu(item_list, current_option)
1527 item_list = idleConf.GetSectionList('user', 'keys')
1528 item_list.sort()
1529 if not item_list:
Cheryl Sabella7028e592017-08-26 14:26:02 -04001530 self.custom_keyset_on.state(('disabled',))
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001531 self.custom_name.set('- no custom keys -')
1532 else:
1533 self.customlist.SetMenu(item_list, item_list[0])
1534 else: # User key set selected.
1535 item_list = idleConf.GetSectionList('user', 'keys')
1536 item_list.sort()
1537 self.customlist.SetMenu(item_list, current_option)
1538 item_list = idleConf.GetSectionList('default', 'keys')
1539 item_list.sort()
1540 self.builtinlist.SetMenu(item_list, idleConf.default_keys())
1541 self.set_keys_type()
1542 # Load keyset element list.
1543 keyset_name = idleConf.CurrentKeys()
1544 self.load_keys_list(keyset_name)
1545
1546 def var_changed_builtin_name(self, *params):
1547 "Process selection of builtin key set."
1548 old_keys = (
1549 'IDLE Classic Windows',
1550 'IDLE Classic Unix',
1551 'IDLE Classic Mac',
1552 'IDLE Classic OSX',
1553 )
1554 value = self.builtin_name.get()
1555 if value not in old_keys:
1556 if idleConf.GetOption('main', 'Keys', 'name') not in old_keys:
1557 changes.add_option('main', 'Keys', 'name', old_keys[0])
1558 changes.add_option('main', 'Keys', 'name2', value)
1559 self.keys_message['text'] = 'New key set, see Help'
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001560 else:
1561 changes.add_option('main', 'Keys', 'name', value)
1562 changes.add_option('main', 'Keys', 'name2', '')
1563 self.keys_message['text'] = ''
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001564 self.load_keys_list(value)
1565
1566 def var_changed_custom_name(self, *params):
1567 "Process selection of custom key set."
1568 value = self.custom_name.get()
1569 if value != '- no custom keys -':
1570 changes.add_option('main', 'Keys', 'name', value)
1571 self.load_keys_list(value)
1572
1573 def var_changed_keyset_source(self, *params):
1574 "Process toggle between builtin key set and custom key set."
1575 value = self.keyset_source.get()
1576 changes.add_option('main', 'Keys', 'default', value)
1577 if value:
1578 self.var_changed_builtin_name()
1579 else:
1580 self.var_changed_custom_name()
1581
1582 def var_changed_keybinding(self, *params):
1583 "Store change to a keybinding."
1584 value = self.keybinding.get()
1585 key_set = self.custom_name.get()
1586 event = self.bindingslist.get(ANCHOR).split()[0]
1587 if idleConf.IsCoreBinding(event):
1588 changes.add_option('keys', key_set, event, value)
1589 else: # Event is an extension binding.
1590 ext_name = idleConf.GetExtnNameForEvent(event)
1591 ext_keybind_section = ext_name + '_cfgBindings'
1592 changes.add_option('extensions', ext_keybind_section, event, value)
1593
1594 def set_keys_type(self):
1595 "Set available screen options based on builtin or custom key set."
1596 if self.keyset_source.get():
Cheryl Sabella7028e592017-08-26 14:26:02 -04001597 self.builtinlist['state'] = 'normal'
1598 self.customlist['state'] = 'disabled'
1599 self.button_delete_custom_keys.state(('disabled',))
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001600 else:
Cheryl Sabella7028e592017-08-26 14:26:02 -04001601 self.builtinlist['state'] = 'disabled'
1602 self.custom_keyset_on.state(('!disabled',))
1603 self.customlist['state'] = 'normal'
1604 self.button_delete_custom_keys.state(('!disabled',))
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001605
1606 def get_new_keys(self):
1607 """Handle event to change key binding for selected line.
1608
1609 A selection of a key/binding in the list of current
1610 bindings pops up a dialog to enter a new binding. If
1611 the current key set is builtin and a binding has
1612 changed, then a name for a custom key set needs to be
1613 entered for the change to be applied.
1614 """
1615 list_index = self.bindingslist.index(ANCHOR)
1616 binding = self.bindingslist.get(list_index)
1617 bind_name = binding.split()[0]
1618 if self.keyset_source.get():
1619 current_key_set_name = self.builtin_name.get()
1620 else:
1621 current_key_set_name = self.custom_name.get()
1622 current_bindings = idleConf.GetCurrentKeySet()
1623 if current_key_set_name in changes['keys']: # unsaved changes
1624 key_set_changes = changes['keys'][current_key_set_name]
1625 for event in key_set_changes:
1626 current_bindings[event] = key_set_changes[event].split()
1627 current_key_sequences = list(current_bindings.values())
1628 new_keys = GetKeysDialog(self, 'Get New Keys', bind_name,
1629 current_key_sequences).result
1630 if new_keys:
1631 if self.keyset_source.get(): # Current key set is a built-in.
1632 message = ('Your changes will be saved as a new Custom Key Set.'
1633 ' Enter a name for your new Custom Key Set below.')
1634 new_keyset = self.get_new_keys_name(message)
1635 if not new_keyset: # User cancelled custom key set creation.
1636 self.bindingslist.select_set(list_index)
1637 self.bindingslist.select_anchor(list_index)
1638 return
1639 else: # Create new custom key set based on previously active key set.
1640 self.create_new_key_set(new_keyset)
1641 self.bindingslist.delete(list_index)
1642 self.bindingslist.insert(list_index, bind_name+' - '+new_keys)
1643 self.bindingslist.select_set(list_index)
1644 self.bindingslist.select_anchor(list_index)
1645 self.keybinding.set(new_keys)
1646 else:
1647 self.bindingslist.select_set(list_index)
1648 self.bindingslist.select_anchor(list_index)
1649
1650 def get_new_keys_name(self, message):
1651 "Return new key set name from query popup."
1652 used_names = (idleConf.GetSectionList('user', 'keys') +
1653 idleConf.GetSectionList('default', 'keys'))
1654 new_keyset = SectionName(
1655 self, 'New Custom Key Set', message, used_names).result
1656 return new_keyset
1657
1658 def save_as_new_key_set(self):
1659 "Prompt for name of new key set and save changes using that name."
1660 new_keys_name = self.get_new_keys_name('New Key Set Name:')
1661 if new_keys_name:
1662 self.create_new_key_set(new_keys_name)
1663
1664 def on_bindingslist_select(self, event):
1665 "Activate button to assign new keys to selected action."
Cheryl Sabella7028e592017-08-26 14:26:02 -04001666 self.button_new_keys.state(('!disabled',))
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001667
1668 def create_new_key_set(self, new_key_set_name):
1669 """Create a new custom key set with the given name.
1670
1671 Copy the bindings/keys from the previously active keyset
1672 to the new keyset and activate the new custom keyset.
1673 """
1674 if self.keyset_source.get():
1675 prev_key_set_name = self.builtin_name.get()
1676 else:
1677 prev_key_set_name = self.custom_name.get()
1678 prev_keys = idleConf.GetCoreKeys(prev_key_set_name)
1679 new_keys = {}
1680 for event in prev_keys: # Add key set to changed items.
1681 event_name = event[2:-2] # Trim off the angle brackets.
1682 binding = ' '.join(prev_keys[event])
1683 new_keys[event_name] = binding
1684 # Handle any unsaved changes to prev key set.
1685 if prev_key_set_name in changes['keys']:
1686 key_set_changes = changes['keys'][prev_key_set_name]
1687 for event in key_set_changes:
1688 new_keys[event] = key_set_changes[event]
1689 # Save the new key set.
1690 self.save_new_key_set(new_key_set_name, new_keys)
1691 # Change GUI over to the new key set.
1692 custom_key_list = idleConf.GetSectionList('user', 'keys')
1693 custom_key_list.sort()
1694 self.customlist.SetMenu(custom_key_list, new_key_set_name)
1695 self.keyset_source.set(0)
1696 self.set_keys_type()
1697
1698 def load_keys_list(self, keyset_name):
1699 """Reload the list of action/key binding pairs for the active key set.
1700
1701 An action/key binding can be selected to change the key binding.
1702 """
1703 reselect = False
1704 if self.bindingslist.curselection():
1705 reselect = True
1706 list_index = self.bindingslist.index(ANCHOR)
1707 keyset = idleConf.GetKeySet(keyset_name)
1708 bind_names = list(keyset.keys())
1709 bind_names.sort()
1710 self.bindingslist.delete(0, END)
1711 for bind_name in bind_names:
1712 key = ' '.join(keyset[bind_name])
1713 bind_name = bind_name[2:-2] # Trim off the angle brackets.
1714 if keyset_name in changes['keys']:
1715 # Handle any unsaved changes to this key set.
1716 if bind_name in changes['keys'][keyset_name]:
1717 key = changes['keys'][keyset_name][bind_name]
1718 self.bindingslist.insert(END, bind_name+' - '+key)
1719 if reselect:
1720 self.bindingslist.see(list_index)
1721 self.bindingslist.select_set(list_index)
1722 self.bindingslist.select_anchor(list_index)
1723
1724 @staticmethod
1725 def save_new_key_set(keyset_name, keyset):
1726 """Save a newly created core key set.
1727
1728 Add keyset to idleConf.userCfg['keys'], not to disk.
1729 If the keyset doesn't exist, it is created. The
1730 binding/keys are taken from the keyset argument.
1731
1732 keyset_name - string, the name of the new key set
1733 keyset - dictionary containing the new keybindings
1734 """
Cheryl Sabelladd023ad2020-01-27 17:15:56 -05001735 idleConf.userCfg['keys'].AddSection(keyset_name)
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001736 for event in keyset:
1737 value = keyset[event]
1738 idleConf.userCfg['keys'].SetOption(keyset_name, event, value)
1739
Terry Jan Reedy3457f422017-08-27 16:39:41 -04001740 def askyesno(self, *args, **kwargs):
1741 # Make testing easier. Could change implementation.
Terry Jan Reedy0efc7c62017-09-17 20:13:25 -04001742 return messagebox.askyesno(*args, **kwargs)
Terry Jan Reedy3457f422017-08-27 16:39:41 -04001743
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001744 def delete_custom_keys(self):
1745 """Handle event to delete a custom key set.
1746
1747 Applying the delete deactivates the current configuration and
1748 reverts to the default. The custom key set is permanently
1749 deleted from the config file.
1750 """
1751 keyset_name = self.custom_name.get()
1752 delmsg = 'Are you sure you wish to delete the key set %r ?'
Terry Jan Reedy3457f422017-08-27 16:39:41 -04001753 if not self.askyesno(
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001754 'Delete Key Set', delmsg % keyset_name, parent=self):
1755 return
1756 self.cd.deactivate_current_config()
1757 # Remove key set from changes, config, and file.
1758 changes.delete_section('keys', keyset_name)
1759 # Reload user key set list.
1760 item_list = idleConf.GetSectionList('user', 'keys')
1761 item_list.sort()
1762 if not item_list:
Cheryl Sabella7028e592017-08-26 14:26:02 -04001763 self.custom_keyset_on.state(('disabled',))
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001764 self.customlist.SetMenu(item_list, '- no custom keys -')
1765 else:
1766 self.customlist.SetMenu(item_list, item_list[0])
1767 # Revert to default key set.
1768 self.keyset_source.set(idleConf.defaultCfg['main']
Tal Einat604e7b92018-09-25 15:10:14 +03001769 .Get('Keys', 'default'))
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001770 self.builtin_name.set(idleConf.defaultCfg['main'].Get('Keys', 'name')
Tal Einat604e7b92018-09-25 15:10:14 +03001771 or idleConf.default_keys())
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001772 # User can't back out of these changes, they must be applied now.
1773 changes.save_all()
1774 self.cd.save_all_changed_extensions()
1775 self.cd.activate_config_changes()
1776 self.set_keys_type()
1777
1778
csabellae8eb17b2017-07-30 18:39:17 -04001779class GenPage(Frame):
1780
csabella6f446be2017-08-01 00:24:07 -04001781 def __init__(self, master):
1782 super().__init__(master)
Tal Einat1ebee372019-07-23 13:02:11 +03001783
1784 self.init_validators()
csabellae8eb17b2017-07-30 18:39:17 -04001785 self.create_page_general()
1786 self.load_general_cfg()
1787
Tal Einat1ebee372019-07-23 13:02:11 +03001788 def init_validators(self):
1789 digits_or_empty_re = re.compile(r'[0-9]*')
1790 def is_digits_or_empty(s):
1791 "Return 's is blank or contains only digits'"
1792 return digits_or_empty_re.fullmatch(s) is not None
1793 self.digits_only = (self.register(is_digits_or_empty), '%P',)
1794
csabellae8eb17b2017-07-30 18:39:17 -04001795 def create_page_general(self):
1796 """Return frame of widgets for General tab.
1797
1798 Enable users to provisionally change general options. Function
Terry Jan Reedy0acb6462019-07-30 18:14:58 -04001799 load_general_cfg initializes tk variables and helplist using
csabellae8eb17b2017-07-30 18:39:17 -04001800 idleConf. Radiobuttons startup_shell_on and startup_editor_on
1801 set var startup_edit. Radiobuttons save_ask_on and save_auto_on
1802 set var autosave. Entry boxes win_width_int and win_height_int
1803 set var win_width and win_height. Setting var_name invokes the
1804 default callback that adds option to changes.
1805
1806 Helplist: load_general_cfg loads list user_helplist with
1807 name, position pairs and copies names to listbox helplist.
1808 Clicking a name invokes help_source selected. Clicking
1809 button_helplist_name invokes helplist_item_name, which also
1810 changes user_helplist. These functions all call
1811 set_add_delete_state. All but load call update_help_changes to
1812 rewrite changes['main']['HelpFiles'].
1813
Cheryl Sabella2f896462017-08-14 21:21:43 -04001814 Widgets for GenPage(Frame): (*) widgets bound to self
Terry Jan Reedy390eadd2017-08-30 00:59:11 -04001815 frame_window: LabelFrame
1816 frame_run: Frame
1817 startup_title: Label
1818 (*)startup_editor_on: Radiobutton - startup_edit
1819 (*)startup_shell_on: Radiobutton - startup_edit
1820 frame_win_size: Frame
1821 win_size_title: Label
1822 win_width_title: Label
1823 (*)win_width_int: Entry - win_width
1824 win_height_title: Label
1825 (*)win_height_int: Entry - win_height
Zackery Spytz9c284492019-11-13 00:13:33 -07001826 frame_cursor_blink: Frame
1827 cursor_blink_title: Label
1828 (*)cursor_blink_bool: Checkbutton - cursor_blink
Cheryl Sabella845d8642018-02-04 18:15:21 -05001829 frame_autocomplete: Frame
1830 auto_wait_title: Label
1831 (*)auto_wait_int: Entry - autocomplete_wait
1832 frame_paren1: Frame
1833 paren_style_title: Label
1834 (*)paren_style_type: OptionMenu - paren_style
1835 frame_paren2: Frame
1836 paren_time_title: Label
1837 (*)paren_flash_time: Entry - flash_delay
1838 (*)bell_on: Checkbutton - paren_bell
Terry Jan Reedy390eadd2017-08-30 00:59:11 -04001839 frame_editor: LabelFrame
1840 frame_save: Frame
1841 run_save_title: Label
1842 (*)save_ask_on: Radiobutton - autosave
1843 (*)save_auto_on: Radiobutton - autosave
Cheryl Sabella845d8642018-02-04 18:15:21 -05001844 frame_format: Frame
1845 format_width_title: Label
1846 (*)format_width_int: Entry - format_width
Tal Einat7123ea02019-07-23 15:22:11 +03001847 frame_line_numbers_default: Frame
1848 line_numbers_default_title: Label
1849 (*)line_numbers_default_bool: Checkbutton - line_numbers_default
Cheryl Sabella845d8642018-02-04 18:15:21 -05001850 frame_context: Frame
1851 context_title: Label
1852 (*)context_int: Entry - context_lines
Tal Einat604e7b92018-09-25 15:10:14 +03001853 frame_shell: LabelFrame
1854 frame_auto_squeeze_min_lines: Frame
1855 auto_squeeze_min_lines_title: Label
1856 (*)auto_squeeze_min_lines_int: Entry - auto_squeeze_min_lines
Cheryl Sabella2f896462017-08-14 21:21:43 -04001857 frame_help: LabelFrame
1858 frame_helplist: Frame
1859 frame_helplist_buttons: Frame
1860 (*)button_helplist_edit
1861 (*)button_helplist_add
1862 (*)button_helplist_remove
1863 (*)helplist: ListBox
1864 scroll_helplist: Scrollbar
csabellae8eb17b2017-07-30 18:39:17 -04001865 """
wohlganger58fc71c2017-09-10 16:19:47 -05001866 # Integer values need StringVar because int('') raises.
csabellae8eb17b2017-07-30 18:39:17 -04001867 self.startup_edit = tracers.add(
1868 IntVar(self), ('main', 'General', 'editor-on-startup'))
csabellae8eb17b2017-07-30 18:39:17 -04001869 self.win_width = tracers.add(
1870 StringVar(self), ('main', 'EditorWindow', 'width'))
1871 self.win_height = tracers.add(
1872 StringVar(self), ('main', 'EditorWindow', 'height'))
Zackery Spytz9c284492019-11-13 00:13:33 -07001873 self.cursor_blink = tracers.add(
1874 BooleanVar(self), ('main', 'EditorWindow', 'cursor-blink'))
wohlganger58fc71c2017-09-10 16:19:47 -05001875 self.autocomplete_wait = tracers.add(
1876 StringVar(self), ('extensions', 'AutoComplete', 'popupwait'))
1877 self.paren_style = tracers.add(
1878 StringVar(self), ('extensions', 'ParenMatch', 'style'))
1879 self.flash_delay = tracers.add(
1880 StringVar(self), ('extensions', 'ParenMatch', 'flash-delay'))
1881 self.paren_bell = tracers.add(
1882 BooleanVar(self), ('extensions', 'ParenMatch', 'bell'))
csabellae8eb17b2017-07-30 18:39:17 -04001883
Tal Einat604e7b92018-09-25 15:10:14 +03001884 self.auto_squeeze_min_lines = tracers.add(
1885 StringVar(self), ('main', 'PyShell', 'auto-squeeze-min-lines'))
1886
wohlganger58fc71c2017-09-10 16:19:47 -05001887 self.autosave = tracers.add(
1888 IntVar(self), ('main', 'General', 'autosave'))
1889 self.format_width = tracers.add(
1890 StringVar(self), ('extensions', 'FormatParagraph', 'max-width'))
Tal Einat7123ea02019-07-23 15:22:11 +03001891 self.line_numbers_default = tracers.add(
1892 BooleanVar(self),
1893 ('main', 'EditorWindow', 'line-numbers-default'))
wohlganger58fc71c2017-09-10 16:19:47 -05001894 self.context_lines = tracers.add(
Cheryl Sabella29996a12018-06-01 19:23:00 -04001895 StringVar(self), ('extensions', 'CodeContext', 'maxlines'))
wohlganger58fc71c2017-09-10 16:19:47 -05001896
1897 # Create widgets:
csabellae8eb17b2017-07-30 18:39:17 -04001898 # Section frames.
Terry Jan Reedy390eadd2017-08-30 00:59:11 -04001899 frame_window = LabelFrame(self, borderwidth=2, relief=GROOVE,
1900 text=' Window Preferences')
1901 frame_editor = LabelFrame(self, borderwidth=2, relief=GROOVE,
1902 text=' Editor Preferences')
Tal Einat604e7b92018-09-25 15:10:14 +03001903 frame_shell = LabelFrame(self, borderwidth=2, relief=GROOVE,
1904 text=' Shell Preferences')
csabellae8eb17b2017-07-30 18:39:17 -04001905 frame_help = LabelFrame(self, borderwidth=2, relief=GROOVE,
Tal Einat604e7b92018-09-25 15:10:14 +03001906 text=' Additional Help Sources ')
Terry Jan Reedy390eadd2017-08-30 00:59:11 -04001907 # Frame_window.
1908 frame_run = Frame(frame_window, borderwidth=0)
csabellae8eb17b2017-07-30 18:39:17 -04001909 startup_title = Label(frame_run, text='At Startup')
1910 self.startup_editor_on = Radiobutton(
1911 frame_run, variable=self.startup_edit, value=1,
1912 text="Open Edit Window")
1913 self.startup_shell_on = Radiobutton(
1914 frame_run, variable=self.startup_edit, value=0,
1915 text='Open Shell Window')
Terry Jan Reedy390eadd2017-08-30 00:59:11 -04001916
wohlganger58fc71c2017-09-10 16:19:47 -05001917 frame_win_size = Frame(frame_window, borderwidth=0)
csabellae8eb17b2017-07-30 18:39:17 -04001918 win_size_title = Label(
1919 frame_win_size, text='Initial Window Size (in characters)')
1920 win_width_title = Label(frame_win_size, text='Width')
1921 self.win_width_int = Entry(
Tal Einat1ebee372019-07-23 13:02:11 +03001922 frame_win_size, textvariable=self.win_width, width=3,
1923 validatecommand=self.digits_only, validate='key',
1924 )
csabellae8eb17b2017-07-30 18:39:17 -04001925 win_height_title = Label(frame_win_size, text='Height')
1926 self.win_height_int = Entry(
Tal Einat1ebee372019-07-23 13:02:11 +03001927 frame_win_size, textvariable=self.win_height, width=3,
1928 validatecommand=self.digits_only, validate='key',
1929 )
Terry Jan Reedy390eadd2017-08-30 00:59:11 -04001930
Zackery Spytz9c284492019-11-13 00:13:33 -07001931 frame_cursor_blink = Frame(frame_window, borderwidth=0)
1932 cursor_blink_title = Label(frame_cursor_blink, text='Cursor Blink')
1933 self.cursor_blink_bool = Checkbutton(frame_cursor_blink,
1934 variable=self.cursor_blink, width=1)
1935
wohlganger58fc71c2017-09-10 16:19:47 -05001936 frame_autocomplete = Frame(frame_window, borderwidth=0,)
1937 auto_wait_title = Label(frame_autocomplete,
1938 text='Completions Popup Wait (milliseconds)')
1939 self.auto_wait_int = Entry(frame_autocomplete, width=6,
Tal Einat1ebee372019-07-23 13:02:11 +03001940 textvariable=self.autocomplete_wait,
1941 validatecommand=self.digits_only,
1942 validate='key',
1943 )
wohlganger58fc71c2017-09-10 16:19:47 -05001944
1945 frame_paren1 = Frame(frame_window, borderwidth=0)
1946 paren_style_title = Label(frame_paren1, text='Paren Match Style')
1947 self.paren_style_type = OptionMenu(
1948 frame_paren1, self.paren_style, 'expression',
1949 "opener","parens","expression")
1950 frame_paren2 = Frame(frame_window, borderwidth=0)
1951 paren_time_title = Label(
1952 frame_paren2, text='Time Match Displayed (milliseconds)\n'
1953 '(0 is until next input)')
1954 self.paren_flash_time = Entry(
1955 frame_paren2, textvariable=self.flash_delay, width=6)
1956 self.bell_on = Checkbutton(
1957 frame_paren2, text="Bell on Mismatch", variable=self.paren_bell)
1958
Terry Jan Reedy390eadd2017-08-30 00:59:11 -04001959 # Frame_editor.
1960 frame_save = Frame(frame_editor, borderwidth=0)
1961 run_save_title = Label(frame_save, text='At Start of Run (F5) ')
1962 self.save_ask_on = Radiobutton(
1963 frame_save, variable=self.autosave, value=0,
1964 text="Prompt to Save")
1965 self.save_auto_on = Radiobutton(
1966 frame_save, variable=self.autosave, value=1,
1967 text='No Prompt')
1968
wohlganger58fc71c2017-09-10 16:19:47 -05001969 frame_format = Frame(frame_editor, borderwidth=0)
1970 format_width_title = Label(frame_format,
1971 text='Format Paragraph Max Width')
1972 self.format_width_int = Entry(
Tal Einat1ebee372019-07-23 13:02:11 +03001973 frame_format, textvariable=self.format_width, width=4,
1974 validatecommand=self.digits_only, validate='key',
1975 )
wohlganger58fc71c2017-09-10 16:19:47 -05001976
Tal Einat7123ea02019-07-23 15:22:11 +03001977 frame_line_numbers_default = Frame(frame_editor, borderwidth=0)
1978 line_numbers_default_title = Label(
1979 frame_line_numbers_default, text='Show line numbers in new windows')
1980 self.line_numbers_default_bool = Checkbutton(
1981 frame_line_numbers_default,
1982 variable=self.line_numbers_default,
1983 width=1)
1984
wohlganger58fc71c2017-09-10 16:19:47 -05001985 frame_context = Frame(frame_editor, borderwidth=0)
Cheryl Sabella29996a12018-06-01 19:23:00 -04001986 context_title = Label(frame_context, text='Max Context Lines :')
wohlganger58fc71c2017-09-10 16:19:47 -05001987 self.context_int = Entry(
Tal Einat1ebee372019-07-23 13:02:11 +03001988 frame_context, textvariable=self.context_lines, width=3,
1989 validatecommand=self.digits_only, validate='key',
1990 )
wohlganger58fc71c2017-09-10 16:19:47 -05001991
Tal Einat604e7b92018-09-25 15:10:14 +03001992 # Frame_shell.
1993 frame_auto_squeeze_min_lines = Frame(frame_shell, borderwidth=0)
1994 auto_squeeze_min_lines_title = Label(frame_auto_squeeze_min_lines,
1995 text='Auto-Squeeze Min. Lines:')
1996 self.auto_squeeze_min_lines_int = Entry(
Tal Einat1ebee372019-07-23 13:02:11 +03001997 frame_auto_squeeze_min_lines, width=4,
1998 textvariable=self.auto_squeeze_min_lines,
1999 validatecommand=self.digits_only, validate='key',
2000 )
wohlganger58fc71c2017-09-10 16:19:47 -05002001
csabellae8eb17b2017-07-30 18:39:17 -04002002 # frame_help.
2003 frame_helplist = Frame(frame_help)
2004 frame_helplist_buttons = Frame(frame_helplist)
2005 self.helplist = Listbox(
2006 frame_helplist, height=5, takefocus=True,
2007 exportselection=FALSE)
2008 scroll_helplist = Scrollbar(frame_helplist)
2009 scroll_helplist['command'] = self.helplist.yview
2010 self.helplist['yscrollcommand'] = scroll_helplist.set
2011 self.helplist.bind('<ButtonRelease-1>', self.help_source_selected)
2012 self.button_helplist_edit = Button(
Cheryl Sabella7028e592017-08-26 14:26:02 -04002013 frame_helplist_buttons, text='Edit', state='disabled',
csabellae8eb17b2017-07-30 18:39:17 -04002014 width=8, command=self.helplist_item_edit)
2015 self.button_helplist_add = Button(
2016 frame_helplist_buttons, text='Add',
2017 width=8, command=self.helplist_item_add)
2018 self.button_helplist_remove = Button(
Cheryl Sabella7028e592017-08-26 14:26:02 -04002019 frame_helplist_buttons, text='Remove', state='disabled',
csabellae8eb17b2017-07-30 18:39:17 -04002020 width=8, command=self.helplist_item_remove)
2021
2022 # Pack widgets:
Terry Jan Reedy390eadd2017-08-30 00:59:11 -04002023 # Body.
2024 frame_window.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
2025 frame_editor.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
Tal Einat604e7b92018-09-25 15:10:14 +03002026 frame_shell.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
csabellae8eb17b2017-07-30 18:39:17 -04002027 frame_help.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
2028 # frame_run.
Terry Jan Reedy390eadd2017-08-30 00:59:11 -04002029 frame_run.pack(side=TOP, padx=5, pady=0, fill=X)
csabellae8eb17b2017-07-30 18:39:17 -04002030 startup_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
2031 self.startup_shell_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
2032 self.startup_editor_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
csabellae8eb17b2017-07-30 18:39:17 -04002033 # frame_win_size.
Terry Jan Reedy390eadd2017-08-30 00:59:11 -04002034 frame_win_size.pack(side=TOP, padx=5, pady=0, fill=X)
csabellae8eb17b2017-07-30 18:39:17 -04002035 win_size_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
2036 self.win_height_int.pack(side=RIGHT, anchor=E, padx=10, pady=5)
2037 win_height_title.pack(side=RIGHT, anchor=E, pady=5)
2038 self.win_width_int.pack(side=RIGHT, anchor=E, padx=10, pady=5)
2039 win_width_title.pack(side=RIGHT, anchor=E, pady=5)
Zackery Spytz9c284492019-11-13 00:13:33 -07002040 # frame_cursor_blink.
2041 frame_cursor_blink.pack(side=TOP, padx=5, pady=0, fill=X)
2042 cursor_blink_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
2043 self.cursor_blink_bool.pack(side=LEFT, padx=5, pady=5)
wohlganger58fc71c2017-09-10 16:19:47 -05002044 # frame_autocomplete.
2045 frame_autocomplete.pack(side=TOP, padx=5, pady=0, fill=X)
2046 auto_wait_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
2047 self.auto_wait_int.pack(side=TOP, padx=10, pady=5)
2048 # frame_paren.
2049 frame_paren1.pack(side=TOP, padx=5, pady=0, fill=X)
2050 paren_style_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
2051 self.paren_style_type.pack(side=TOP, padx=10, pady=5)
2052 frame_paren2.pack(side=TOP, padx=5, pady=0, fill=X)
2053 paren_time_title.pack(side=LEFT, anchor=W, padx=5)
2054 self.bell_on.pack(side=RIGHT, anchor=E, padx=15, pady=5)
2055 self.paren_flash_time.pack(side=TOP, anchor=W, padx=15, pady=5)
2056
Terry Jan Reedy390eadd2017-08-30 00:59:11 -04002057 # frame_save.
2058 frame_save.pack(side=TOP, padx=5, pady=0, fill=X)
2059 run_save_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
2060 self.save_auto_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
2061 self.save_ask_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
wohlganger58fc71c2017-09-10 16:19:47 -05002062 # frame_format.
2063 frame_format.pack(side=TOP, padx=5, pady=0, fill=X)
2064 format_width_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
2065 self.format_width_int.pack(side=TOP, padx=10, pady=5)
Tal Einat7123ea02019-07-23 15:22:11 +03002066 # frame_line_numbers_default.
2067 frame_line_numbers_default.pack(side=TOP, padx=5, pady=0, fill=X)
2068 line_numbers_default_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
2069 self.line_numbers_default_bool.pack(side=LEFT, padx=5, pady=5)
wohlganger58fc71c2017-09-10 16:19:47 -05002070 # frame_context.
2071 frame_context.pack(side=TOP, padx=5, pady=0, fill=X)
2072 context_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
2073 self.context_int.pack(side=TOP, padx=5, pady=5)
2074
Tal Einat604e7b92018-09-25 15:10:14 +03002075 # frame_auto_squeeze_min_lines
2076 frame_auto_squeeze_min_lines.pack(side=TOP, padx=5, pady=0, fill=X)
2077 auto_squeeze_min_lines_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
2078 self.auto_squeeze_min_lines_int.pack(side=TOP, padx=5, pady=5)
2079
csabellae8eb17b2017-07-30 18:39:17 -04002080 # frame_help.
2081 frame_helplist_buttons.pack(side=RIGHT, padx=5, pady=5, fill=Y)
2082 frame_helplist.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
2083 scroll_helplist.pack(side=RIGHT, anchor=W, fill=Y)
2084 self.helplist.pack(side=LEFT, anchor=E, expand=TRUE, fill=BOTH)
2085 self.button_helplist_edit.pack(side=TOP, anchor=W, pady=5)
2086 self.button_helplist_add.pack(side=TOP, anchor=W)
2087 self.button_helplist_remove.pack(side=TOP, anchor=W, pady=5)
2088
2089 def load_general_cfg(self):
2090 "Load current configuration settings for the general options."
wohlganger58fc71c2017-09-10 16:19:47 -05002091 # Set variables for all windows.
csabellae8eb17b2017-07-30 18:39:17 -04002092 self.startup_edit.set(idleConf.GetOption(
wohlganger58fc71c2017-09-10 16:19:47 -05002093 'main', 'General', 'editor-on-startup', type='bool'))
csabellae8eb17b2017-07-30 18:39:17 -04002094 self.win_width.set(idleConf.GetOption(
2095 'main', 'EditorWindow', 'width', type='int'))
2096 self.win_height.set(idleConf.GetOption(
2097 'main', 'EditorWindow', 'height', type='int'))
Zackery Spytz9c284492019-11-13 00:13:33 -07002098 self.cursor_blink.set(idleConf.GetOption(
2099 'main', 'EditorWindow', 'cursor-blink', type='bool'))
wohlganger58fc71c2017-09-10 16:19:47 -05002100 self.autocomplete_wait.set(idleConf.GetOption(
2101 'extensions', 'AutoComplete', 'popupwait', type='int'))
2102 self.paren_style.set(idleConf.GetOption(
2103 'extensions', 'ParenMatch', 'style'))
2104 self.flash_delay.set(idleConf.GetOption(
2105 'extensions', 'ParenMatch', 'flash-delay', type='int'))
2106 self.paren_bell.set(idleConf.GetOption(
2107 'extensions', 'ParenMatch', 'bell'))
2108
2109 # Set variables for editor windows.
2110 self.autosave.set(idleConf.GetOption(
2111 'main', 'General', 'autosave', default=0, type='bool'))
2112 self.format_width.set(idleConf.GetOption(
2113 'extensions', 'FormatParagraph', 'max-width', type='int'))
Tal Einat7123ea02019-07-23 15:22:11 +03002114 self.line_numbers_default.set(idleConf.GetOption(
2115 'main', 'EditorWindow', 'line-numbers-default', type='bool'))
wohlganger58fc71c2017-09-10 16:19:47 -05002116 self.context_lines.set(idleConf.GetOption(
Cheryl Sabella29996a12018-06-01 19:23:00 -04002117 'extensions', 'CodeContext', 'maxlines', type='int'))
wohlganger58fc71c2017-09-10 16:19:47 -05002118
Tal Einat604e7b92018-09-25 15:10:14 +03002119 # Set variables for shell windows.
2120 self.auto_squeeze_min_lines.set(idleConf.GetOption(
2121 'main', 'PyShell', 'auto-squeeze-min-lines', type='int'))
2122
csabellae8eb17b2017-07-30 18:39:17 -04002123 # Set additional help sources.
2124 self.user_helplist = idleConf.GetAllExtraHelpSourcesList()
2125 self.helplist.delete(0, 'end')
2126 for help_item in self.user_helplist:
2127 self.helplist.insert(END, help_item[0])
2128 self.set_add_delete_state()
2129
2130 def help_source_selected(self, event):
2131 "Handle event for selecting additional help."
2132 self.set_add_delete_state()
2133
2134 def set_add_delete_state(self):
2135 "Toggle the state for the help list buttons based on list entries."
2136 if self.helplist.size() < 1: # No entries in list.
Cheryl Sabella7028e592017-08-26 14:26:02 -04002137 self.button_helplist_edit.state(('disabled',))
2138 self.button_helplist_remove.state(('disabled',))
csabellae8eb17b2017-07-30 18:39:17 -04002139 else: # Some entries.
2140 if self.helplist.curselection(): # There currently is a selection.
Cheryl Sabella7028e592017-08-26 14:26:02 -04002141 self.button_helplist_edit.state(('!disabled',))
2142 self.button_helplist_remove.state(('!disabled',))
csabellae8eb17b2017-07-30 18:39:17 -04002143 else: # There currently is not a selection.
Cheryl Sabella7028e592017-08-26 14:26:02 -04002144 self.button_helplist_edit.state(('disabled',))
2145 self.button_helplist_remove.state(('disabled',))
csabellae8eb17b2017-07-30 18:39:17 -04002146
2147 def helplist_item_add(self):
2148 """Handle add button for the help list.
2149
2150 Query for name and location of new help sources and add
2151 them to the list.
2152 """
2153 help_source = HelpSource(self, 'New Help Source').result
2154 if help_source:
2155 self.user_helplist.append(help_source)
2156 self.helplist.insert(END, help_source[0])
2157 self.update_help_changes()
2158
2159 def helplist_item_edit(self):
2160 """Handle edit button for the help list.
2161
2162 Query with existing help source information and update
2163 config if the values are changed.
2164 """
2165 item_index = self.helplist.index(ANCHOR)
2166 help_source = self.user_helplist[item_index]
2167 new_help_source = HelpSource(
2168 self, 'Edit Help Source',
2169 menuitem=help_source[0],
2170 filepath=help_source[1],
2171 ).result
2172 if new_help_source and new_help_source != help_source:
2173 self.user_helplist[item_index] = new_help_source
2174 self.helplist.delete(item_index)
2175 self.helplist.insert(item_index, new_help_source[0])
2176 self.update_help_changes()
2177 self.set_add_delete_state() # Selected will be un-selected
2178
2179 def helplist_item_remove(self):
2180 """Handle remove button for the help list.
2181
2182 Delete the help list item from config.
2183 """
2184 item_index = self.helplist.index(ANCHOR)
2185 del(self.user_helplist[item_index])
2186 self.helplist.delete(item_index)
2187 self.update_help_changes()
2188 self.set_add_delete_state()
2189
2190 def update_help_changes(self):
2191 "Clear and rebuild the HelpFiles section in changes"
2192 changes['main']['HelpFiles'] = {}
2193 for num in range(1, len(self.user_helplist) + 1):
2194 changes.add_option(
2195 'main', 'HelpFiles', str(num),
2196 ';'.join(self.user_helplist[num-1][:2]))
2197
2198
csabella45bf7232017-07-26 19:09:58 -04002199class VarTrace:
2200 """Maintain Tk variables trace state."""
2201
2202 def __init__(self):
2203 """Store Tk variables and callbacks.
2204
2205 untraced: List of tuples (var, callback)
2206 that do not have the callback attached
2207 to the Tk var.
2208 traced: List of tuples (var, callback) where
2209 that callback has been attached to the var.
2210 """
2211 self.untraced = []
2212 self.traced = []
2213
Terry Jan Reedy5d0f30a2017-07-28 17:00:02 -04002214 def clear(self):
2215 "Clear lists (for tests)."
Terry Jan Reedy733d0f62017-08-07 14:22:44 -04002216 # Call after all tests in a module to avoid memory leaks.
Terry Jan Reedy5d0f30a2017-07-28 17:00:02 -04002217 self.untraced.clear()
2218 self.traced.clear()
2219
csabella45bf7232017-07-26 19:09:58 -04002220 def add(self, var, callback):
2221 """Add (var, callback) tuple to untraced list.
2222
2223 Args:
2224 var: Tk variable instance.
csabella5b591542017-07-28 14:40:59 -04002225 callback: Either function name to be used as a callback
2226 or a tuple with IdleConf config-type, section, and
2227 option names used in the default callback.
csabella45bf7232017-07-26 19:09:58 -04002228
2229 Return:
2230 Tk variable instance.
2231 """
2232 if isinstance(callback, tuple):
2233 callback = self.make_callback(var, callback)
2234 self.untraced.append((var, callback))
2235 return var
2236
2237 @staticmethod
2238 def make_callback(var, config):
2239 "Return default callback function to add values to changes instance."
2240 def default_callback(*params):
2241 "Add config values to changes instance."
2242 changes.add_option(*config, var.get())
2243 return default_callback
2244
2245 def attach(self):
2246 "Attach callback to all vars that are not traced."
2247 while self.untraced:
2248 var, callback = self.untraced.pop()
2249 var.trace_add('write', callback)
2250 self.traced.append((var, callback))
2251
2252 def detach(self):
2253 "Remove callback from traced vars."
2254 while self.traced:
2255 var, callback = self.traced.pop()
2256 var.trace_remove('write', var.trace_info()[0][1])
2257 self.untraced.append((var, callback))
2258
2259
csabella5b591542017-07-28 14:40:59 -04002260tracers = VarTrace()
2261
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04002262help_common = '''\
2263When you click either the Apply or Ok buttons, settings in this
2264dialog that are different from IDLE's default are saved in
2265a .idlerc directory in your home directory. Except as noted,
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -05002266these changes apply to all versions of IDLE installed on this
Terry Jan Reedye2e42272017-10-17 18:56:16 -04002267machine. [Cancel] only cancels changes made since the last save.
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04002268'''
2269help_pages = {
Terry Jan Reedye2e42272017-10-17 18:56:16 -04002270 'Fonts/Tabs':'''
2271Font sample: This shows what a selection of Basic Multilingual Plane
2272unicode characters look like for the current font selection. If the
2273selected font does not define a character, Tk attempts to find another
2274font that does. Substitute glyphs depend on what is available on a
2275particular system and will not necessarily have the same size as the
2276font selected. Line contains 20 characters up to Devanagari, 14 for
2277Tamil, and 10 for East Asia.
2278
2279Hebrew and Arabic letters should display right to left, starting with
2280alef, \u05d0 and \u0627. Arabic digits display left to right. The
2281Devanagari and Tamil lines start with digits. The East Asian lines
2282are Chinese digits, Chinese Hanzi, Korean Hangul, and Japanese
2283Hiragana and Katakana.
Serhiy Storchakaed6554c2017-10-28 03:22:44 +03002284
2285You can edit the font sample. Changes remain until IDLE is closed.
Terry Jan Reedye2e42272017-10-17 18:56:16 -04002286''',
Cheryl Sabella3866d9b2017-09-10 22:41:10 -04002287 'Highlights': '''
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04002288Highlighting:
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -05002289The IDLE Dark color theme is new in October 2015. It can only
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04002290be used with older IDLE releases if it is saved as a custom
2291theme, with a different name.
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -04002292''',
2293 'Keys': '''
2294Keys:
2295The IDLE Modern Unix key set is new in June 2016. It can only
2296be used with older IDLE releases if it is saved as a custom
2297key set, with a different name.
2298''',
wohlganger58fc71c2017-09-10 16:19:47 -05002299 'General': '''
2300General:
wohlgangerfae2c352017-06-27 21:36:23 -05002301
penguindustin96466302019-05-06 14:57:17 -04002302AutoComplete: Popupwait is milliseconds to wait after key char, without
wohlgangerfae2c352017-06-27 21:36:23 -05002303cursor movement, before popping up completion box. Key char is '.' after
2304identifier or a '/' (or '\\' on Windows) within a string.
2305
2306FormatParagraph: Max-width is max chars in lines after re-formatting.
2307Use with paragraphs in both strings and comment blocks.
2308
2309ParenMatch: Style indicates what is highlighted when closer is entered:
2310'opener' - opener '({[' corresponding to closer; 'parens' - both chars;
2311'expression' (default) - also everything in between. Flash-delay is how
2312long to highlight if cursor is not moved (0 means forever).
Cheryl Sabella29996a12018-06-01 19:23:00 -04002313
2314CodeContext: Maxlines is the maximum number of code context lines to
2315display when Code Context is turned on for an editor window.
Tal Einat604e7b92018-09-25 15:10:14 +03002316
2317Shell Preferences: Auto-Squeeze Min. Lines is the minimum number of lines
2318of output to automatically "squeeze".
wohlgangerfae2c352017-06-27 21:36:23 -05002319'''
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04002320}
2321
Steven M. Gavac11ccf32001-09-24 09:43:17 +00002322
Terry Jan Reedy93f35422015-10-13 22:03:51 -04002323def is_int(s):
2324 "Return 's is blank or represents an int'"
2325 if not s:
2326 return True
2327 try:
2328 int(s)
2329 return True
2330 except ValueError:
2331 return False
2332
2333
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002334class VerticalScrolledFrame(Frame):
2335 """A pure Tkinter vertically scrollable frame.
2336
2337 * Use the 'interior' attribute to place widgets inside the scrollable frame
2338 * Construct and pack/place/grid normally
2339 * This frame only allows vertical scrolling
2340 """
2341 def __init__(self, parent, *args, **kw):
2342 Frame.__init__(self, parent, *args, **kw)
2343
csabella7eb58832017-07-04 21:30:58 -04002344 # Create a canvas object and a vertical scrollbar for scrolling it.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002345 vscrollbar = Scrollbar(self, orient=VERTICAL)
2346 vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE)
Cheryl Sabella7028e592017-08-26 14:26:02 -04002347 canvas = Canvas(self, borderwidth=0, highlightthickness=0,
Terry Jan Reedyd0812292015-10-22 03:27:31 -04002348 yscrollcommand=vscrollbar.set, width=240)
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002349 canvas.pack(side=LEFT, fill=BOTH, expand=TRUE)
2350 vscrollbar.config(command=canvas.yview)
2351
csabella7eb58832017-07-04 21:30:58 -04002352 # Reset the view.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002353 canvas.xview_moveto(0)
2354 canvas.yview_moveto(0)
2355
csabella7eb58832017-07-04 21:30:58 -04002356 # Create a frame inside the canvas which will be scrolled with it.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002357 self.interior = interior = Frame(canvas)
2358 interior_id = canvas.create_window(0, 0, window=interior, anchor=NW)
2359
csabella7eb58832017-07-04 21:30:58 -04002360 # Track changes to the canvas and frame width and sync them,
2361 # also updating the scrollbar.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002362 def _configure_interior(event):
csabella7eb58832017-07-04 21:30:58 -04002363 # Update the scrollbars to match the size of the inner frame.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002364 size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
2365 canvas.config(scrollregion="0 0 %s %s" % size)
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002366 interior.bind('<Configure>', _configure_interior)
2367
2368 def _configure_canvas(event):
2369 if interior.winfo_reqwidth() != canvas.winfo_width():
csabella7eb58832017-07-04 21:30:58 -04002370 # Update the inner frame's width to fill the canvas.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002371 canvas.itemconfigure(interior_id, width=canvas.winfo_width())
2372 canvas.bind('<Configure>', _configure_canvas)
2373
2374 return
2375
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002376
Steven M. Gava44d3d1a2001-07-31 06:59:02 +00002377if __name__ == '__main__':
Terry Jan Reedy4d921582018-06-19 19:12:52 -04002378 from unittest import main
2379 main('idlelib.idle_test.test_configdialog', verbosity=2, exit=False)
2380
Terry Jan Reedy2e8234a2014-05-29 01:46:26 -04002381 from idlelib.idle_test.htest import run
Terry Jan Reedy47304c02015-10-20 02:15:28 -04002382 run(ConfigDialog)